diff --git a/cmd/tidb-dashboard/main.go b/cmd/tidb-dashboard/main.go index c3b2eb22f6..9b575f9652 100755 --- a/cmd/tidb-dashboard/main.go +++ b/cmd/tidb-dashboard/main.go @@ -21,6 +21,7 @@ import ( _ "net/http/pprof" // #nosec "os" "os/signal" + "path" "strings" "sync" "syscall" @@ -31,13 +32,13 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" - _ "github.com/pingcap/tidb-dashboard/internal/resource/distrores" "github.com/pingcap/tidb-dashboard/pkg/apiserver" "github.com/pingcap/tidb-dashboard/pkg/config" keyvisualregion "github.com/pingcap/tidb-dashboard/pkg/keyvisual/region" "github.com/pingcap/tidb-dashboard/pkg/swaggerserver" "github.com/pingcap/tidb-dashboard/pkg/uiserver" "github.com/pingcap/tidb-dashboard/pkg/utils/version" + "github.com/pingcap/tidb-dashboard/util/distro" ) type DashboardCLIConfig struct { @@ -147,6 +148,26 @@ func buildTLSConfig(caPath, keyPath, certPath *string) *tls.Config { return tlsConfig } +const ( + distroResFolderName string = "distro-res" + distroStringsResFileName string = "strings.json" +) + +func loadDistroStringsRes() { + exePath, err := os.Executable() + if err != nil { + log.Fatal("Failed to get executable path", zap.Error(err)) + } + + distroStringsResPath := path.Join(path.Dir(exePath), distroResFolderName, distroStringsResFileName) + distroStringsRes, err := distro.ReadResourceStringsFromFile(distroStringsResPath) + if err != nil { + log.Fatal("Failed to load distro strings res", zap.String("path", distroStringsResPath), zap.Error(err)) + } + + distro.ReplaceGlobal(distroStringsRes) +} + func main() { // Flushing any buffered log entries defer log.Sync() //nolint:errcheck @@ -164,10 +185,12 @@ func main() { log.SetLevel(zapcore.DebugLevel) } + loadDistroStringsRes() + listenAddr := fmt.Sprintf("%s:%d", cliConfig.ListenHost, cliConfig.ListenPort) listener, err := net.Listen("tcp", listenAddr) if err != nil { - log.Fatal("TiDB Dashboard server listen failed", zap.String("addr", listenAddr), zap.Error(err)) + log.Fatal("Dashboard server listen failed", zap.String("addr", listenAddr), zap.Error(err)) } var customKeyVisualProvider *keyvisualregion.DataProvider diff --git a/internal/resource/distrores/populate.go b/internal/resource/distrores/populate.go deleted file mode 100644 index 2495d75b80..0000000000 --- a/internal/resource/distrores/populate.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 PingCAP, Inc. Licensed under Apache-2.0. - -package distrores - -import ( - "github.com/pingcap/tidb-dashboard/util/distro" -) - -func init() { - distro.ReplaceGlobal(resource) -} diff --git a/internal/resource/distrores/strings.go b/internal/resource/distrores/strings.go deleted file mode 100644 index bbc19b2e3f..0000000000 --- a/internal/resource/distrores/strings.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2021 PingCAP, Inc. Licensed under Apache-2.0. - -// Note: The content of this file will be replaced by the distro resource. - -package distrores - -import ( - "github.com/pingcap/tidb-dashboard/util/distro" -) - -var resource = distro.DistributionResource{} diff --git a/pkg/uiserver/embedded_assets_rewriter.go b/pkg/uiserver/embedded_assets_rewriter.go index 958f4a8a18..6a11c807a3 100644 --- a/pkg/uiserver/embedded_assets_rewriter.go +++ b/pkg/uiserver/embedded_assets_rewriter.go @@ -7,9 +7,13 @@ package uiserver import ( "net/http" "os" + "path" "sync" "time" + "go.uber.org/zap" + + "github.com/pingcap/log" "github.com/pingcap/tidb-dashboard/pkg/config" ) @@ -17,7 +21,13 @@ var once sync.Once func Assets(cfg *config.Config) http.FileSystem { once.Do(func() { - RewriteAssets(assets, cfg, func(fs http.FileSystem, f http.File, path, newContent string, bs []byte) { + exePath, err := os.Executable() + if err != nil { + log.Fatal("Failed to get executable path", zap.Error(err)) + } + + distroResFolderPath := path.Join(path.Dir(exePath), distroResFolderName) + RewriteAssets(assets, cfg, distroResFolderPath, func(fs http.FileSystem, f http.File, path, newContent string, bs []byte) { m := fs.(vfsgen۰FS) fi := f.(os.FileInfo) m[path] = &vfsgen۰CompressedFileInfo{ diff --git a/pkg/uiserver/uiserver.go b/pkg/uiserver/uiserver.go index 484f3b03ad..8a50d52829 100644 --- a/pkg/uiserver/uiserver.go +++ b/pkg/uiserver/uiserver.go @@ -5,10 +5,15 @@ package uiserver import ( "bytes" "compress/gzip" + "encoding/base64" + "encoding/json" + "errors" "html" "io" "io/ioutil" "net/http" + "os" + "path" "strings" "github.com/pingcap/log" @@ -16,11 +21,16 @@ import ( "go.uber.org/zap" "github.com/pingcap/tidb-dashboard/pkg/config" + "github.com/pingcap/tidb-dashboard/util/distro" +) + +const ( + distroResFolderName = "distro-res" ) type UpdateContentFunc func(fs http.FileSystem, oldFile http.File, path, newContent string, zippedBytes []byte) -func RewriteAssets(fs http.FileSystem, cfg *config.Config, updater UpdateContentFunc) { +func RewriteAssets(fs http.FileSystem, cfg *config.Config, distroResFolderPath string, updater UpdateContentFunc) { if fs == nil { return } @@ -39,6 +49,9 @@ func RewriteAssets(fs http.FileSystem, cfg *config.Config, updater UpdateContent tmplText := string(bs) updated := strings.ReplaceAll(tmplText, "__PUBLIC_PATH_PREFIX__", html.EscapeString(cfg.PublicPathPrefix)) + distroStrings, _ := json.Marshal(distro.R()) // this will never fail + updated = strings.ReplaceAll(updated, "__DISTRO_STRINGS_RES__", base64.StdEncoding.EncodeToString(distroStrings)) + var b bytes.Buffer w := gzip.NewWriter(&b) if _, err := w.Write([]byte(updated)); err != nil { @@ -53,6 +66,67 @@ func RewriteAssets(fs http.FileSystem, cfg *config.Config, updater UpdateContent rewrite("/index.html") rewrite("/diagnoseReport.html") + + if err := overrideDistroAssetsRes(fs, distroResFolderPath, updater); err != nil { + log.Fatal("Failed to load distro assets res", zap.Error(err)) + } +} + +func overrideDistroAssetsRes(fs http.FileSystem, distroResFolderPath string, updater UpdateContentFunc) error { + info, err := os.Stat(distroResFolderPath) + if errors.Is(err, os.ErrNotExist) || !info.IsDir() { + // just ignore if the folder doesn't exist or it's not a folder + return nil + } + if err != nil { + return err + } + + // traverse + files, err := ioutil.ReadDir(distroResFolderPath) + if err != nil { + return err + } + for _, file := range files { + if err := overrideSingleDistroAsset(fs, distroResFolderPath, file.Name(), updater); err != nil { + return err + } + } + return nil +} + +func overrideSingleDistroAsset(fs http.FileSystem, distroResFolderPath, assetName string, updater UpdateContentFunc) error { + assetPath := path.Join("/", distroResFolderName, assetName) + targetFile, err := fs.Open(assetPath) + if err != nil { + // has no target asset to be overried, skip + return nil + } + defer targetFile.Close() + + assetFullPath := path.Join(distroResFolderPath, assetName) + sourceFile, err := os.Open(assetFullPath) + if err != nil { + return err + } + defer sourceFile.Close() + + data, err := ioutil.ReadAll(sourceFile) + if err != nil { + return err + } + + var b bytes.Buffer + w := gzip.NewWriter(&b) + if _, err := w.Write(data); err != nil { + return err + } + if err := w.Close(); err != nil { + return err + } + + updater(fs, targetFile, assetPath, string(data), b.Bytes()) + return nil } func Handler(root http.FileSystem) http.Handler { diff --git a/scripts/distro/replace_resources.sh b/scripts/distro/replace_resources.sh deleted file mode 100755 index 6e499232b6..0000000000 --- a/scripts/distro/replace_resources.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# This script replaces the resources in the project with specific distribution resources. -# -# Required params: -# DISTRIBUTION_DIR -# Specify the resource directory to be used for replacement. - -set -euo pipefail - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -PROJECT_DIR=$(cd "$DIR/../.."; pwd) - -echo "+ Preflight check" -if [ -z "${DISTRIBUTION_DIR}" ] || [ ! -d "${DISTRIBUTION_DIR}" ]; then - echo " - Error: DISTRIBUTION_DIR must be specified" - exit 1 -fi - -echo "+ Replace distribution resources" - -cp -f "${DISTRIBUTION_DIR}/strings.go" "${PROJECT_DIR}/internal/resource/distrores/strings.go" -\cp -f "${DISTRIBUTION_DIR}/logo.svg" "${PROJECT_DIR}/ui/dashboardApp/layout/signin/logo.svg" || true -\cp -f "${DISTRIBUTION_DIR}/landing.svg" "${PROJECT_DIR}/ui/dashboardApp/layout/signin/landing.svg" || true -\cp -f "${DISTRIBUTION_DIR}/logo-icon-light.svg" "${PROJECT_DIR}/ui/dashboardApp/layout/main/Sider/logo-icon-light.svg" || true -\cp -f "${DISTRIBUTION_DIR}/favicon.ico" "${PROJECT_DIR}/ui/public/favicon.ico" || true - -echo " - Success!" diff --git a/scripts/distro/write_strings.go b/scripts/distro/write_strings.go index f898ec32a8..60302f65ea 100644 --- a/scripts/distro/write_strings.go +++ b/scripts/distro/write_strings.go @@ -10,7 +10,6 @@ import ( "github.com/pingcap/log" "go.uber.org/zap" - _ "github.com/pingcap/tidb-dashboard/internal/resource/distrores" "github.com/pingcap/tidb-dashboard/util/distro" ) diff --git a/scripts/distro/write_strings.sh b/scripts/distro/write_strings.sh index b05aaa9106..9848b9f793 100755 --- a/scripts/distro/write_strings.sh +++ b/scripts/distro/write_strings.sh @@ -1,25 +1,17 @@ #!/usr/bin/env bash -# This script writes distribution strings in json which is required for the frontend. +# This script outputs distribution strings to json which is required for the frontend. set -euo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" PROJECT_DIR=$(cd "$DIR/../.."; pwd) -BUILD_TAG="" -if [[ -f "${PROJECT_DIR}/internal/resource/distrores/strings.go" ]]; then - echo "+ Existing distribution resource is detected, using it to write strings" - BUILD_TAG=distro -fi - -echo "+ Write resource strings" +echo "+ Write distro strings" cd "$PROJECT_DIR" -# FIXME: distro/write_strings needs to access the /internal package, which is not allowed to be invoked in another module -# Currently we workaround this by invoking in the TiDB Dashboard module. -go run -tags="${BUILD_TAG}" scripts/distro/write_strings.go -o="${PROJECT_DIR}/ui/lib/distribution.json" +go run scripts/distro/write_strings.go -o="${PROJECT_DIR}/ui/lib/distro_strings.json" -echo " - Success! Resource strings:" -cat "${PROJECT_DIR}/ui/lib/distribution.json" +echo " - Success! Distro strings:" +cat "${PROJECT_DIR}/ui/lib/distro_strings.json" echo diff --git a/ui/config-overrides.js b/ui/config-overrides.js index 3bfb2a176b..ab0d7f675b 100755 --- a/ui/config-overrides.js +++ b/ui/config-overrides.js @@ -1,5 +1,5 @@ const path = require('path') -const fs = require('fs') +const fs = require('fs-extra') const os = require('os') const { override, @@ -17,16 +17,37 @@ const WebpackBar = require('webpackbar') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const rewireHtmlWebpackPlugin = require('react-app-rewire-html-webpack-plugin') +function copyDistroRes() { + const distroResPath = '../bin/distro-res' + if (fs.existsSync(distroResPath)) { + fs.copySync(distroResPath, './public/distro-res') + } +} + function injectDistroToHTML(config, env) { - const distroInfo = Object.entries(require('./lib/distribution.json')).reduce( - (prev, [k, v]) => { - return { - ...prev, - [`distro_${k}`]: v, - } + let distroStringsResMeta = '__DISTRO_STRINGS_RES__' + + // For dev mode, + // we copy distro assets from bin/distro-res to public/distro-res to override the default assets, + // read distro strings res from public/distro-res/strings.json and encode it by base64 if it exists. + // For production mode, we keep the "__DISTRO_STRINGS_RES__" value, it will be replaced by the backend RewriteAssets() method in the run time. + if (isBuildAsDevServer()) { + copyDistroRes() + + const distroStringsResFilePath = './public/distro-res/strings.json' + if (fs.existsSync(distroStringsResFilePath)) { + const distroStringsRes = require(distroStringsResFilePath) + distroStringsResMeta = btoa(JSON.stringify(distroStringsRes)) + } + } + + // Store the distro strings res in the html head meta, + // HtmlWebpacPlugin will write this meta into the html head. + const distroInfo = { + meta: { + 'x-distro-strings-res': distroStringsResMeta, }, - {} - ) + } return rewireHtmlWebpackPlugin(config, env, distroInfo) } @@ -183,10 +204,10 @@ module.exports = override( ) ), disableMinimizeByEnv(), - addExtraEntries(), supportDynamicPublicPathPrefix(), overrideProcessEnv({ REACT_APP_RELEASE_VERSION: JSON.stringify(getInternalVersion()), }), - injectDistroToHTML + injectDistroToHTML, + addExtraEntries() ) diff --git a/ui/dashboardApp/index.ts b/ui/dashboardApp/index.ts index a183e0f41e..9eedc5c2b3 100755 --- a/ui/dashboardApp/index.ts +++ b/ui/dashboardApp/index.ts @@ -10,6 +10,7 @@ import AppRegistry from '@lib/utils/registry' import * as routing from '@lib/utils/routing' import * as auth from '@lib/utils/auth' import * as i18n from '@lib/utils/i18n' +import { distro } from '@lib/utils/i18n' import { saveAppOptions, loadAppOptions } from '@lib/utils/appOptions' import { initSentryRoutingInstrument, @@ -148,6 +149,8 @@ async function webPageStart() { } async function main() { + document.title = `${distro.tidb} Dashboard` + if (routing.isPortalPage()) { // the portal page is only used to receive options function handlePortalEvent(event) { diff --git a/ui/dashboardApp/layout/main/Sider/Banner.tsx b/ui/dashboardApp/layout/main/Sider/Banner.tsx index 37edf1b06b..f20a66d7d0 100644 --- a/ui/dashboardApp/layout/main/Sider/Banner.tsx +++ b/ui/dashboardApp/layout/main/Sider/Banner.tsx @@ -3,13 +3,14 @@ import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons' import { useSize } from 'ahooks' import Flexbox from '@g07cha/flexbox-react' import { useSpring, animated } from 'react-spring' -import { InfoInfoResponse } from '@lib/client' import { useTranslation } from 'react-i18next' import { TFunction } from 'i18next' -import { ReactComponent as Logo } from './logo-icon-light.svg' -import styles from './Banner.module.less' +import { InfoInfoResponse } from '@lib/client' import { store } from '@lib/utils/store' +import publicPathPrefix from '@lib/utils/publicPathPrefix' + +import styles from './Banner.module.less' const toggleWidth = 40 const toggleHeight = 50 @@ -81,7 +82,10 @@ export default function ToggleBanner({ >
- +
diff --git a/ui/dashboardApp/layout/signin/index.module.less b/ui/dashboardApp/layout/signin/index.module.less index a58bd5b744..1993d71400 100644 --- a/ui/dashboardApp/layout/signin/index.module.less +++ b/ui/dashboardApp/layout/signin/index.module.less @@ -37,11 +37,15 @@ margin: auto; } +.landingContainer { + flex-grow: 1; +} + .landing { flex-grow: 1; - background-image: url(./landing.svg); background-size: cover; background-position: center left; + height: 100%; } .logo { diff --git a/ui/dashboardApp/layout/signin/index.tsx b/ui/dashboardApp/layout/signin/index.tsx index e9bf6f84cc..29fa1766a5 100755 --- a/ui/dashboardApp/layout/signin/index.tsx +++ b/ui/dashboardApp/layout/signin/index.tsx @@ -29,11 +29,11 @@ import { getAuthURL } from '@lib/utils/authSSO' import { AuthTypes } from '@lib/utils/auth' import { isDistro } from '@lib/utils/i18n' import * as auth from '@lib/utils/auth' +import { useIsFeatureSupport } from '@lib/utils/store' +import publicPathPrefix from '@lib/utils/publicPathPrefix' import { Root, AppearAnimate, LanguageDropdown } from '@lib/components' import styles from './index.module.less' -import { ReactComponent as Logo } from './logo.svg' -import { useIsFeatureSupport } from '@lib/utils/store' enum DisplayFormType { uninitialized, @@ -256,7 +256,10 @@ function TiDBSignInForm({ successRoute, onClickAlternative }) { initialValues={{ username: lastLoginUsername }} form={refForm} > - +

{t('signin.form.tidb_auth.title')}

@@ -340,7 +343,10 @@ function CodeSignInForm({ successRoute, onClickAlternative }) {
- +

{t('signin.form.code_auth.title')}

@@ -400,7 +406,10 @@ function SSOSignInForm({ successRoute, onClickAlternative }) {
- +