Browse Source

Merge branch 'agile' of https://github.com/visuddhinanda/mint into agile

visuddhinanda 3 years ago
parent
commit
7d9f46c019
68 changed files with 683 additions and 428 deletions
  1. 2 0
      dashboard-fluent/.env
  2. 0 10
      dashboard-fluent/.env.orig
  3. 2 9
      dashboard-fluent/.gitignore
  4. 14 0
      dashboard-fluent/README.md
  5. 13 0
      dashboard-fluent/index.html
  6. 17 37
      dashboard-fluent/package.json
  7. BIN
      dashboard-fluent/public/favicon.ico
  8. 0 43
      dashboard-fluent/public/index.html
  9. 1 0
      dashboard-fluent/public/logo.svg
  10. BIN
      dashboard-fluent/public/logo192.png
  11. BIN
      dashboard-fluent/public/logo512.png
  12. 0 25
      dashboard-fluent/public/manifest.json
  13. 0 3
      dashboard-fluent/public/robots.txt
  14. 0 2
      dashboard-fluent/src/App.css
  15. 0 9
      dashboard-fluent/src/App.test.tsx
  16. 19 17
      dashboard-fluent/src/App.tsx
  17. 43 3
      dashboard-fluent/src/Router.tsx
  18. 5 0
      dashboard-fluent/src/components/Loading.tsx
  19. 5 0
      dashboard-fluent/src/hooks.ts
  20. 0 15
      dashboard-fluent/src/index.tsx
  21. 15 0
      dashboard-fluent/src/layouts/anonymous/index.tsx
  22. 15 0
      dashboard-fluent/src/layouts/dashboard/index.tsx
  23. 31 0
      dashboard-fluent/src/locales.ts
  24. 0 3
      dashboard-fluent/src/locales/en-US/index.ts
  25. 0 58
      dashboard-fluent/src/locales/index.ts
  26. 0 11
      dashboard-fluent/src/locales/languages.ts
  27. 0 52
      dashboard-fluent/src/locales/zh-Hans/index.ts
  28. 0 3
      dashboard-fluent/src/locales/zh-Hant/index.ts
  29. 7 0
      dashboard-fluent/src/main.tsx
  30. 0 23
      dashboard-fluent/src/pages/Home.tsx
  31. 0 9
      dashboard-fluent/src/pages/NotFound.tsx
  32. 5 0
      dashboard-fluent/src/pages/forbidden.tsx
  33. 5 0
      dashboard-fluent/src/pages/home.tsx
  34. 5 0
      dashboard-fluent/src/pages/loading.tsx
  35. 5 0
      dashboard-fluent/src/pages/not-found.tsx
  36. 5 0
      dashboard-fluent/src/pages/users/confirm/new.tsx
  37. 8 0
      dashboard-fluent/src/pages/users/confirm/verify.tsx
  38. 5 0
      dashboard-fluent/src/pages/users/forgot-password.tsx
  39. 5 0
      dashboard-fluent/src/pages/users/logs.tsx
  40. 8 0
      dashboard-fluent/src/pages/users/reset-password.tsx
  41. 5 0
      dashboard-fluent/src/pages/users/sign-in.tsx
  42. 5 0
      dashboard-fluent/src/pages/users/sign-up.tsx
  43. 5 0
      dashboard-fluent/src/pages/users/unlock/new.tsx
  44. 8 0
      dashboard-fluent/src/pages/users/unlock/verify.tsx
  45. 0 1
      dashboard-fluent/src/react-app-env.d.ts
  46. 131 0
      dashboard-fluent/src/reducers/current-user.ts
  47. 50 0
      dashboard-fluent/src/reducers/layout.ts
  48. 0 15
      dashboard-fluent/src/reportWebVitals.ts
  49. 99 0
      dashboard-fluent/src/request.ts
  50. 0 5
      dashboard-fluent/src/setupTests.ts
  51. 16 0
      dashboard-fluent/src/store.ts
  52. 1 0
      dashboard-fluent/src/vite-env.d.ts
  53. 9 14
      dashboard-fluent/tsconfig.json
  54. 9 0
      dashboard-fluent/tsconfig.node.json
  55. 17 0
      dashboard-fluent/vite.config.ts
  56. 1 0
      docker/alpine/Dockerfile
  57. 63 43
      docker/jammy/Dockerfile
  58. 6 5
      docker/jammy/conan/conanfile.txt
  59. 1 1
      docker/jammy/conan/profiles/amd64
  60. 1 1
      docker/jammy/conan/profiles/arm64
  61. 1 1
      docker/jammy/conan/profiles/armhf
  62. 1 1
      docker/jammy/conan/toolchains/amd64.cmake
  63. 1 1
      docker/jammy/conan/toolchains/arm64.cmake
  64. 1 1
      docker/jammy/conan/toolchains/armhf.cmake
  65. 5 5
      docker/jammy/etc/envoy.yaml
  66. 5 0
      docker/jammy/etc/nginx.conf
  67. 1 1
      docker/jammy/start.sh
  68. 1 1
      scripts/spring/user/setup.sh

+ 2 - 0
dashboard-fluent/.env

@@ -0,0 +1,2 @@
+VITE_APP_GRPC_HOST=http://localhost:9999
+VITE_APP_ENABLE_LOCAL_TOKEN=true

+ 0 - 10
dashboard-fluent/.env.orig

@@ -1,10 +0,0 @@
-GENERATE_SOURCEMAP=false
-BROWSER=none
-PUBLIC_URL=/my
-HOST=0.0.0.0
-PORT=20080
-
-#REACT_APP_GRPC_HOST=http://127.0.0.1:10012
-REACT_APP_DEFAULT_LOCALE=zh-Hans
-REACT_APP_LANGUAGES=en-US,zh-Hans,zh-Hant
-REACT_APP_API_HOST=https://jeremy.spring.wikipali.org

+ 2 - 9
dashboard-fluent/.gitignore

@@ -1,12 +1,5 @@
 /node_modules/
 /yarn.lock
 
-# production
-/build/
-
-# misc
-.env
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
+/dist/
+/.env.*

+ 14 - 0
dashboard-fluent/README.md

@@ -0,0 +1,14 @@
+# USAGE
+
+- 开发环境
+
+  ```bash
+  # 安装依赖库
+  yarn install
+  # 创建开发环境配置文件
+  cp .env .env.development.local
+  # 启动 dev server
+  yarn run dev
+  ```
+
+- 修改 **port** 和 **proxy** 配置在: `vite.config.ts`

+ 13 - 0
dashboard-fluent/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/logo.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Mint</title>
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>

+ 17 - 37
dashboard-fluent/package.json

@@ -1,24 +1,23 @@
 {
   "name": "dashboard",
-  "version": "0.1.0",
   "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "tsc && vite build",
+    "preview": "vite preview"
+  },
   "dependencies": {
-    "@fluentui/react": "^8.98.7",
+    "@fluentui/react": "^8.98.8",
     "@fortawesome/fontawesome-free": "^6.2.0",
     "@reduxjs/toolkit": "^1.8.6",
-    "@testing-library/jest-dom": "^5.14.1",
-    "@testing-library/react": "^13.0.0",
-    "@testing-library/user-event": "^13.2.1",
     "@types/google-protobuf": "^3.15.6",
-    "@types/jest": "^27.0.1",
     "@types/js-cookie": "^3.0.2",
-    "@types/node": "^16.7.13",
-    "@types/react": "^18.0.0",
     "@types/react-big-calendar": "^0.38.2",
     "@types/react-color": "^3.0.6",
     "@types/react-copy-to-clipboard": "^5.0.4",
-    "@types/react-dom": "^18.0.0",
-    "@types/react-pdf": "^5.7.2",
+    "@types/react-pdf": "^5.7.3",
     "@uiw/react-md-editor": "^3.19.1",
     "dayjs": "^1.11.6",
     "dinero.js": "^2.0.0-alpha.8",
@@ -52,36 +51,17 @@
     "react-quill": "^2.0.0",
     "react-redux": "^8.0.4",
     "react-router-dom": "^6.4.2",
-    "react-scripts": "5.0.1",
     "react-sparklines": "^1.7.0",
     "react-syntax-highlighter": "^15.5.0",
+    "redux-thunk": "^2.4.1",
     "timezones-list": "^3.0.1",
-    "typescript": "^4.4.2",
-    "video.js": "^7.20.3",
-    "web-vitals": "^2.1.0"
-  },
-  "scripts": {
-    "start": "react-scripts start",
-    "build": "react-scripts build",
-    "test": "react-scripts test",
-    "eject": "react-scripts eject"
-  },
-  "eslintConfig": {
-    "extends": [
-      "react-app",
-      "react-app/jest"
-    ]
+    "video.js": "^7.20.3"
   },
-  "browserslist": {
-    "production": [
-      ">0.2%",
-      "not dead",
-      "not op_mini all"
-    ],
-    "development": [
-      "last 1 chrome version",
-      "last 1 firefox version",
-      "last 1 safari version"
-    ]
+  "devDependencies": {
+    "@types/react": "^18.0.22",
+    "@types/react-dom": "^18.0.7",
+    "@vitejs/plugin-react": "^2.2.0",
+    "typescript": "^4.6.4",
+    "vite": "^3.2.0"
   }
 }

BIN
dashboard-fluent/public/favicon.ico


+ 0 - 43
dashboard-fluent/public/index.html

@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
-    <meta name="theme-color" content="#000000" />
-    <meta
-      name="description"
-      content="Web site created using create-react-app"
-    />
-    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-    <!--
-      manifest.json provides metadata used when your web app is installed on a
-      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-    -->
-    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-    <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
-      It will be replaced with the URL of the `public` folder during the build.
-      Only files inside the `public` folder can be referenced from the HTML.
-
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
-      work correctly both with client-side routing and a non-root public URL.
-      Learn how to configure a non-root public URL by running `npm run build`.
-    -->
-    <title>React App</title>
-  </head>
-  <body>
-    <noscript>You need to enable JavaScript to run this app.</noscript>
-    <div id="root"></div>
-    <!--
-      This HTML file is a template.
-      If you open it directly in the browser, you will see an empty page.
-
-      You can add webfonts, meta tags, or analytics to this file.
-      The build step will place the bundled scripts into the <body> tag.
-
-      To begin the development, run `npm start` or `yarn start`.
-      To create a production bundle, use `npm run build` or `yarn build`.
-    -->
-  </body>
-</html>

+ 1 - 0
dashboard-fluent/public/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

BIN
dashboard-fluent/public/logo192.png


BIN
dashboard-fluent/public/logo512.png


+ 0 - 25
dashboard-fluent/public/manifest.json

@@ -1,25 +0,0 @@
-{
-  "short_name": "React App",
-  "name": "Create React App Sample",
-  "icons": [
-    {
-      "src": "favicon.ico",
-      "sizes": "64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
-    },
-    {
-      "src": "logo192.png",
-      "type": "image/png",
-      "sizes": "192x192"
-    },
-    {
-      "src": "logo512.png",
-      "type": "image/png",
-      "sizes": "512x512"
-    }
-  ],
-  "start_url": ".",
-  "display": "standalone",
-  "theme_color": "#000000",
-  "background_color": "#ffffff"
-}

+ 0 - 3
dashboard-fluent/public/robots.txt

@@ -1,3 +0,0 @@
-# https://www.robotstxt.org/robotstxt.html
-User-agent: *
-Disallow:

+ 0 - 2
dashboard-fluent/src/App.css

@@ -1,5 +1,3 @@
-@import "~react-quill/dist/quill.snow.css";
-
 body {
   margin: 0;
   padding: 0;

+ 0 - 9
dashboard-fluent/src/App.test.tsx

@@ -1,9 +0,0 @@
-import React from 'react';
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
-  render(<App />);
-  const linkElement = screen.getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});

+ 19 - 17
dashboard-fluent/src/App.tsx

@@ -1,29 +1,31 @@
-import { BrowserRouter } from "react-router-dom";
+import { Suspense } from "react";
 import { IntlProvider } from "react-intl";
+import { Provider } from "react-redux";
+import { BrowserRouter } from "react-router-dom";
 
+import store from "./store";
+import Loading from "./components/Loading";
 import Router from "./Router";
-import locales, {
-  get as getLocale,
-  DEFAULT as DEFAULT_LOCALE,
-} from "./locales";
+import { get as getLocale } from "./locales";
+
+import "react-quill/dist/quill.snow.css";
 
 import "./App.css";
 
 const lang = getLocale();
-const i18n = locales(lang);
 
-function Widget() {
+const Widget = () => {
   return (
-    <IntlProvider
-      messages={i18n.messages}
-      locale={lang}
-      defaultLocale={DEFAULT_LOCALE}
-    >
-      <BrowserRouter basename={process.env.PUBLIC_URL}>
-        <Router />
-      </BrowserRouter>
-    </IntlProvider>
+    <Provider store={store}>
+      <IntlProvider messages={{}} locale={lang} defaultLocale={"en-US"}>
+        <BrowserRouter basename={import.meta.env.BASE_URL}>
+          <Suspense fallback={<Loading />}>
+            <Router />
+          </Suspense>
+        </BrowserRouter>
+      </IntlProvider>
+    </Provider>
   );
-}
+};
 
 export default Widget;

+ 43 - 3
dashboard-fluent/src/Router.tsx

@@ -1,12 +1,52 @@
+import { lazy } from "react";
 import { Route, Routes } from "react-router-dom";
 
-import Home from "./pages/Home";
-import NotFound from "./pages/NotFound";
+import Anonymous from "./layouts/anonymous";
+import Dashboard from "./layouts/dashboard";
+
+const Home = lazy(() => import("./pages/home"));
+const NotFound = lazy(() => import("./pages/not-found"));
+const Forbidden = lazy(() => import("./pages/forbidden"));
+const Loading = lazy(() => import("./pages/loading"));
+const UsersSignIn = lazy(() => import("./pages/users/sign-in"));
+const UsersSignUp = lazy(() => import("./pages/users/sign-up"));
+const UsersLogs = lazy(() => import("./pages/users/logs"));
+const UsersConfirmNew = lazy(() => import("./pages/users/confirm/new"));
+const UsersConfirmVerify = lazy(() => import("./pages/users/confirm/verify"));
+const UsersUnlockNew = lazy(() => import("./pages/users/unlock/new"));
+const UsersUnlockVerify = lazy(() => import("./pages/users/unlock/verify"));
+const UsersForgotPassword = lazy(() => import("./pages/users/forgot-password"));
+const UsersResetPassword = lazy(() => import("./pages/users/reset-password"));
 
 const Widget = () => {
   return (
     <Routes>
-      {/* PLEASE KEEP THOSE ARE THE LAST TWO ROUTES */}
+      <Route path="anonymous" element={<Anonymous />}>
+        <Route path="users">
+          <Route path="sign-in" element={<UsersSignIn />} />
+          <Route path="sign-up" element={<UsersSignUp />} />
+          <Route path="confirm">
+            <Route path="new" element={<UsersConfirmNew />} />
+            <Route path="verify/:token" element={<UsersConfirmVerify />} />
+          </Route>
+          <Route path="unlock">
+            <Route path="new" element={<UsersUnlockNew />} />
+            <Route path="verify/:token" element={<UsersUnlockVerify />} />
+          </Route>
+          <Route
+            path="reset-password/:token"
+            element={<UsersResetPassword />}
+          />
+          <Route path="forgot-password" element={<UsersForgotPassword />} />
+        </Route>
+      </Route>
+      <Route path="dashboard" element={<Dashboard />}>
+        <Route path="users">
+          <Route path="logs" element={<UsersLogs />} />
+        </Route>
+      </Route>
+      <Route path="loading" element={<Loading />} />
+      <Route path="forbidden" element={<Forbidden />} />
       <Route path="" element={<Home />} />
       <Route path="*" element={<NotFound />} />
     </Routes>

+ 5 - 0
dashboard-fluent/src/components/Loading.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>loading</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/hooks.ts

@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
+import type { RootState, AppDispatch } from "./store";
+
+export const useAppDispatch: () => AppDispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

+ 0 - 15
dashboard-fluent/src/index.tsx

@@ -1,15 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-
-import App from "./App";
-import reportWebVitals from "./reportWebVitals";
-
-const root = ReactDOM.createRoot(
-  document.getElementById("root") as HTMLElement
-);
-root.render(<App />);
-
-// If you want to start measuring performance in your app, pass a function
-// to log results (for example: reportWebVitals(console.log))
-// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();

+ 15 - 0
dashboard-fluent/src/layouts/anonymous/index.tsx

@@ -0,0 +1,15 @@
+import { Outlet } from "react-router-dom";
+
+const Widget = () => {
+  return (
+    <div>
+      <div>anonymous header</div>
+      <div>
+        <Outlet />
+      </div>
+      <div>anonymous footer</div>
+    </div>
+  );
+};
+
+export default Widget;

+ 15 - 0
dashboard-fluent/src/layouts/dashboard/index.tsx

@@ -0,0 +1,15 @@
+import { Outlet } from "react-router-dom";
+
+const Widget = () => {
+  return (
+    <div>
+      <div>dashboard header</div>
+      <div>
+        <Outlet />
+      </div>
+      <div>dashboard footer</div>
+    </div>
+  );
+};
+
+export default Widget;

+ 31 - 0
dashboard-fluent/src/locales.ts

@@ -0,0 +1,31 @@
+import Cookies from "js-cookie";
+
+import "moment/locale/zh-cn";
+import "moment/locale/zh-tw";
+import "moment/locale/es";
+import "moment/locale/fr";
+import "moment/locale/ja";
+import "moment/locale/ko";
+
+const KEY = "locale";
+
+export const get = (): string => {
+  return localStorage.getItem(KEY) || Cookies.get(KEY) || "en-US";
+};
+
+export const set = (lang: string, reload: boolean) => {
+  Cookies.set(KEY, lang);
+  localStorage.setItem(KEY, lang);
+  if (reload) {
+    window.location.reload();
+  }
+};
+
+export const remove = () => {
+  Cookies.remove(KEY);
+  localStorage.removeItem(KEY);
+};
+
+interface ILocale {
+  messages: Record<string, string>;
+}

+ 0 - 3
dashboard-fluent/src/locales/en-US/index.ts

@@ -1,3 +0,0 @@
-const items = {};
-
-export default items;

+ 0 - 58
dashboard-fluent/src/locales/index.ts

@@ -1,58 +0,0 @@
-import Cookies from "js-cookie";
-
-import "moment/locale/zh-cn";
-import "moment/locale/zh-tw";
-import "moment/locale/es";
-import "moment/locale/fr";
-import "moment/locale/ja";
-import "moment/locale/ko";
-
-import languages from "./languages";
-import enUS from "./en-US";
-import zhHans from "./zh-Hans";
-import zhHant from "./zh-Hant";
-
-const KEY = "locale";
-
-export const DEFAULT: string = process.env.REACT_APP_DEFAULT_LOCALE || "zh-Hans";
-export const LANGUAGES: string[] = process.env.REACT_APP_LANGUAGES?.split(",") || ["en-US", "zh-Hans"];
-
-export const get = (): string => {
-	return localStorage.getItem(KEY) || Cookies.get(KEY) || DEFAULT;
-};
-
-export const set = (lang: string, reload: boolean) => {
-	Cookies.set(KEY, lang);
-	localStorage.setItem(KEY, lang);
-	if (reload) {
-		window.location.reload();
-	}
-};
-
-export const remove = () => {
-	Cookies.remove(KEY);
-	localStorage.removeItem(KEY);
-};
-
-interface ILocale {
-	messages: Record<string, string>;
-}
-
-const messages = (lang: string): ILocale => {
-	switch (lang) {
-		case "en-US":
-			return {
-				messages: { ...enUS, ...languages },
-			};
-		case "zh-Hant":
-			return {
-				messages: { ...zhHant, ...languages },
-			};
-		default:
-			return {
-				messages: { ...zhHans, ...languages },
-			};
-	}
-};
-
-export default messages;

+ 0 - 11
dashboard-fluent/src/locales/languages.ts

@@ -1,11 +0,0 @@
-const items = {
-  "languages.en-US": "English",
-  "languages.zh-Hans": "简体中文",
-  "languages.zh-Hant": "繁體中文",
-  "languages.fr": "Français",
-  "languages.es": "Español",
-  "languages.ja": "日本語",
-  "languages.ko": "한국어",
-};
-
-export default items;

+ 0 - 52
dashboard-fluent/src/locales/zh-Hans/index.ts

@@ -1,52 +0,0 @@
-import forms from "./forms";
-import buttons from "./buttons";
-import tables from "./tables";
-import nut from "./nut";
-import channel from "./channel";
-import dict from "./dict";
-import term from "./term";
-import group from "./group";
-import article from "./article";
-import utilities from "./utilities";
-
-const items = {
-	"flashes.success": "操作成功",
-	"columns.library.title": "藏经阁",
-	"columns.library.community.title": "社区",
-	"columns.library.palicanon.title": "圣典",
-	"columns.library.course.title": "课程",
-	"columns.library.term.title": "术语百科",
-	"columns.library.dict.title": "字典",
-	"columns.library.anthology.title": "文集",
-	"columns.library.help.title": "帮助",
-	"columns.library.more.title": "更多",
-	"columns.library.calendar.title": "佛历",
-	"columns.library.convertor.title": "巴利字符转换器",
-	"columns.library.palihandbook.title": "语法手册",
-	"columns.library.statistics.title": "单词统计",
-	"columns.studio.title": "译经楼",
-	"columns.studio.palicanon.title": "圣典",
-	"columns.studio.recent.title": "最近编辑",
-	"columns.studio.channel.title": "版本风格",
-	"columns.studio.group.title": "群组",
-	"columns.studio.userdict.title": "用户字典",
-	"columns.studio.term.title": "术语",
-	"columns.studio.article.title": "文章",
-	"columns.studio.anthology.title": "文集",
-	"columns.studio.analysis.title": "分析",
-	"columns.studio.collaboration.title": "协作",
-	"columns.studio.basic.title": "常用",
-	"columns.studio.advance.title": "高级",
-	...buttons,
-	...forms,
-	...tables,
-	...nut,
-	...channel,
-	...dict,
-	...term,
-	...group,
-	...article,
-	...utilities,
-};
-
-export default items;

+ 0 - 3
dashboard-fluent/src/locales/zh-Hant/index.ts

@@ -1,3 +0,0 @@
-const items = {};
-
-export default items;

+ 7 - 0
dashboard-fluent/src/main.tsx

@@ -0,0 +1,7 @@
+import ReactDOM from "react-dom/client";
+
+import App from "./App";
+
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+  <App />
+);

+ 0 - 23
dashboard-fluent/src/pages/Home.tsx

@@ -1,23 +0,0 @@
-import { PrimaryButton } from "@fluentui/react";
-import HeadBar from "../components/library/HeadBar";
-
-const Widget = () => {
-	return (
-		<div>
-			<div>
-				<HeadBar />
-			</div>
-			<div>
-				<PrimaryButton
-					onClick={() => {
-						alert("aaa");
-					}}
-				>
-					Demo
-				</PrimaryButton>
-			</div>
-		</div>
-	);
-};
-
-export default Widget;

+ 0 - 9
dashboard-fluent/src/pages/NotFound.tsx

@@ -1,9 +0,0 @@
-const Widget = () => {
-  return (
-    <div>
-      <h1>not found</h1>
-    </div>
-  );
-};
-
-export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/forbidden.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>forbidden</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/home.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>home</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/loading.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>loading</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/not-found.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>not found</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/confirm/new.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>confirm new</div>;
+};
+
+export default Widget;

+ 8 - 0
dashboard-fluent/src/pages/users/confirm/verify.tsx

@@ -0,0 +1,8 @@
+import { useParams } from "react-router-dom";
+
+const Widget = () => {
+  const { token } = useParams();
+  return <div>confirm verify {token}</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/forgot-password.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>forgot password</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/logs.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>logs</div>;
+};
+
+export default Widget;

+ 8 - 0
dashboard-fluent/src/pages/users/reset-password.tsx

@@ -0,0 +1,8 @@
+import { useParams } from "react-router-dom";
+
+const Widget = () => {
+  const { token } = useParams();
+  return <div>reset password {token}</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/sign-in.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>sign in</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/sign-up.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>sign up</div>;
+};
+
+export default Widget;

+ 5 - 0
dashboard-fluent/src/pages/users/unlock/new.tsx

@@ -0,0 +1,5 @@
+const Widget = () => {
+  return <div>unlock new</div>;
+};
+
+export default Widget;

+ 8 - 0
dashboard-fluent/src/pages/users/unlock/verify.tsx

@@ -0,0 +1,8 @@
+import { useParams } from "react-router-dom";
+
+const Widget = () => {
+  const { token } = useParams();
+  return <div>unlock verify {token}</div>;
+};
+
+export default Widget;

+ 0 - 1
dashboard-fluent/src/react-app-env.d.ts

@@ -1 +0,0 @@
-/// <reference types="react-scripts" />

+ 131 - 0
dashboard-fluent/src/reducers/current-user.ts

@@ -0,0 +1,131 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+
+export const ROLE_ROOT = "root";
+export const ROLE_ADMINISTRATOR = "administrator";
+
+export const TO_SIGN_IN = "/anonymous/users/sign-in";
+export const TO_PROFILE = "/dashboard/users/logs";
+
+const KEY = "token";
+export const DURATION = 60 * 60 * 24;
+
+const ENABLE_LOCAL_TOKEN =
+  import.meta.env.VITE_APP_ENABLE_LOCAL_TOKEN === "true";
+
+export const get = (): string | null => {
+  const token = sessionStorage.getItem(KEY);
+  if (token) {
+    return token;
+  }
+  if (ENABLE_LOCAL_TOKEN) {
+    return localStorage.getItem(KEY);
+  }
+  return null;
+};
+
+const set = (token: string) => {
+  sessionStorage.setItem(KEY, token);
+  if (ENABLE_LOCAL_TOKEN) {
+    localStorage.setItem(KEY, token);
+  }
+};
+
+const remove = () => {
+  sessionStorage.removeItem(KEY);
+  localStorage.removeItem(KEY);
+};
+
+export const OPERATION_READ = "read";
+export const OPERATION_WRITE = "write";
+export const OPERATION_CREATE = "create";
+export const OPERATION_UPDATE = "update";
+export const OPERATION_REMOVE = "remove";
+
+export interface IRoleOption {
+  code: string;
+  name: string;
+}
+
+export const user_option_to_string = (it: IUserOption): string =>
+  `${it.nickName}(${it.realName})`;
+
+export const permission2string = (it: IPermission): string =>
+  `${it.resourceType}://${it.resourceId ? it.resourceId : "*"}/${it.operation}`;
+
+export interface IUserOption {
+  id: number;
+  nickName: string;
+  realName: string;
+}
+
+export interface IUserDetails {
+  id: number;
+  email: string;
+  nickName: string;
+  realName: string;
+  lang: string;
+  uid: string;
+  timeZone: string;
+  avatar: string;
+  signInCount: number;
+  currentSignInAt?: Date;
+  currentSignInIp?: string;
+  lastSignInAt?: Date;
+  lastSignInIp?: string;
+  lockedAt?: Date;
+  confirmedAt?: Date;
+  deletedAt?: Date;
+  updatedAt: Date;
+}
+
+export interface IPermission {
+  operation: string;
+  resourceType: string;
+  resourceId?: number;
+}
+
+export interface IUser {
+  id: number;
+  uid: string;
+  nickName: string;
+  realName: string;
+  avatar: string;
+  permissions: IPermission[];
+  roles: string[];
+}
+
+interface IState {
+  payload?: IUser;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "current-user",
+  initialState,
+  reducers: {
+    signIn: (state, action: PayloadAction<[IUser, string]>) => {
+      state.payload = action.payload[0];
+      set(action.payload[1]);
+    },
+    signOut: (state) => {
+      state.payload = undefined;
+      remove();
+    },
+  },
+});
+
+export const { signIn, signOut } = slice.actions;
+
+export const permissions = (state: RootState): IPermission[] =>
+  state.currentUser.payload?.permissions || [];
+export const isRoot = (state: RootState): boolean =>
+  state.currentUser.payload?.roles.includes(ROLE_ROOT) || false;
+export const isAdministrator = (state: RootState): boolean =>
+  state.currentUser.payload?.roles.includes(ROLE_ADMINISTRATOR) || false;
+export const currentUser = (state: RootState): IUser | undefined =>
+  state.currentUser.payload;
+
+export default slice.reducer;

+ 50 - 0
dashboard-fluent/src/reducers/layout.ts

@@ -0,0 +1,50 @@
+import { createSlice, PayloadAction } from "@reduxjs/toolkit";
+
+import type { RootState } from "../store";
+
+export interface IAuthor {
+  name: string;
+  email: string;
+}
+
+export interface ISite {
+  logo: string;
+  title: string;
+  subhead: string;
+  keywords: string[];
+  description: string;
+  copyright: string;
+  author: IAuthor;
+}
+
+interface IState {
+  site?: ISite;
+  title?: string;
+}
+
+const initialState: IState = {};
+
+export const slice = createSlice({
+  name: "layout",
+  initialState,
+  reducers: {
+    refresh: (state, action: PayloadAction<ISite>) => {
+      state.site = action.payload;
+    },
+    setTitle: (state, action: PayloadAction<string>) => {
+      state.title = action.payload;
+    },
+  },
+});
+
+export const { refresh, setTitle } = slice.actions;
+
+export const layout = (state: RootState): IState => state.layout;
+
+export const siteInfo = (state: RootState): ISite | undefined =>
+  state.layout.site;
+
+export const pageTitle = (state: RootState): string | undefined =>
+  state.layout.title;
+
+export default slice.reducer;

+ 0 - 15
dashboard-fluent/src/reportWebVitals.ts

@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals';
-
-const reportWebVitals = (onPerfEntry?: ReportHandler) => {
-  if (onPerfEntry && onPerfEntry instanceof Function) {
-    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
-      getCLS(onPerfEntry);
-      getFID(onPerfEntry);
-      getFCP(onPerfEntry);
-      getLCP(onPerfEntry);
-      getTTFB(onPerfEntry);
-    });
-  }
-};
-
-export default reportWebVitals;

+ 99 - 0
dashboard-fluent/src/request.ts

@@ -0,0 +1,99 @@
+import { Metadata } from "grpc-web";
+import { get as getLocale } from "./locales";
+
+import { get as getToken } from "./reducers/current-user";
+
+export const backend = (u: string) => `/api${u}`;
+
+export const GRPC_HOST: string =
+  import.meta.env.VITE_APP_GRPC_HOST || "http://localhost:9999";
+
+export const grpc_metadata = (): Metadata => {
+  return {
+    authorization: `Bearer ${getToken()}`,
+    "accept-language": getLocale() || "en-US",
+  };
+};
+
+export const upload = () => {
+  return {
+    Authorization: `Bearer ${getToken()}`,
+  };
+};
+
+export const options = (method: string): RequestInit => {
+  return {
+    credentials: "include",
+    headers: {
+      Authorization: `Bearer ${getToken()}`,
+      "Content-Type": "application/json; charset=utf-8",
+    },
+    mode: "cors",
+    method,
+  };
+};
+
+export const get = async <R>(path: string): Promise<R> => {
+  const response = await fetch(backend(path), options("GET"));
+  const res: R = await response.json();
+  return res;
+};
+
+export const delete_ = async <R>(path: string): Promise<R> => {
+  const response = await fetch(backend(path), options("DELETE"));
+  const res: R = await response.json();
+  return res;
+};
+
+// https://github.github.io/fetch/#options
+export const post = async <Q, R>(path: string, body: Q): Promise<R> => {
+  const data = options("POST");
+  data.body = JSON.stringify(body);
+  const response = await fetch(backend(path), data);
+  const res: R = await response.json();
+  return res;
+};
+
+export const patch = <Request, Response>(
+  path: string,
+  body: Request
+): Promise<Response> => {
+  const data = options("PATCH");
+  data.body = JSON.stringify(body);
+  return fetch(backend(path), data).then((res) => {
+    if (res.status === 200) {
+      return res.json();
+    }
+    throw res.text();
+  });
+};
+
+export const put = <Request, Response>(
+  path: string,
+  body: Request
+): Promise<Response> => {
+  const data = options("PUT");
+  data.body = JSON.stringify(body);
+  return fetch(backend(path), data).then((res) =>
+    res.status === 200
+      ? res.json()
+      : res.json().then((err) => {
+          throw err;
+        })
+  );
+};
+
+export const download = (path: string, name: string) => {
+  const data = options("GET");
+  fetch(backend(path), data)
+    .then((response) => response.blob())
+    .then((blob) => {
+      var url = window.URL.createObjectURL(blob);
+      var a = document.createElement("a");
+      a.href = url;
+      a.download = name;
+      document.body.appendChild(a); // for firefox
+      a.click();
+      a.remove();
+    });
+};

+ 0 - 5
dashboard-fluent/src/setupTests.ts

@@ -1,5 +0,0 @@
-// jest-dom adds custom jest matchers for asserting on DOM nodes.
-// allows you to do things like:
-// expect(element).toHaveTextContent(/react/i)
-// learn more: https://github.com/testing-library/jest-dom
-import '@testing-library/jest-dom';

+ 16 - 0
dashboard-fluent/src/store.ts

@@ -0,0 +1,16 @@
+import { configureStore } from "@reduxjs/toolkit";
+
+import currentUserReducer from "./reducers/current-user";
+import layoutReducer from "./reducers/layout";
+
+const store = configureStore({
+  reducer: {
+    layout: layoutReducer,
+    currentUser: currentUserReducer,
+  },
+});
+
+export type RootState = ReturnType<typeof store.getState>;
+export type AppDispatch = typeof store.dispatch;
+
+export default store;

+ 1 - 0
dashboard-fluent/src/vite-env.d.ts

@@ -0,0 +1 @@
+/// <reference types="vite/client" />

+ 9 - 14
dashboard-fluent/tsconfig.json

@@ -1,26 +1,21 @@
 {
   "compilerOptions": {
-    "target": "es5",
-    "lib": [
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "allowJs": true,
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "lib": ["DOM", "DOM.Iterable", "ESNext"],
+    "allowJs": false,
     "skipLibCheck": true,
-    "esModuleInterop": true,
+    "esModuleInterop": false,
     "allowSyntheticDefaultImports": true,
     "strict": true,
     "forceConsistentCasingInFileNames": true,
-    "noFallthroughCasesInSwitch": true,
-    "module": "esnext",
-    "moduleResolution": "node",
+    "module": "ESNext",
+    "moduleResolution": "Node",
     "resolveJsonModule": true,
     "isolatedModules": true,
     "noEmit": true,
     "jsx": "react-jsx"
   },
-  "include": [
-    "src"
-  ]
+  "include": ["src"],
+  "references": [{ "path": "./tsconfig.node.json" }]
 }

+ 9 - 0
dashboard-fluent/tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 17 - 0
dashboard-fluent/vite.config.ts

@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [react()],
+  base: "/my/",
+  server: {
+    port: 3000,
+    proxy: {
+      "/api": {
+        target: "http://localhost:8080/api",
+        changeOrigin: true,
+      },
+    },
+  },
+});

+ 1 - 0
docker/alpine/Dockerfile

@@ -21,6 +21,7 @@ RUN git config --global core.quotepath false
 RUN git config --global http.version HTTP/1.1
 RUN git config --global pull.rebase false
 RUN echo 'set-option -g history-limit 102400' > $HOME/.tmux.conf
+RUN echo 'set-option -g default-shell "/bin/zsh"' >> $HOME/.tmux.conf
 
 RUN sh -c ". $HOME/.profile \
     && pip install --user ansible paramiko"

+ 63 - 43
docker/jammy/Dockerfile

@@ -22,10 +22,10 @@ RUN apt -y upgrade
 RUN apt -y install zsh git locales locales-all rsync openssh-client sshpass \
     vim tzdata pwgen zip unzip tree tmux dialog \
     net-tools dnsutils net-tools iputils-arping iputils-ping telnet \
-    imagemagick ffmpeg fonts-dejavu-extra \
-    clang clang-format lldb lld \
+    imagemagick ffmpeg fonts-dejavu-extra texlive-full \
     build-essential cmake pkg-config libtool automake autoconf binutils cpio mold \
     debhelper bison flex ninja-build \
+    musl-tools musl-dev \
     crossbuild-essential-armhf crossbuild-essential-arm64 \
     python3 python3-distutils python3-dev python3-pip virtualenv \
     php-fpm php-mbstring php-json php-xml php-pear php-bcmath php-curl php-zip \
@@ -38,9 +38,10 @@ RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
 RUN apt update
 ENV GCC_VERSION 12
 RUN apt install -y g++-${GCC_VERSION} g++-${GCC_VERSION}-aarch64-linux-gnu g++-${GCC_VERSION}-arm-linux-gnueabihf
+RUN apt install -y libstdc++-${GCC_VERSION}-dev:amd64 libstdc++-${GCC_VERSION}-dev:arm64 libstdc++-${GCC_VERSION}-dev:armhf
 
 # https://apt.llvm.org/
-ENV CLANG_VERSION=14
+ENV CLANG_VERSION=15
 RUN echo "deb [arch=amd64] http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-${CLANG_VERSION} main" > /etc/apt/sources.list.d/llvm.list
 RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
 RUN apt update
@@ -79,29 +80,28 @@ RUN mkdir -p $HOME/downloads $HOME/build $HOME/local $HOME/tmp
 # https://github.com/ohmyzsh/ohmyzsh
 RUN git clone https://github.com/ohmyzsh/ohmyzsh.git $HOME/.oh-my-zsh
 RUN cp $HOME/.oh-my-zsh/templates/zshrc.zsh-template $HOME/.zshrc
-RUN echo 'source $HOME/.profile' >> $HOME/.zshrc
-RUN echo 'export LANG=en_US.UTF-8' >> $HOME/.profile
-RUN echo 'export LC_ALL=en_US.UTF-8' >> $HOME/.profile
+RUN echo 'export LANG=en_US.UTF-8' >> $HOME/.zshrc
+RUN echo 'export LC_ALL=en_US.UTF-8' >> $HOME/.zshrc
+RUN echo 'export PATH=$HOME/.local/bin:$PATH' >> $HOME/.zshrc
 
 RUN git config --global core.quotepath false
 RUN git config --global http.version HTTP/1.1
 RUN git config --global pull.rebase false
 RUN echo 'set-option -g history-limit 102400' > $HOME/.tmux.conf
+RUN echo 'set-option -g default-shell "/bin/zsh"' >> $HOME/.tmux.conf
 
-RUN sh -c ". $HOME/.profile && pip3 install --user cmake"
-
-RUN sh -c ". $HOME/.profile \
+RUN zsh -c "source $HOME/.zshrc && pip3 install --user cmake"
+RUN zsh -c "source $HOME/.zshrc \
     && pip3 install --user ansible paramiko"
-RUN echo 'export ANSIBLE_HOST_KEY_CHECKING=False' >> $HOME/.profile
-RUN echo 'export PATH=$HOME/.local/bin:$PATH' >> $HOME/.profile
-RUN echo 'alias peony="ANSIBLE_LOG_PATH=$HOME/tmp/$(date +%Y%m%d%H%M%S).log ansible-playbook"' >> $HOME/.profile
+RUN echo 'export ANSIBLE_HOST_KEY_CHECKING=False' >> $HOME/.zshrc
+RUN echo 'alias peony="ANSIBLE_LOG_PATH=$HOME/tmp/$(date +%Y%m%d%H%M%S).log ansible-playbook"' >> $HOME/.zshrc
 
-RUN sh -c ". $HOME/.profile \
+RUN zsh -c "source $HOME/.zshrc \
     && pip3 install --user conan \
     && conan profile new default --detect \
     && conan profile update settings.compiler.libcxx=libstdc++11 default"
 
-RUN sh -c ". $HOME/.profile && pip3 install --user supervisor"
+RUN zsh -c "source $HOME/.zshrc && pip3 install --user supervisor"
 
 # https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos
 RUN wget -q -O $HOME/downloads/composer https://getcomposer.org/installer
@@ -153,7 +153,7 @@ RUN echo 'export GOROOT=$HOME/local/go' >> $HOME/.zshrc
 RUN echo 'export PATH=$GOROOT/bin:$PATH' >> $HOME/.zshrc
 RUN echo 'export GOPATH=$HOME/go' >> $HOME/.zshrc
 
-ENV JDK_VERSION "19-open"
+ENV JDK_VERSION "19.0.1-open"
 RUN curl -s "https://get.sdkman.io" | zsh
 RUN sed -i -e 's/sdkman_auto_answer=false/sdkman_auto_answer=true/g' $HOME/.sdkman/etc/config
 RUN zsh -c "source $HOME/.zshrc \
@@ -162,21 +162,25 @@ RUN zsh -c "source $HOME/.zshrc \
     && sdk install gradle"
 
 # https://github.com/nvm-sh/nvm
-RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | sh
-RUN sh -c ". $HOME/.profile \
+ENV NVM_VERSION "v0.39.2"
+RUN git clone -b ${NVM_VERSION} https://github.com/nvm-sh/nvm.git $HOME/.nvm
+RUN echo 'export NVM_DIR="$HOME/.nvm"' >> $HOME/.zshrc
+RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $HOME/.zshrc
+RUN echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> $HOME/.zshrc
+RUN zsh -c "source $HOME/.zshrc \
     && nvm install node \
     && nvm use node \
     && npm i yarn -g"
-RUN sh -c ". $HOME/.profile \
+RUN zsh -c "source $HOME/.zshrc \
     && nvm install --lts \
     && nvm use --lts \
     && npm i yarn -g"
 # https://stackoverflow.com/questions/37324519/node-sass-does-not-yet-support-your-current-environment-linux-64-bit-with-false
-RUN sh -c ". $HOME/.profile \
+RUN zsh -c "source $HOME/.zshrc \
     && nvm install lts/fermium \
     && nvm use lts/fermium \
     && npm i yarn -g"
-RUN echo 'export PATH=$HOME/.yarn/bin:$PATH' >> $HOME/.profile
+RUN echo 'export PATH=$HOME/.yarn/bin:$PATH' >> $HOME/.zshrc
 
 # https://www.rust-lang.org/tools/install
 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
@@ -184,7 +188,9 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
 RUN zsh -c "source $HOME/.cargo/env \
     && rustup component add rust-analyzer \
     && rustup target add armv7-unknown-linux-gnueabihf \
-    && rustup target add aarch64-unknown-linux-gnu"
+    && rustup target add aarch64-unknown-linux-gnu \
+    && rustup target add x86_64-unknown-linux-musl \
+    && rustup target add aarch64-unknown-linux-musl"
 
 
 RUN apt install -y libpq-dev libmysqlclient-dev libsqlite3-dev
@@ -193,12 +199,18 @@ RUN zsh -c "source $HOME/.zshrc \
     && cargo install --locked cargo-outdated \
     && cargo install mdbook"
 
-ADD conan /opt/conan
-RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh amd64"
-RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh arm64"
-RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh armhf"
+# https://opensearch.org/downloads.html#opensearch
+ENV OPENSEARCH_VERSION "2.3.0"
+RUN wget -q -P $HOME/downloads \
+    https://artifacts.opensearch.org/releases/bundle/opensearch/${OPENSEARCH_VERSION}/opensearch-${OPENSEARCH_VERSION}-linux-x64.tar.gz
+RUN tar xf $HOME/downloads/opensearch-${OPENSEARCH_VERSION}-linux-x64.tar.gz -C /opt
+
+# https://min.io/download#/linux
+RUN wget -q -O /usr/bin/minio \
+    https://dl.min.io/server/minio/release/linux-amd64/minio
+RUN chmod +x /usr/bin/minio
 
-ENV GRPC_VERSION "v1.49.1"
+ENV GRPC_VERSION "v1.50.1"
 RUN git clone --recurse-submodules -b $GRPC_VERSION https://github.com/grpc/grpc.git $HOME/downloads/grpc
 RUN zsh -c "source $HOME/.zshrc \
     && mkdir -pv $HOME/build/grpc \
@@ -211,37 +223,45 @@ RUN zsh -c "source $HOME/.zshrc \
     && make -j \
     && make install"
 
+# https://github.com/grpc/grpc-web#code-generator-plugin
+ENV GRPC_WEB_PLUGIN_VERSION "1.4.2"
+RUN wget -q -O $HOME/.local/bin/protoc-gen-grpc-web \
+    https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_PLUGIN_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64
+RUN chmod +x $HOME/.local/bin/protoc-gen-grpc-web
+
 # https://github.com/protocolbuffers/protobuf-javascript
 ENV GRPC_JS_PLUGIN_VERSION "3.21.2"
-RUN wget -P $HOME/downloads \
+RUN wget -q -P $HOME/downloads \
     https://github.com/protocolbuffers/protobuf-javascript/releases/download/v${GRPC_JS_PLUGIN_VERSION}/protobuf-javascript-${GRPC_JS_PLUGIN_VERSION}-linux-x86_64.tar.gz
 RUN mkdir -p $HOME/build/protobuf-javascript \
-    cd $HOME/build/protobuf-javascript \
+    && cd $HOME/build/protobuf-javascript \
     && tar xf $HOME/downloads/protobuf-javascript-${GRPC_JS_PLUGIN_VERSION}-linux-x86_64.tar.gz \
     && cp bin/protoc-gen-js $HOME/.local/bin/
 
 # https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/
-ENV GRPC_JAVA_PLUGIN_VERSION "1.50.0"
+ENV GRPC_JAVA_PLUGIN_VERSION "1.50.2"
 RUN wget -q -O $HOME/.local/bin/grpc_java_plugin \
     https://repo1.maven.org/maven2/io/grpc/protoc-gen-grpc-java/${GRPC_JAVA_PLUGIN_VERSION}/protoc-gen-grpc-java-${GRPC_JAVA_PLUGIN_VERSION}-linux-x86_64.exe
 RUN chmod +x $HOME/.local/bin/grpc_java_plugin
 
-# https://github.com/grpc/grpc-web#code-generator-plugin
-ENV GRPC_WEB_PLUGIN_VERSION "1.4.1"
-RUN wget -q -O $HOME/.local/bin/protoc-gen-grpc-web \
-    https://github.com/grpc/grpc-web/releases/download/${GRPC_WEB_PLUGIN_VERSION}/protoc-gen-grpc-web-${GRPC_WEB_PLUGIN_VERSION}-linux-x86_64
-RUN chmod +x $HOME/.local/bin/protoc-gen-grpc-web
+ENV FLATBUFFERS_VERSION "v22.10.26"
+RUN git clone -b $FLATBUFFERS_VERSION https://github.com/google/flatbuffers.git $HOME/downloads/flatbuffers
+RUN zsh -c "source $HOME/.zshrc \
+    && mkdir -pv $HOME/build/flatbuffers \
+    && cd $HOME/build/flatbuffers \
+    && cmake -DCMAKE_BUILD_TYPE=Release \
+        -DCMAKE_INSTALL_PREFIX=$HOME/.local $HOME/downloads/flatbuffers \
+    && make -j \
+    && make install"
 
-# https://opensearch.org/downloads.html#opensearch
-ENV OPENSEARCH_VERSION "2.3.0"
-RUN wget -q -P $HOME/downloads \
-    https://artifacts.opensearch.org/releases/bundle/opensearch/${OPENSEARCH_VERSION}/opensearch-${OPENSEARCH_VERSION}-linux-x64.tar.gz
-RUN tar xf $HOME/downloads/opensearch-${OPENSEARCH_VERSION}-linux-x64.tar.gz -C /opt
+RUN git clone https://github.com/microsoft/vcpkg.git $HOME/local/vcpkg
+RUN $HOME/local/vcpkg/bootstrap-vcpkg.sh
+RUN echo 'export VCPKG_DISABLE_METRICS=1' >> $HOME/.zshrc
 
-# https://min.io/download#/linux
-RUN wget -q -O /usr/bin/minio \
-    https://dl.min.io/server/minio/release/linux-amd64/minio
-RUN chmod +x /usr/bin/minio
+ADD conan /opt/conan
+# RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh amd64"
+# RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh arm64"
+# RUN zsh -c "source $HOME/.zshrc && cd /opt/conan && ./install.sh armhf"
 
 # https://opensearch.org/docs/latest/opensearch/install/tar/
 RUN echo "network.host: 0.0.0.0" >> /opt/opensearch-${OPENSEARCH_VERSION}/config/opensearch.yml

+ 6 - 5
docker/jammy/conan/conanfile.txt

@@ -1,5 +1,6 @@
 [requires]
 libxcrypt/4.4.28
+zlib/1.2.13
 libcurl/7.85.0
 openssl/1.1.1q
 boost/1.80.0
@@ -10,17 +11,17 @@ inja/3.3.0
 tomlplusplus/3.2.0
 yaml-cpp/0.7.0
 cppcodec/0.2
-libpq/14.2
+libpq/14.5
 libpqxx/7.7.4
 # libmysqlclient/8.0.17
 mariadb-connector-c/3.1.12
-sqlite3/3.39.3
+sqlite3/3.39.4
 sqlitecpp/3.2.0
 soci/4.0.3
 mongo-cxx-driver/3.6.7
 redis-plus-plus/1.3.3
 rabbitmq-c/0.11.0
-amqp-cpp/4.3.16
+amqp-cpp/4.3.18
 zmqpp/4.2.0
 paho-mqtt-cpp/1.2.0
 libgit2/1.5.0
@@ -29,9 +30,9 @@ libssh2/1.10.0
 serial/1.2.1
 # net-snmp/5.9.1
 # imgui/1.88
-flatbuffers/2.0.6
+flatbuffers/2.0.8
 protobuf/3.21.4
-grpc/1.48.0 
+grpc/1.50.0 
 
 [options]
 zeromq:encryption=None

+ 1 - 1
docker/jammy/conan/profiles/amd64

@@ -1,5 +1,5 @@
 target=x86_64-linux-gnu
-clang_version=14
+clang_version=15
 
 [settings]
 os=Linux

+ 1 - 1
docker/jammy/conan/profiles/arm64

@@ -1,6 +1,6 @@
 target=aarch64-linux-gnu
 gcc_version=12
-clang_version=14
+clang_version=15
 
 [settings]
 os=Linux

+ 1 - 1
docker/jammy/conan/profiles/armhf

@@ -1,6 +1,6 @@
 target=arm-linux-gnueabihf
 gcc_version=12
-clang_version=14
+clang_version=15
 
 [settings]
 os=Linux

+ 1 - 1
docker/jammy/conan/toolchains/amd64.cmake

@@ -2,7 +2,7 @@ set(CMAKE_SYSTEM_NAME Linux)
 set(CMAKE_SYSTEM_PROCESSOR x86_64)
 
 set(target x86_64-linux-gnu)
-set(clang_version 14)
+set(clang_version 15)
 
 set(CMAKE_C_COMPILER clang-${clang_version})
 set(CMAKE_C_COMPILER_TARGET ${target})

+ 1 - 1
docker/jammy/conan/toolchains/arm64.cmake

@@ -3,7 +3,7 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
 
 set(target aarch64-linux-gnu)
 set(gcc_version 12)
-set(clang_version 14)
+set(clang_version 15)
 
 set(CMAKE_C_COMPILER clang-${clang_version})
 set(CMAKE_C_COMPILER_TARGET ${target})

+ 1 - 1
docker/jammy/conan/toolchains/armhf.cmake

@@ -3,7 +3,7 @@ set(CMAKE_SYSTEM_PROCESSOR armv7l)
 
 set(target arm-linux-gnueabihf)
 set(gcc_version 12)
-set(clang_version 14)
+set(clang_version 15)
 
 set(CMAKE_C_COMPILER clang-${clang_version})
 set(CMAKE_C_COMPILER_TARGET ${target})

+ 5 - 5
docker/jammy/etc/envoy.yaml

@@ -1,5 +1,5 @@
 admin:
-  access_log_path: /tmp/admin_access.log
+  access_log_path: /tmp/admin.access.log
   address:
     socket_address: { address: 127.0.0.1, port_value: 9901 }
 
@@ -23,7 +23,7 @@ static_resources:
                       routes:
                         - match: { prefix: "/" }
                           route:
-                            cluster: echo_service
+                            cluster: palm_service
                             timeout: 0s
                             max_stream_duration:
                               grpc_timeout_header_max: 0s
@@ -45,7 +45,7 @@ static_resources:
                     typed_config:
                       "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
   clusters:
-    - name: echo_service
+    - name: palm_service
       connect_timeout: 0.25s
       type: logical_dns
       http2_protocol_options: {}
@@ -57,5 +57,5 @@ static_resources:
               - endpoint:
                   address:
                     socket_address:
-                      address: node-server
-                      port_value: 11111
+                      address: 127.0.0.1
+                      port_value: 9999

+ 5 - 0
docker/jammy/etc/nginx.conf

@@ -1,8 +1,13 @@
+log_format custom '$remote_addr - [$time_iso8601] "$request" $status $body_bytes_sent $request_time "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
+
 server {
     listen 8080;
     server_name _;
     index index.html index.php;
     root /var/www/html;
+    
+    access_log /var/log/nginx/localhost.access.log custom;
+    error_log /var/log/nginx/localhost.error.log warn;
 
     location / {
         try_files $uri $uri/ =404;

+ 1 - 1
docker/jammy/start.sh

@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 export CODE="palm-jammy"
 export NAME="$CODE-$USER"

+ 1 - 1
scripts/spring/user/setup.sh

@@ -27,7 +27,7 @@ EOF
 fi
 
 cd $HOME/.nvm
-git checkout v0.39.1
+git checkout v0.39.2
 . $HOME/.nvm/nvm.sh
 
 if ! [ -x "$(command -v yarn)" ]