浏览代码

chore: migrate to ESlint v9

Hintay 6 月之前
父节点
当前提交
4f25a0c670
共有 100 个文件被更改,包括 5067 次插入3486 次删除
  1. 10 0
      .editorconfig
  2. 0 257
      app/.eslintrc.cjs
  3. 2 1
      app/env.d.ts
  4. 52 0
      app/eslint.config.mjs
  5. 0 1
      app/gettext.config.cjs
  6. 10 15
      app/package.json
  7. 4314 2545
      app/pnpm-lock.yaml
  8. 8 9
      app/src/App.vue
  9. 1 1
      app/src/api/2fa.ts
  10. 1 1
      app/src/api/auth.ts
  11. 2 2
      app/src/api/cert.ts
  12. 1 1
      app/src/api/config.ts
  13. 6 6
      app/src/api/curd.ts
  14. 1 1
      app/src/api/dns_credential.ts
  15. 4 4
      app/src/api/domain.ts
  16. 1 1
      app/src/api/environment.ts
  17. 1 1
      app/src/api/nginx_log.ts
  18. 1 1
      app/src/api/ngx.ts
  19. 1 1
      app/src/api/openai.ts
  20. 1 1
      app/src/api/passkey.ts
  21. 3 3
      app/src/api/stream.ts
  22. 2 2
      app/src/api/template.ts
  23. 1 1
      app/src/components/Breadcrumb/types.d.ts
  24. 5 5
      app/src/components/Chart/AreaChart.vue
  25. 4 4
      app/src/components/Chart/RadialBarChart.vue
  26. 1 1
      app/src/components/Chart/types.d.ts
  27. 14 14
      app/src/components/ChatGPT/ChatGPT.vue
  28. 2 2
      app/src/components/CodeEditor/CodeEditor.vue
  29. 1 1
      app/src/components/EnvIndicator/EnvIndicator.vue
  30. 2 1
      app/src/components/Logo/Logo.vue
  31. 3 3
      app/src/components/NginxControl/NginxControl.vue
  32. 1 1
      app/src/components/NodeSelector/NodeSelector.vue
  33. 6 6
      app/src/components/Notification/Notification.vue
  34. 3 6
      app/src/components/Notification/cert.ts
  35. 6 12
      app/src/components/Notification/config.ts
  36. 2 2
      app/src/components/Notification/detailRender.ts
  37. 4 4
      app/src/components/SetLanguage/SetLanguage.vue
  38. 3 3
      app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue
  39. 32 33
      app/src/components/StdDesign/StdDataDisplay/StdCurd.vue
  40. 4 4
      app/src/components/StdDesign/StdDataDisplay/StdCurdDetail.vue
  41. 28 29
      app/src/components/StdDesign/StdDataDisplay/StdTable.vue
  42. 38 36
      app/src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx
  43. 2 2
      app/src/components/StdDesign/StdDataDisplay/components/CustomRender.tsx
  44. 4 4
      app/src/components/StdDesign/StdDataDisplay/index.ts
  45. 19 21
      app/src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts
  46. 113 109
      app/src/components/StdDesign/StdDataDisplay/methods/sortable.ts
  47. 4 2
      app/src/components/StdDesign/StdDataDisplay/types.d.ts
  48. 6 6
      app/src/components/StdDesign/StdDataEntry/StdDataEntry.vue
  49. 1 1
      app/src/components/StdDesign/StdDataEntry/StdFormItem.vue
  50. 4 4
      app/src/components/StdDesign/StdDataEntry/components/StdSelect.vue
  51. 9 9
      app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue
  52. 18 18
      app/src/components/StdDesign/StdDataEntry/index.tsx
  53. 15 12
      app/src/components/StdDesign/types.d.ts
  54. 2 2
      app/src/components/SwitchAppearance/SwitchAppearance.vue
  55. 5 5
      app/src/components/TwoFA/Authorization.vue
  56. 4 4
      app/src/components/TwoFA/use2FAModal.ts
  57. 1 1
      app/src/composables/useBreadcrumbs.ts
  58. 4 4
      app/src/layouts/BaseLayout.vue
  59. 5 5
      app/src/layouts/HeaderLayout.vue
  60. 16 14
      app/src/layouts/SideBar.vue
  61. 9 8
      app/src/lib/helper/index.ts
  62. 15 15
      app/src/lib/http/index.ts
  63. 4 5
      app/src/lib/websocket/index.ts
  64. 6 4
      app/src/main.ts
  65. 2 2
      app/src/pinia/index.ts
  66. 1 1
      app/src/pinia/moudule/global.ts
  67. 2 2
      app/src/pinia/moudule/user.ts
  68. 2 2
      app/src/routes/index.ts
  69. 2 2
      app/src/routes/type.d.ts
  70. 21 14
      app/src/views/certificate/ACMEUser.vue
  71. 4 4
      app/src/views/certificate/ACMEUserSelector.vue
  72. 10 11
      app/src/views/certificate/CertificateEditor.vue
  73. 3 3
      app/src/views/certificate/CertificateList/Certificate.vue
  74. 25 17
      app/src/views/certificate/CertificateList/certColumns.tsx
  75. 4 2
      app/src/views/certificate/DNSChallenge.vue
  76. 5 5
      app/src/views/certificate/DNSCredential.vue
  77. 3 3
      app/src/views/certificate/RenewCert.vue
  78. 5 6
      app/src/views/certificate/WildcardCertificate.vue
  79. 10 10
      app/src/views/config/ConfigEditor.vue
  80. 4 4
      app/src/views/config/ConfigList.vue
  81. 1 2
      app/src/views/config/components/Mkdir.vue
  82. 2 2
      app/src/views/config/components/Rename.vue
  83. 4 4
      app/src/views/config/configColumns.ts
  84. 1 1
      app/src/views/dashboard/DashBoard.vue
  85. 5 5
      app/src/views/dashboard/Environments.vue
  86. 7 9
      app/src/views/dashboard/ServerAnalytic.vue
  87. 2 3
      app/src/views/dashboard/components/NodeAnalyticItem.vue
  88. 5 7
      app/src/views/environment/BatchUpgrader.vue
  89. 3 3
      app/src/views/environment/Environment.vue
  90. 15 21
      app/src/views/environment/envColumns.tsx
  91. 9 6
      app/src/views/nginx_log/NginxLog.vue
  92. 2 2
      app/src/views/notification/Notification.vue
  93. 25 17
      app/src/views/notification/notificationColumns.tsx
  94. 0 1
      app/src/views/other/Error.vue
  95. 7 9
      app/src/views/other/Install.vue
  96. 8 8
      app/src/views/other/Login.vue
  97. 6 6
      app/src/views/preference/AuthSettings.vue
  98. 2 2
      app/src/views/preference/CertSettings.vue
  99. 9 10
      app/src/views/preference/Preference.vue
  100. 3 3
      app/src/views/preference/components/AddPasskey.vue

+ 10 - 0
.editorconfig

@@ -2,5 +2,15 @@
   <profile version="1.0">
     <option name="myName" value="Project Default" />
     <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="myValues">
+        <value>
+          <list size="1">
+            <item index="0" class="java.lang.String" itemvalue="vue-dompurify-html" />
+          </list>
+        </value>
+      </option>
+      <option name="myCustomValuesEnabled" value="true" />
+    </inspection_tool>
   </profile>
 </component>

+ 0 - 257
app/.eslintrc.cjs

@@ -1,257 +0,0 @@
-module.exports = {
-  env: {
-    browser: true,
-    es2021: true,
-  },
-  extends: [
-    '@antfu/eslint-config-vue',
-    'plugin:vue/vue3-recommended',
-    'plugin:import/recommended',
-    'plugin:import/typescript',
-    'plugin:sonarjs/recommended',
-    'plugin:@typescript-eslint/recommended',
-
-    // 'plugin:unicorn/recommended',
-  ],
-  parser: 'vue-eslint-parser',
-  parserOptions: {
-    ecmaVersion: 13,
-    parser: '@typescript-eslint/parser',
-    sourceType: 'module',
-  },
-  plugins: [
-    'vue',
-    '@typescript-eslint',
-    'regex',
-  ],
-  ignorePatterns: ['src/@iconify/*.js', 'node_modules', 'dist', '*.d.ts'],
-  rules: {
-    'vue/no-v-html': 'off',
-
-    'vue/block-tag-newline': 'off',
-    // eslint-disable-next-line n/prefer-global/process
-    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
-    // eslint-disable-next-line n/prefer-global/process
-    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
-
-    // indentation (Already present in TypeScript)
-    'comma-spacing': ['error', {
-      before: false,
-      after: true,
-    }],
-    'key-spacing': ['error', {afterColon: true}],
-
-    'vue/first-attribute-linebreak': ['error', {
-      singleline: 'beside',
-      multiline: 'below',
-    }],
-
-    'antfu/top-level-function': 'off',
-
-    // Enforce trailing comma (Already present in TypeScript)
-    'comma-dangle': ['error', 'always-multiline'],
-
-    // Disable max-len
-    'max-len': 'off',
-
-    // we don't want it
-    'semi': ['error', 'never'],
-
-    // add parens ony when required in arrow function
-    'arrow-parens': ['error', 'as-needed'],
-
-    // add new line above comment
-    'newline-before-return': 'error',
-
-    // add new line above comment
-    'lines-around-comment': [
-      'error',
-      {
-        beforeBlockComment: true,
-        beforeLineComment: true,
-        allowBlockStart: true,
-        allowClassStart: true,
-        allowObjectStart: true,
-        allowArrayStart: true,
-      },
-    ],
-
-    // Ignore _ as unused variable
-    '@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_+$'}],
-
-    'array-element-newline': ['error', 'consistent'],
-    'array-bracket-newline': ['error', 'consistent'],
-
-    'vue/multi-word-component-names': 'off',
-
-    'padding-line-between-statements': [
-      'error',
-      {
-        blankLine: 'always',
-        prev: 'expression',
-        next: 'const',
-      },
-      {
-        blankLine: 'always',
-        prev: 'const',
-        next: 'expression',
-      },
-      {
-        blankLine: 'always',
-        prev: 'multiline-const',
-        next: '*',
-      },
-      {
-        blankLine: 'always',
-        prev: '*',
-        next: 'multiline-const',
-      },
-    ],
-
-    // Plugin: eslint-plugin-import
-    'import/prefer-default-export': 'off',
-    'import/newline-after-import': ['error', {count: 1}],
-    'no-restricted-imports': ['error', 'vuetify/components'],
-
-    // For omitting extension for ts files
-    'import/extensions': [
-      'error',
-      'ignorePackages',
-      {
-        mjs: 'never',
-        js: 'never',
-        jsx: 'never',
-        ts: 'never',
-        tsx: 'never',
-      },
-    ],
-
-    // ignore virtual files
-    'import/no-unresolved': [2, {
-      ignore: [
-        '~pages$',
-        'virtual:generated-layouts',
-
-        // Ignore vite's ?raw imports
-        '.*\?raw',
-      ],
-    }],
-
-    // Thanks: https://stackoverflow.com/a/63961972/10796681
-    'no-shadow': 'off',
-    '@typescript-eslint/no-shadow': ['error'],
-
-    '@typescript-eslint/consistent-type-imports': 'error',
-
-    // Plugin: eslint-plugin-promise
-    'promise/always-return': 'off',
-    'promise/catch-or-return': 'off',
-
-    // ESLint plugin vue
-    'vue/component-api-style': 'error',
-    'vue/component-name-in-template-casing': ['error', 'PascalCase', {registeredComponentsOnly: false}],
-    'vue/custom-event-name-casing': ['error', 'camelCase', {
-      ignores: [
-        '/^(click):[a-z]+((\d)|([A-Z0-9][a-z0-9]+))*([A-Z])?/',
-      ],
-    }],
-    'vue/define-macros-order': 'error',
-    'vue/html-comment-content-newline': 'error',
-    'vue/html-comment-content-spacing': 'error',
-    'vue/html-comment-indent': 'error',
-    'vue/match-component-file-name': 'error',
-    'vue/no-child-content': 'error',
-    'vue/require-default-prop': 'off',
-
-    // NOTE this rule only supported in SFC,  Users of the unplugin-vue-define-options should disable that rule: https://github.com/vuejs/eslint-plugin-vue/issues/1886
-    // 'vue/no-duplicate-attr-inheritance': 'error',
-    'vue/no-multiple-objects-in-class': 'error',
-    'vue/no-reserved-component-names': 'error',
-    'vue/no-template-target-blank': 'error',
-    'vue/no-useless-mustaches': 'error',
-    'vue/no-useless-v-bind': 'error',
-    'vue/padding-line-between-blocks': 'error',
-    'vue/prefer-separate-static-class': 'error',
-    'vue/prefer-true-attribute-shorthand': 'error',
-    'vue/v-on-function-call': 'error',
-    'vue/valid-v-slot': ['error', {
-      allowModifiers: true,
-    }],
-
-    // -- Extension Rules
-    'vue/no-irregular-whitespace': 'error',
-
-    // -- Sonarlint
-    'sonarjs/no-duplicate-string': 'off',
-    'sonarjs/no-nested-template-literals': 'off',
-
-    // -- Unicorn
-    // 'unicorn/filename-case': 'off',
-    // 'unicorn/prevent-abbreviations': ['error', {
-    //   replacements: {
-    //     props: false,
-    //   },
-    // }],
-    // https://github.com/gmullerb/eslint-plugin-regex
-    'regex/invalid': [
-      'error',
-      [
-        {
-          regex: '@/assets/images',
-          replacement: '@images',
-          message: 'Use \'@images\' path alias for image imports',
-        },
-        {
-          regex: '@/styles',
-          replacement: '@styles',
-          message: 'Use \'@styles\' path alias for importing styles from \'src/styles\'',
-        },
-
-        // {
-        //   id: 'Disallow icon of icon library',
-        //   regex: 'tabler-\\w',
-        //   message: 'Only \'mdi\' icons are allowed',
-        // },
-
-        {
-          regex: '@core/\\w',
-          message: 'You can\'t use @core when you are in @layouts module',
-          files: {
-            inspect: '@layouts/.*',
-          },
-        },
-        {
-          regex: 'useLayouts\\(',
-          message: '`useLayouts` composable is only allowed in @layouts & @core directory. Please use `useThemeConfig` composable instead.',
-          files: {
-            inspect: '^(?!.*(@core|@layouts)).*',
-          },
-        },
-      ],
-
-      // Ignore files
-      '\.eslintrc\.js',
-    ],
-  },
-  settings: {
-    'import/resolver': {
-      node: {
-        extensions: ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.png', '.jpg'],
-      },
-      typescript: {},
-      alias: {
-        map: [
-          ['@', './src'],
-        ],
-      },
-    },
-  },
-  overrides: [
-    {
-      files: ['*.json'],
-      rules: {
-        'no-invalid-meta': 'off',
-      },
-    },
-  ],
-}

+ 2 - 1
app/env.d.ts

@@ -1,5 +1,6 @@
 declare module '*.svg' {
-  import React from 'react'
+  import type React from 'react'
+
   const content: React.FC<React.SVGProps<SVGElement>>
   export default content
 }

+ 52 - 0
app/eslint.config.mjs

@@ -0,0 +1,52 @@
+import createConfig from '@antfu/eslint-config'
+import sonarjs from 'eslint-plugin-sonarjs'
+
+export default createConfig(
+  {
+    stylistic: true,
+    ignores: ['**/version.json', 'tsconfig.json', 'tsconfig.node.json'],
+  },
+  sonarjs.configs.recommended,
+  {
+    name: '@nginx-ui/eslint-config',
+    rules: {
+      'no-console': 'warn',
+      'no-alert': 'warn',
+      'ts/no-explicit-any': 'warn',
+      'vue/no-unused-refs': 'warn',
+      'vue/prop-name-casing': 'warn',
+      'node/prefer-global/process': 'off',
+      'unused-imports/no-unused-vars': 'warn',
+
+      // https://eslint.org/docs/latest/rules/dot-notation
+      'style/dot-notation': 'off',
+
+      // https://eslint.org/docs/latest/rules/arrow-parens
+      'style/arrow-parens': ['error', 'as-needed'],
+
+      // https://eslint.org/docs/latest/rules/prefer-template
+      'prefer-template': 'error',
+
+      // https://eslint.style/rules/js/arrow-spacing
+      'style/arrow-spacing': ['error', { before: true, after: true }],
+
+      // https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/prefer-default-export.md
+      'import/prefer-default-export': 'off',
+
+      // https://eslint.vuejs.org/rules/require-typed-ref
+      'vue/require-typed-ref': 'warn',
+
+      // https://eslint.vuejs.org/rules/require-prop-types
+      'vue/require-prop-types': 'warn',
+
+      // https://eslint.vuejs.org/rules/no-ref-as-operand.html
+      'vue/no-ref-as-operand': 'error',
+
+      // -- Sonarlint
+      'sonarjs/no-duplicate-string': 'off',
+      'sonarjs/no-nested-template-literals': 'off',
+      'sonarjs/pseudo-random': 'warn',
+      'sonarjs/no-nested-functions': 'off',
+    },
+  },
+)

+ 0 - 1
app/gettext.config.cjs

@@ -1,4 +1,3 @@
-// eslint-disable-next-line @typescript-eslint/no-var-requires
 const i18n = require('./i18n.json')
 
 module.exports = {

+ 10 - 15
app/package.json

@@ -1,11 +1,13 @@
 {
   "name": "nginx-ui-app-next",
-  "version": "2.0.0-beta.36",
   "type": "module",
+  "version": "2.0.0-beta.36",
+  "packageManager": "pnpm@9.10.0",
   "scripts": {
     "dev": "vite --host",
     "typecheck": "vue-tsc --noEmit",
-    "lint": "eslint . -c .eslintrc.cjs --fix --ext .ts,.vue,.tsx,.d.ts",
+    "lint": "eslint .",
+    "lint:fix": "eslint --fix .",
     "build": "vite build",
     "preview": "vite preview",
     "gettext:extract": "vue-gettext-extract"
@@ -39,6 +41,7 @@
     "universal-cookie": "^7.2.1",
     "vite-plugin-build-id": "0.4.2",
     "vue": "^3.5.12",
+    "vue-dompurify-html": "^5.1.0",
     "vue-router": "^4.4.5",
     "vue3-ace-editor": "2.2.4",
     "vue3-apexcharts": "1.5.3",
@@ -47,26 +50,19 @@
     "vuedraggable": "^4.1.0"
   },
   "devDependencies": {
-    "@antfu/eslint-config-vue": "^0.43.1",
+    "@antfu/eslint-config": "^3.8.0",
     "@simplewebauthn/types": "^11.0.0",
     "@types/lodash": "^4.17.12",
     "@types/nprogress": "^0.2.3",
     "@types/sortablejs": "^1.15.8",
-    "@typescript-eslint/eslint-plugin": "^6.21.0",
-    "@typescript-eslint/parser": "^6.21.0",
     "@vitejs/plugin-vue": "^5.1.4",
     "@vitejs/plugin-vue-jsx": "^4.0.1",
     "@vue/compiler-sfc": "^3.5.12",
     "@vue/tsconfig": "^0.5.1",
     "ace-builds": "^1.36.3",
     "autoprefixer": "^10.4.20",
-    "eslint": "^8.57.1",
-    "eslint-import-resolver-alias": "^1.1.2",
-    "eslint-import-resolver-typescript": "^3.6.3",
-    "eslint-plugin-import": "^2.31.0",
-    "eslint-plugin-regex": "^1.10.0",
-    "eslint-plugin-sonarjs": "^0.23.0",
-    "eslint-plugin-vue": "^9.29.1",
+    "eslint": "^9.13.0",
+    "eslint-plugin-sonarjs": "^2.0.4",
     "less": "^4.2.0",
     "postcss": "^8.4.47",
     "tailwindcss": "^3.4.14",
@@ -74,9 +70,8 @@
     "unplugin-auto-import": "^0.18.3",
     "unplugin-vue-components": "^0.27.4",
     "unplugin-vue-define-options": "^1.5.2",
-    "vite": "^5.4.9",
+    "vite": "^5.4.10",
     "vite-svg-loader": "^5.1.0",
     "vue-tsc": "^2.1.6"
-  },
-  "packageManager": "pnpm@9.10.0"
+  }
 }

文件差异内容过多而无法显示
+ 4314 - 2545
app/pnpm-lock.yaml


+ 8 - 9
app/src/App.vue

@@ -1,22 +1,21 @@
 <script setup lang="ts">
+import loadTranslations from '@/api/translations'
+import gettext from '@/gettext'
+import { useSettingsStore } from '@/pinia'
+import { theme } from 'ant-design-vue'
+import en_US from 'ant-design-vue/es/locale/en_US'
 
+import zh_CN from 'ant-design-vue/es/locale/zh_CN'
+import zh_TW from 'ant-design-vue/es/locale/zh_TW'
 // This starter template is using Vue 3 <script setup> SFCs
 // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
 import { computed, provide } from 'vue'
-import { theme } from 'ant-design-vue'
-import zh_CN from 'ant-design-vue/es/locale/zh_CN'
-import zh_TW from 'ant-design-vue/es/locale/zh_TW'
-import en_US from 'ant-design-vue/es/locale/en_US'
-
-import { useSettingsStore } from '@/pinia'
-import gettext from '@/gettext'
-import loadTranslations from '@/api/translations'
 
 const route = useRoute()
 
 const media = window.matchMedia('(prefers-color-scheme: dark)')
 
-const callback = () => {
+function callback() {
   const settings = useSettingsStore()
   if (settings.preference_theme === 'auto') {
     if (media.matches)

+ 1 - 1
app/src/api/2fa.ts

@@ -23,7 +23,7 @@ const twoFA = {
   begin_start_secure_session_by_passkey() {
     return http.get('/2fa_secure_session/passkey')
   },
-  finish_start_secure_session_by_passkey(data: { session_id: string; options: AuthenticationResponseJSON }): Promise<{
+  finish_start_secure_session_by_passkey(data: { session_id: string, options: AuthenticationResponseJSON }): Promise<{
     session_id: string
   }> {
     return http.post('/2fa_secure_session/passkey', data.options, {

+ 1 - 1
app/src/api/auth.ts

@@ -41,7 +41,7 @@ const auth = {
   begin_passkey_login() {
     return http.get('/begin_passkey_login')
   },
-  finish_passkey_login(data: { session_id: string; options: AuthenticationResponseJSON }) {
+  finish_passkey_login(data: { session_id: string, options: AuthenticationResponseJSON }) {
     return http.post('/finish_passkey_login', data.options, {
       headers: {
         'X-Passkey-Session-Id': data.session_id,

+ 2 - 2
app/src/api/cert.ts

@@ -1,8 +1,8 @@
+import type { AcmeUser } from '@/api/acme_user'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
 import type { DnsCredential } from '@/api/dns_credential'
-import type { AcmeUser } from '@/api/acme_user'
 import type { PrivateKeyType } from '@/constants'
+import Curd from '@/api/curd'
 
 export interface Cert extends ModelBase {
   name: string

+ 1 - 1
app/src/api/config.ts

@@ -1,5 +1,5 @@
-import Curd from '@/api/curd'
 import type { ChatComplicationMessage } from '@/api/openai'
+import Curd from '@/api/curd'
 import http from '@/lib/http'
 
 export interface Config {

+ 6 - 6
app/src/api/curd.ts

@@ -34,27 +34,27 @@ class Curd<T> {
     this.plural = plural ?? `${this.baseUrl}s`
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   _get_list(params: any = null): Promise<GetListResponse<T>> {
     return http.get(this.plural, { params })
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   _get(id: any = null, params: any = {}): Promise<T> {
     return http.get(this.baseUrl + (id ? `/${id}` : ''), { params })
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  _save(id: any = null, data: any, config: any = undefined): Promise<T> {
+  // eslint-disable-next-line ts/no-explicit-any
+  _save(id: any = null, data: any = undefined, config: any = undefined): Promise<T> {
     return http.post(this.baseUrl + (id ? `/${id}` : ''), data, config)
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   _destroy(id: any = null, params: any = {}) {
     return http.delete(`${this.baseUrl}/${id}`, { params })
   }
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   _recover(id: any = null) {
     return http.patch(`${this.baseUrl}/${id}`)
   }

+ 1 - 1
app/src/api/dns_credential.ts

@@ -1,6 +1,6 @@
+import type { DNSProvider } from '@/api/auto_cert'
 import type { ModelBase } from '@/api/curd'
 import Curd from '@/api/curd'
-import type { DNSProvider } from '@/api/auto_cert'
 
 export interface DnsCredential extends ModelBase {
   name: string

+ 4 - 4
app/src/api/domain.ts

@@ -1,9 +1,9 @@
-import Curd from '@/api/curd'
-import http from '@/lib/http'
-import type { ChatComplicationMessage } from '@/api/openai'
 import type { CertificateInfo } from '@/api/cert'
 import type { NgxConfig } from '@/api/ngx'
+import type { ChatComplicationMessage } from '@/api/openai'
 import type { PrivateKeyType } from '@/constants'
+import Curd from '@/api/curd'
+import http from '@/lib/http'
 
 export interface Site {
   modified_at: string
@@ -26,7 +26,7 @@ export interface AutoCertRequest {
 }
 
 class Domain extends Curd<Site> {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   enable(name: string, config?: any) {
     return http.post(`${this.baseUrl}/${name}/enable`, undefined, config)
   }

+ 1 - 1
app/src/api/environment.ts

@@ -1,6 +1,6 @@
-import http from '@/lib/http'
 import type { ModelBase } from '@/api/curd'
 import Curd from '@/api/curd'
+import http from '@/lib/http'
 
 export interface Environment extends ModelBase {
   name: string

+ 1 - 1
app/src/api/nginx_log.ts

@@ -8,7 +8,7 @@ export interface INginxLogData {
 }
 
 const nginx_log = {
-  page(page = 0, data: INginxLogData) {
+  page(page = 0, data: INginxLogData | undefined = undefined) {
     return http.post(`/nginx_log?page=${page}`, data)
   },
 }

+ 1 - 1
app/src/api/ngx.ts

@@ -46,7 +46,7 @@ const ngx = {
     return http.post('/ngx/format_code', { content })
   },
 
-  status(): Promise<{ running: boolean; message: string; level: number }> {
+  status(): Promise<{ running: boolean, message: string, level: number }> {
     return http.get('/nginx/status')
   },
 

+ 1 - 1
app/src/api/openai.ts

@@ -7,7 +7,7 @@ export interface ChatComplicationMessage {
 }
 
 const openai = {
-  store_record(data: { file_name?: string; messages?: ChatComplicationMessage[] }) {
+  store_record(data: { file_name?: string, messages?: ChatComplicationMessage[] }) {
     return http.post('/chatgpt_record', data)
   },
 }

+ 1 - 1
app/src/api/passkey.ts

@@ -1,6 +1,6 @@
+import type { ModelBase } from '@/api/curd'
 import type { RegistrationResponseJSON } from '@simplewebauthn/types'
 import http from '@/lib/http'
-import type { ModelBase } from '@/api/curd'
 
 export interface Passkey extends ModelBase {
   name: string

+ 3 - 3
app/src/api/stream.ts

@@ -1,7 +1,7 @@
+import type { NgxConfig } from '@/api/ngx'
+import type { ChatComplicationMessage } from '@/api/openai'
 import Curd from '@/api/curd'
 import http from '@/lib/http'
-import type { ChatComplicationMessage } from '@/api/openai'
-import type { NgxConfig } from '@/api/ngx'
 
 export interface Stream {
   modified_at: string
@@ -15,7 +15,7 @@ export interface Stream {
 }
 
 class StreamCurd extends Curd<Stream> {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   enable(name: string, config?: any) {
     return http.post(`${this.baseUrl}/${name}/enable`, undefined, config)
   }

+ 2 - 2
app/src/api/template.ts

@@ -1,11 +1,11 @@
+import type { NgxDirective, NgxLocation, NgxServer } from '@/api/ngx'
 import Curd from '@/api/curd'
 import http from '@/lib/http'
-import type { NgxDirective, NgxLocation, NgxServer } from '@/api/ngx'
 
 export interface Variable {
   type?: string
   name?: Record<string, string>
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   value?: any
   mask?: Record<string, Record<string, string>>
 }

+ 1 - 1
app/src/components/Breadcrumb/types.d.ts

@@ -1,4 +1,4 @@
-import {LocationQueryRaw} from "vue-router";
+import type { LocationQueryRaw } from 'vue-router'
 
 export interface Bread {
   name: string

+ 5 - 5
app/src/components/Chart/AreaChart.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import VueApexCharts from 'vue3-apexcharts'
-import { storeToRefs } from 'pinia'
+import type { Series } from '@/components/Chart/types'
 import type { Ref } from 'vue'
 import { useSettingsStore } from '@/pinia'
-import type { Series } from '@/components/Chart/types'
+import { storeToRefs } from 'pinia'
+import VueApexCharts from 'vue3-apexcharts'
 
 const { series, max, yFormatter } = defineProps<{
   series: Series[]
@@ -14,7 +14,7 @@ const { series, max, yFormatter } = defineProps<{
 const settings = useSettingsStore()
 const { theme } = storeToRefs(settings)
 
-const fontColor = () => {
+function fontColor() {
   return theme.value === 'dark' ? '#b4b4b4' : undefined
 }
 
@@ -85,7 +85,7 @@ let chartOptions = {
   },
 }
 
-const callback = () => {
+function callback() {
   chartOptions = {
     ...chartOptions,
     ...{

+ 4 - 4
app/src/components/Chart/RadialBarChart.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import VueApexCharts from 'vue3-apexcharts'
+import type { Series } from '@/components/Chart/types'
 
-import { storeToRefs } from 'pinia'
 import { useSettingsStore } from '@/pinia'
-import type { Series } from '@/components/Chart/types'
+import { storeToRefs } from 'pinia'
+import VueApexCharts from 'vue3-apexcharts'
 
 const props = defineProps<{
   series: Series[] | number[]
@@ -17,7 +17,7 @@ const settings = useSettingsStore()
 
 const { theme } = storeToRefs(settings)
 
-const fontColor = () => {
+function fontColor() {
   return theme.value === 'dark' ? '#fcfcfc' : undefined
 }
 

+ 1 - 1
app/src/components/Chart/types.d.ts

@@ -1,4 +1,4 @@
-import type {Usage} from '@/api/analytic'
+import type { Usage } from '@/api/analytic'
 
 export interface Series {
   name: string

+ 14 - 14
app/src/components/ChatGPT/ChatGPT.vue

@@ -1,18 +1,18 @@
 <script setup lang="ts">
-import nginx from 'highlight.js/lib/languages/nginx'
-import Icon, { SendOutlined } from '@ant-design/icons-vue'
-import { storeToRefs } from 'pinia'
-import { Marked } from 'marked'
-import hljs from 'highlight.js'
-import { markedHighlight } from 'marked-highlight'
+import type { ChatComplicationMessage } from '@/api/openai'
 import type { Ref } from 'vue'
+import openai from '@/api/openai'
+import ChatGPT_logo from '@/assets/svg/ChatGPT_logo.svg?component'
 import { urlJoin } from '@/lib/helper'
 import { useSettingsStore, useUserStore } from '@/pinia'
-import 'highlight.js/styles/vs2015.css'
+import Icon, { SendOutlined } from '@ant-design/icons-vue'
+import hljs from 'highlight.js'
+import nginx from 'highlight.js/lib/languages/nginx'
+import { Marked } from 'marked'
 
-import type { ChatComplicationMessage } from '@/api/openai'
-import openai from '@/api/openai'
-import ChatGPT_logo from '@/assets/svg/ChatGPT_logo.svg?component'
+import { markedHighlight } from 'marked-highlight'
+import { storeToRefs } from 'pinia'
+import 'highlight.js/styles/vs2015.css'
 
 const props = defineProps<{
   content: string
@@ -40,7 +40,6 @@ watch(history_messages, () => {
 const loading = ref(false)
 const ask_buffer = ref('')
 
-// eslint-disable-next-line sonarjs/cognitive-complexity
 async function request() {
   loading.value = true
 
@@ -82,7 +81,7 @@ async function request() {
       }
       apply(value!)
     }
-    catch (e) {
+    catch {
       break
     }
   }
@@ -172,7 +171,8 @@ const marked = new Marked(
 
       return `<pre><code class="hljs ${language}">${highlightedCode}</code></pre>`
     },
-  }))
+  }),
+)
 
 marked.setOptions({
   pedantic: false,
@@ -238,8 +238,8 @@ const show = computed(() => !messages.value || messages.value?.length === 0)
             <template #content>
               <div
                 v-if="item.role === 'assistant' || editing_idx !== index"
+                v-dompurify-html="marked.parse(item.content)"
                 class="content"
-                v-html="marked.parse(item.content)"
               />
               <AInput
                 v-else

+ 2 - 2
app/src/components/CodeEditor/CodeEditor.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
+import ace from 'ace-builds'
+import extSearchboxUrl from 'ace-builds/src-noconflict/ext-searchbox?url'
 import { VAceEditor } from 'vue3-ace-editor'
 import 'ace-builds/src-noconflict/mode-nginx'
-import ace from 'ace-builds'
 import 'ace-builds/src-noconflict/theme-monokai'
-import extSearchboxUrl from 'ace-builds/src-noconflict/ext-searchbox?url'
 
 const props = defineProps<{
   content?: string

+ 1 - 1
app/src/components/EnvIndicator/EnvIndicator.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
+import { useSettingsStore } from '@/pinia'
 import { CloseOutlined, DashboardOutlined, DatabaseOutlined } from '@ant-design/icons-vue'
 import { storeToRefs } from 'pinia'
 import { useRouter } from 'vue-router'
-import { useSettingsStore } from '@/pinia'
 
 const settingsStore = useSettingsStore()
 

+ 2 - 1
app/src/components/Logo/Logo.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
-import logo from '@/assets/img/logo.png'</script>
+import logo from '@/assets/img/logo.png'
+</script>
 
 <template>
   <div class="logo">

+ 3 - 3
app/src/components/NginxControl/NginxControl.vue

@@ -1,10 +1,10 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
-import { ReloadOutlined } from '@ant-design/icons-vue'
 import ngx from '@/api/ngx'
-import { logLevel } from '@/views/config/constants'
 import { NginxStatus } from '@/constants'
 import { useGlobalStore } from '@/pinia/moudule/global'
+import { logLevel } from '@/views/config/constants'
+import { ReloadOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
 
 const global = useGlobalStore()
 const { nginxStatus: status } = storeToRefs(global)

+ 1 - 1
app/src/components/NodeSelector/NodeSelector.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import type { Ref } from 'vue'
 import type { Environment } from '@/api/environment'
+import type { Ref } from 'vue'
 import environment from '@/api/environment'
 
 const props = defineProps<{

+ 6 - 6
app/src/components/Notification/Notification.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
-import { BellOutlined, CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, InfoCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
+import type { Notification } from '@/api/notification'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Ref } from 'vue'
-import { message } from 'ant-design-vue'
 import notification from '@/api/notification'
-import type { Notification } from '@/api/notification'
+import { detailRender } from '@/components/Notification/detailRender'
 import { NotificationTypeT } from '@/constants'
 import { useUserStore } from '@/pinia'
-import { detailRender } from '@/components/Notification/detailRender'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { BellOutlined, CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, InfoCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
 
 const loading = ref(false)
 
@@ -112,7 +112,7 @@ function viewAll() {
               </template>
               <AListItemMeta
                 :title="$gettext(item.title)"
-                :description="detailRender({ text: item.details, record: item } as customRender)"
+                :description="detailRender({ text: item.details, record: item } as CustomRenderProps)"
               >
                 <template #avatar>
                   <div>

+ 3 - 6
app/src/components/Notification/cert.ts

@@ -1,18 +1,15 @@
 export function syncCertificateSuccess(text: string) {
   const data = JSON.parse(text)
 
-  return $gettext('Sync Certificate %{cert_name} to %{env_name} successfully',
-    { cert_name: data.cert_name, env_name: data.env_name })
+  return $gettext('Sync Certificate %{cert_name} to %{env_name} successfully', { cert_name: data.cert_name, env_name: data.env_name })
 }
 
 export function syncCertificateError(text: string) {
   const data = JSON.parse(text)
 
   if (data.status_code === 404) {
-    return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
-      { cert_name: data.cert_name, env_name: data.env_name }, true)
+    return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version', { cert_name: data.cert_name, env_name: data.env_name }, true)
   }
 
-  return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, response: %{resp}',
-    { cert_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
+  return $gettext('Sync Certificate %{cert_name} to %{env_name} failed, response: %{resp}', { cert_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
 }

+ 6 - 12
app/src/components/Notification/config.ts

@@ -1,37 +1,31 @@
 export function syncConfigSuccess(text: string) {
   const data = JSON.parse(text)
 
-  return $gettext('Sync Config %{config_name} to %{env_name} successfully',
-    { config_name: data.config_name, env_name: data.env_name })
+  return $gettext('Sync Config %{config_name} to %{env_name} successfully', { config_name: data.config_name, env_name: data.env_name })
 }
 
 export function syncConfigError(text: string) {
   const data = JSON.parse(text)
 
   if (data.status_code === 404) {
-    return $gettext('Sync config %{config_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
-      { config_name: data.config_name, env_name: data.env_name }, true)
+    return $gettext('Sync config %{config_name} to %{env_name} failed, please upgrade the remote Nginx UI to the latest version', { config_name: data.config_name, env_name: data.env_name }, true)
   }
 
-  return $gettext('Sync config %{config_name} to %{env_name} failed, response: %{resp}',
-    { config_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
+  return $gettext('Sync config %{config_name} to %{env_name} failed, response: %{resp}', { config_name: data.cert_name, env_name: data.env_name, resp: data.resp_body }, true)
 }
 
 export function syncRenameConfigSuccess(text: string) {
   const data = JSON.parse(text)
 
-  return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} successfully',
-    { orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name })
+  return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} successfully', { orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name })
 }
 
 export function syncRenameConfigError(text: string) {
   const data = JSON.parse(text)
 
   if (data.status_code === 404) {
-    return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, please upgrade the remote Nginx UI to the latest version',
-      { orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name }, true)
+    return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, please upgrade the remote Nginx UI to the latest version', { orig_path: data.orig_path, new_path: data.orig_path, env_name: data.env_name }, true)
   }
 
-  return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, response: %{resp}',
-    { orig_path: data.orig_path, new_path: data.orig_path, resp: data.resp_body, env_name: data.env_name }, true)
+  return $gettext('Rename %{orig_path} to %{new_path} on %{env_name} failed, response: %{resp}', { orig_path: data.orig_path, new_path: data.orig_path, resp: data.resp_body, env_name: data.env_name }, true)
 }

+ 2 - 2
app/src/components/Notification/detailRender.ts

@@ -1,4 +1,4 @@
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { syncCertificateError, syncCertificateSuccess } from '@/components/Notification/cert'
 import {
   syncConfigError,
@@ -7,7 +7,7 @@ import {
   syncRenameConfigSuccess,
 } from '@/components/Notification/config'
 
-export const detailRender = (args: customRender) => {
+export function detailRender(args: CustomRenderProps) {
   switch (args.record.title) {
     case 'Sync Certificate Success':
       return syncCertificateSuccess(args.text)

+ 4 - 4
app/src/components/SetLanguage/SetLanguage.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import dayjs from 'dayjs'
-import { useSettingsStore } from '@/pinia'
-import gettext from '@/gettext'
 import loadTranslations from '@/api/translations'
+import gettext from '@/gettext'
+import { useSettingsStore } from '@/pinia'
+import dayjs from 'dayjs'
 
 import 'dayjs/locale/fr'
 import 'dayjs/locale/ja'
@@ -29,7 +29,7 @@ const current = computed({
 
 const languageAvailable = gettext.available
 
-const updateTitle = () => {
+function updateTitle() {
   const name = route.meta.name as never as () => string
 
   document.title = `${name()} | Nginx UI`

+ 3 - 3
app/src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
 import StdDataEntry from '@/components/StdDesign/StdDataEntry'
+import { message } from 'ant-design-vue'
 
 const props = defineProps<{
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   api: (ids: number[], data: any) => Promise<void>
   beforeSave?: () => Promise<void>
 }>()
@@ -15,7 +15,7 @@ const batchColumns = ref([])
 const visible = ref(false)
 
 const selectedRowKeys = ref([])
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function showModal(c: any, rowKeys: any) {
   visible.value = true
   selectedRowKeys.value = rowKeys

+ 32 - 33
app/src/components/StdDesign/StdDataDisplay/StdCurd.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts" generic="T=any">
-import { message } from 'ant-design-vue'
+import type { StdTableSlots } from '@/components/StdDesign/StdDataDisplay/types'
+import type { Column } from '@/components/StdDesign/types'
 import type { ComputedRef } from 'vue'
 import type { StdTableProps } from './StdTable.vue'
-import StdTable from './StdTable.vue'
-import StdDataEntry from '@/components/StdDesign/StdDataEntry'
-import type { Column } from '@/components/StdDesign/types'
 import StdCurdDetail from '@/components/StdDesign/StdDataDisplay/StdCurdDetail.vue'
-import type { StdTableSlots } from '@/components/StdDesign/StdDataDisplay/types'
+import StdDataEntry from '@/components/StdDesign/StdDataEntry'
+import { message } from 'ant-design-vue'
+import StdTable from './StdTable.vue'
 
 export interface StdCurdProps<T> extends StdTableProps<T> {
   cardTitleKey?: string
@@ -19,13 +19,13 @@ export interface StdCurdProps<T> extends StdTableProps<T> {
   onClickAdd?: () => void
 
   onClickEdit?: (id: number | string, record: T, index: number) => void
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   beforeSave?: (data: any) => Promise<void>
 }
 
 const props = defineProps<StdTableProps<T> & StdCurdProps<T>>()
 const visible = ref(false)
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const data: any = reactive({ id: null })
 const modifyMode = ref(true)
 const editMode = ref<string>()
@@ -35,11 +35,11 @@ provide('data', data)
 provide('editMode', editMode)
 provide('shouldRefetchList', shouldRefetchList)
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const error: any = reactive({})
 const selected = ref([])
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function onSelect(keys: any) {
   selected.value = keys
 }
@@ -50,7 +50,7 @@ const editableColumns = computed(() => {
   })
 }) as ComputedRef<Column[]>
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function add(preset: any = undefined) {
   if (props.onClickAdd)
     return
@@ -69,12 +69,12 @@ function add(preset: any = undefined) {
 
 const table = ref()
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
   default: () => [],
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const selectedRows = defineModel<any[]>('selectedRows', {
   type: Array,
   default: () => [],
@@ -84,8 +84,8 @@ const getParams = reactive({
   trash: false,
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const setParams = (k: string, v: any) => {
+// eslint-disable-next-line ts/no-explicit-any
+function setParams(k: string, v: any) {
   getParams[k] = v
 }
 
@@ -117,17 +117,15 @@ async function ok() {
     await formRef.validateFields()
     props?.beforeSave?.(data)
     props
-      .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } })
-      .then(r => {
-        message.success($gettext('Save successfully'))
-        Object.assign(data, r)
-        get_list()
-        visible.value = false
-      })
-      .catch(e => {
-        message.error($gettext(e?.message ?? 'Server error'), 5)
-        Object.assign(error, e.errors)
-      })
+      .api!.save(data.id, { ...data, ...props.overwriteParams }, { params: { ...props.overwriteParams } }).then(r => {
+      message.success($gettext('Save successfully'))
+      Object.assign(data, r)
+      get_list()
+      visible.value = false
+    }).catch(e => {
+      message.error($gettext(e?.message ?? 'Server error'), 5)
+      Object.assign(error, e.errors)
+    })
   }
   catch {
     message.error($gettext('Please fill in the required fields'))
@@ -169,18 +167,19 @@ function view(id: number | string) {
 
 async function get(id: number | string) {
   return props
-    .api!.get(id, { ...props.overwriteParams })
-    .then(async r => {
-      Object.keys(data).forEach(k => {
-        delete data[k]
-      })
-      data.id = null
-      Object.assign(data, r)
+    .api!.get(id, { ...props.overwriteParams }).then(async r => {
+    Object.keys(data).forEach(k => {
+      delete data[k]
     })
+    data.id = null
+    Object.assign(data, r)
+  })
 }
 
 const modalTitle = computed(() => {
-  return data.id ? modifyMode.value ? $gettext('Modify') : $gettext('View Details') : $gettext('Add')
+  if (data.id)
+    return modifyMode.value ? $gettext('Modify') : $gettext('View Details')
+  return $gettext('Add')
 })
 
 const localOverwriteParams = reactive(props.overwriteParams ?? {})

+ 4 - 4
app/src/components/StdDesign/StdDataDisplay/StdCurdDetail.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
-import type { ComputedRef } from 'vue'
-import _ from 'lodash'
 import type { Column } from '@/components/StdDesign/types'
-import { labelRender } from '@/components/StdDesign/StdDataEntry'
+import type { ComputedRef } from 'vue'
 import { CustomRender } from '@/components/StdDesign/StdDataDisplay/components/CustomRender'
+import { labelRender } from '@/components/StdDesign/StdDataEntry'
+import _ from 'lodash'
 
 const props = defineProps<{
   columns: Column[]
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   data: any
 }>()
 

+ 28 - 29
app/src/components/StdDesign/StdDataDisplay/StdTable.vue

@@ -1,28 +1,28 @@
 <script setup lang="ts" generic="T=any">
+import type { Pagination } from '@/api/curd'
+import type Curd from '@/api/curd'
+import type { Column } from '@/components/StdDesign/types'
 import type { TableProps } from 'ant-design-vue'
-import { message } from 'ant-design-vue'
-import { HolderOutlined } from '@ant-design/icons-vue'
-import type { ComputedRef, Ref } from 'vue'
-import type { SorterResult, TablePaginationConfig } from 'ant-design-vue/lib/table/interface'
-import type { FilterValue } from 'ant-design-vue/es/table/interface'
 import type { Key } from 'ant-design-vue/es/_util/type'
+import type { FilterValue } from 'ant-design-vue/es/table/interface'
+import type { SorterResult, TablePaginationConfig } from 'ant-design-vue/lib/table/interface'
+import type { ComputedRef, Ref } from 'vue'
 import type { RouteParams } from 'vue-router'
+import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
+import StdDataEntry from '@/components/StdDesign/StdDataEntry'
+import { HolderOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
 import _ from 'lodash'
 import StdPagination from './StdPagination.vue'
-import StdDataEntry from '@/components/StdDesign/StdDataEntry'
-import type { Pagination } from '@/api/curd'
-import type { Column } from '@/components/StdDesign/types'
-import useSortable from '@/components/StdDesign/StdDataDisplay/methods/sortable'
-import type Curd from '@/api/curd'
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export interface StdTableProps<T = any> {
   title?: string
   mode?: string
   rowKey?: string
   api: Curd<T>
   columns: Column[]
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   getParams?: Record<string, any>
   size?: string
   disableQueryParams?: boolean
@@ -30,7 +30,7 @@ export interface StdTableProps<T = any> {
   pithy?: boolean
   exportExcel?: boolean
   exportMaterial?: boolean
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   overwriteParams?: Record<string, any>
   disableView?: boolean
   disableModify?: boolean
@@ -40,7 +40,7 @@ export interface StdTableProps<T = any> {
   disablePagination?: boolean
   sortableMoveHook?: (oldRow: number[], newRow: number[]) => boolean
   scrollX?: string | number
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   getCheckboxProps?: (record: any) => any
 }
 
@@ -71,10 +71,10 @@ watch(dataSource, () => {
   expandKeysList.value = res
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const rowsKeyIndexMap: Ref<Record<number, any>> = ref({})
 const loading = ref(true)
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const selectedRecords: Ref<Record<any, any>> = ref({})
 
 // This can be useful if there are more than one StdTable in the same page.
@@ -93,12 +93,12 @@ const params = reactive({
   ...props.getParams,
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const selectedRowKeys = defineModel<any[]>('selectedRowKeys', {
   default: () => [],
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const selectedRows = defineModel<any[]>('selectedRows', {
   type: Array,
   default: () => [],
@@ -213,10 +213,10 @@ function recover(id: number | string) {
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
   if (data && data.length > 0) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     data.forEach((v: any) => {
       v.level = level
 
@@ -253,7 +253,7 @@ async function _get_list(page_num = null, page_size = 20) {
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function onTableChange(_pagination: TablePaginationConfig, filters: Record<string, FilterValue>, sorter: SorterResult | SorterResult<any>[]) {
   if (sorter) {
     sorter = sorter as SorterResult
@@ -286,7 +286,7 @@ function expandedTable(keys: Key[]) {
   expandKeysList.value = keys
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any,sonarjs/cognitive-complexity
+// eslint-disable-next-line ts/no-explicit-any
 async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
   if (props.selectionType === 'checkbox' || props.exportExcel) {
     if (selected) {
@@ -300,13 +300,13 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
       })
     }
     else {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      // eslint-disable-next-line ts/no-explicit-any
       selectedRowKeys.value = selectedRowKeys.value.filter((v: any) => v !== record[props.rowKey])
       delete selectedRecords.value[record[props.rowKey]]
     }
 
     await nextTick(async () => {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      // eslint-disable-next-line ts/no-explicit-any
       const filteredRows: any[] = []
 
       selectedRowKeys.value.forEach(v => {
@@ -325,10 +325,10 @@ async function onSelect(record: any, selected: boolean, _selectedRows: any[]) {
   }
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 async function onSelectAll(selected: boolean, _selectedRows: any[], changeRows: any[]) {
   // console.log(selected, selectedRows, changeRows)
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   changeRows.forEach((v: any) => {
     if (v) {
       if (selected) {
@@ -350,7 +350,7 @@ async function onSelectAll(selected: boolean, _selectedRows: any[], changeRows:
   // console.log(selectedRowKeysBuffer.value, selectedRecords.value)
 
   await nextTick(async () => {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     const filteredRows: any[] = []
 
     selectedRowKeys.value.forEach(v => {
@@ -362,7 +362,7 @@ async function onSelectAll(selected: boolean, _selectedRows: any[], changeRows:
 
 const router = useRouter()
 
-const resetSearch = async () => {
+async function resetSearch() {
   Object.keys(params).forEach(v => {
     delete params[v]
   })
@@ -460,7 +460,6 @@ const paginationSize = computed(() => {
   else
     return 'default'
 })
-
 </script>
 
 <template>

+ 38 - 36
app/src/components/StdDesign/StdDataDisplay/StdTableTransformer.tsx

@@ -1,27 +1,27 @@
-// text, record, index, column
-import dayjs from 'dayjs'
 import type { JSX } from 'vue/jsx-runtime'
 import { Tag } from 'ant-design-vue'
+// text, record, index, column
+import dayjs from 'dayjs'
 import { get } from 'lodash'
 
-export interface customRender {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+export interface CustomRenderProps {
+  // eslint-disable-next-line ts/no-explicit-any
   text: any
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   record: any
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   index: any
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   column: any
   isExport?: boolean
   isDetail?: boolean
 }
 
-export const datetime = (args: customRender) => {
+export function datetime(args: CustomRenderProps) {
   return dayjs(args.text).format('YYYY-MM-DD HH:mm:ss')
 }
 
-export const date = (args: customRender) => {
+export function date(args: CustomRenderProps) {
   return args.text ? dayjs(args.text).format('YYYY-MM-DD') : '-'
 }
 
@@ -29,10 +29,10 @@ export const date = (args: customRender) => {
 date.isDate = true
 datetime.isDatetime = true
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const mask = (maskObj: any): (args: customRender) => JSX.Element => {
-  return (args: customRender) => {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
+export function mask(maskObj: any): (args: CustomRenderProps) => JSX.Element {
+  return (args: CustomRenderProps) => {
+    // eslint-disable-next-line ts/no-explicit-any
     let v: any
 
     if (typeof maskObj?.[args.text] === 'function')
@@ -45,15 +45,15 @@ export const mask = (maskObj: any): (args: customRender) => JSX.Element => {
   }
 }
 
-export const arrayToTextRender = (args: customRender) => {
+export function arrayToTextRender(args: CustomRenderProps) {
   return args.text?.join(', ')
 }
-export const actualValueRender = (args: customRender, actualDataIndex: string | string[]) => {
+export function actualValueRender(args: CustomRenderProps, actualDataIndex: string | string[]) {
   return get(args.record, actualDataIndex)
 }
 
-export const longTextWithEllipsis = (len: number): (args: customRender) => JSX.Element => {
-  return (args: customRender) => {
+export function longTextWithEllipsis(len: number): (args: CustomRenderProps) => JSX.Element {
+  return (args: CustomRenderProps) => {
     if (args.isExport || args.isDetail)
       return args.text
 
@@ -61,29 +61,31 @@ export const longTextWithEllipsis = (len: number): (args: customRender) => JSX.E
   }
 }
 
-export const year = (args: customRender) => {
+export function year(args: CustomRenderProps) {
   return dayjs(args.text).format('YYYY')
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const maskRenderWithColor = (maskObj: any) => (args: customRender) => {
-  let label: string
-  if (typeof maskObj[args.text] === 'function')
-    label = maskObj[args.text]()
-  else if (typeof maskObj[args.text] === 'string')
-    label = maskObj[args.text]
-  else label = args.text
+// eslint-disable-next-line ts/no-explicit-any
+export function maskRenderWithColor(maskObj: any) {
+  return (args: CustomRenderProps) => {
+    let label: string
+    if (typeof maskObj[args.text] === 'function')
+      label = maskObj[args.text]()
+    else if (typeof maskObj[args.text] === 'string')
+      label = maskObj[args.text]
+    else label = args.text
 
-  if (args.isExport)
-    return label
+    if (args.isExport)
+      return label
 
-  const colorMap = {
-    0: '',
-    1: 'blue',
-    2: 'green',
-    3: 'red',
-    4: 'cyan',
-  }
+    const colorMap = {
+      0: '',
+      1: 'blue',
+      2: 'green',
+      3: 'red',
+      4: 'cyan',
+    }
 
-  return args.text ? h(Tag, { color: colorMap[args.text] }, maskObj[args.text]) : '-'
+    return args.text ? h(Tag, { color: colorMap[args.text] }, maskObj[args.text]) : '-'
+  }
 }

+ 2 - 2
app/src/components/StdDesign/StdDataDisplay/components/CustomRender.tsx

@@ -1,7 +1,7 @@
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import _ from 'lodash'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 
-export function CustomRender(props: customRender) {
+export function CustomRender(props: CustomRenderProps) {
   return props.column.customRender
     ? props.column.customRender(props)
     : _.get(props.record, props.column.dataIndex!)

+ 4 - 4
app/src/components/StdDesign/StdDataDisplay/index.ts

@@ -1,9 +1,9 @@
-import StdTable from './StdTable.vue'
-import StdCurd from './StdCurd.vue'
 import StdBatchEdit from './StdBatchEdit.vue'
+import StdCurd from './StdCurd.vue'
+import StdTable from './StdTable.vue'
 
 export {
-  StdTable,
-  StdCurd,
   StdBatchEdit,
+  StdCurd,
+  StdTable,
 }

+ 19 - 21
app/src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts

@@ -1,14 +1,14 @@
+import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
+import type { Column, StdTableResponse } from '@/components/StdDesign/types'
+import type { ComputedRef } from 'vue'
+import { downloadCsv } from '@/lib/helper'
 import { message } from 'ant-design-vue'
 import dayjs from 'dayjs'
-import type { ComputedRef } from 'vue'
 import _ from 'lodash'
-import { downloadCsv } from '@/lib/helper'
-import type { Column, StdTableResponse } from '@/components/StdDesign/types'
-import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 
 async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[]>) {
-  const header: { title?: string; key: Column['dataIndex'] }[] = []
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  const header: { title?: string, key: Column['dataIndex'] }[] = []
+  // eslint-disable-next-line ts/no-explicit-any
   const headerKeys: any[] = []
   const showColumnsMap: Record<string, Column> = {}
 
@@ -26,33 +26,31 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
     showColumnsMap[column?.dataIndex?.toString() as string] = column
   })
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   const dataSource: any[] = []
   let hasMore = true
   let page = 1
   while (hasMore) {
     // 准备 DataSource
     await props
-      .api!.get_list({ page })
-      .then((r: StdTableResponse) => {
-        if (r.data.length === 0) {
-          hasMore = false
-
-          return
-        }
-        dataSource.push(...r.data)
-      })
-      .catch((e: { message?: string }) => {
-        message.error(e.message ?? $gettext('Server error'))
+      .api!.get_list({ page }).then((r: StdTableResponse) => {
+      if (r.data.length === 0) {
         hasMore = false
-      })
+
+        return
+      }
+      dataSource.push(...r.data)
+    }).catch((e: { message?: string }) => {
+      message.error(e.message ?? $gettext('Server error'))
+      hasMore = false
+    })
     page += 1
   }
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   const data: any[] = []
 
   dataSource.forEach(row => {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     const obj: Record<string, any> = {}
 
     headerKeys.forEach(key => {

+ 113 - 109
app/src/components/StdDesign/StdDataDisplay/methods/sortable.ts

@@ -1,17 +1,17 @@
-import { message } from 'ant-design-vue'
+import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
+import type { Key } from 'ant-design-vue/es/_util/type'
 import type { Ref } from 'vue'
+import { message } from 'ant-design-vue'
 import sortable from 'sortablejs'
-import type { Key } from 'ant-design-vue/es/_util/type'
-import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function getRowKey(item: any) {
   return item.dataset.rowKey
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function getTargetData(data: any, indexList: number[]): any {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   let target: any = { children: data }
   indexList.forEach((index: number) => {
     target.children[index].parent = target
@@ -21,112 +21,116 @@ function getTargetData(data: any, indexList: number[]): any {
   return target
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Ref<any[]>,
-  rowsKeyIndexMap: Ref<Record<number, number[]>>, expandKeysList: Ref<Key[]>) {
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
+function useSortable(props: StdTableProps, randomId: Ref<string>, dataSource: Ref<any[]>, rowsKeyIndexMap: Ref<Record<number, number[]>>, expandKeysList: Ref<Key[]>) {
+  // eslint-disable-next-line ts/no-explicit-any
   const table: any = document.querySelector(`#std-table-${randomId.value} tbody`)
 
-  // eslint-disable-next-line no-new,new-cap
-  new sortable(table, {
-    handle: '.ant-table-drag-icon',
-    animation: 150,
-    sort: true,
-    forceFallback: true,
-    setData(dataTransfer) {
-      dataTransfer.setData('Text', '')
-    },
-    onStart({ item }) {
-      const targetRowKey = Number(getRowKey(item))
-      if (targetRowKey)
-        expandKeysList.value = expandKeysList.value.filter((_item: Key) => _item !== targetRowKey)
-    },
-    onMove({
-      dragged,
-             related,
-    }) {
-      const oldRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(dragged))]
-      const newRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(related))]
-
-      if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] !== newRow[newRow.length - 2])
-        return false
-
-      if (props.sortableMoveHook)
-        return props.sortableMoveHook(oldRow, newRow)
-    },
-    async onEnd({
-      item,
-                  newIndex,
-                  oldIndex,
-    }) {
-      if (newIndex === oldIndex)
-        return
-
-      const indexDelta: number = Number(oldIndex) - Number(newIndex)
-      const direction: number = indexDelta > 0 ? +1 : -1
-
-      const rowIndex: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(item))]
-      const newRow = getTargetData(dataSource.value, rowIndex)
-      const newRowParent = newRow.parent
-      const level: number = newRow.level
-
-      const currentRowIndex: number[] = [...rowsKeyIndexMap.value?.
-        [Number(getRowKey(table.children[Number(newIndex) + direction]))]]
-
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
-
-      // Reset parent
-      currentRow.parent = newRow.parent = null
-      newRowParent.children.splice(rowIndex[level], 1)
-      newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
-
-      const changeIds: number[] = []
-
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      function processChanges(row: any, children = false, _newIndex: number | undefined = undefined) {
-        // Build changes ID list expect new row
-        if (children || _newIndex === undefined)
-          changeIds.push(row.id)
-
-        if (_newIndex !== undefined)
-          rowsKeyIndexMap.value[row.id][level] = _newIndex
-        else if (children)
-          rowsKeyIndexMap.value[row.id][level] += direction
-
-        row.parent = null
-        if (row.children) {
-          // eslint-disable-next-line @typescript-eslint/no-explicit-any
-          row.children.forEach((v: any) => processChanges(v, true, _newIndex))
+  try {
+    // eslint-disable-next-line no-new,new-cap
+    new sortable(table, {
+      handle: '.ant-table-drag-icon',
+      animation: 150,
+      sort: true,
+      forceFallback: true,
+      setData(dataTransfer) {
+        dataTransfer.setData('Text', '')
+      },
+      onStart({ item }) {
+        const targetRowKey = Number(getRowKey(item))
+        if (targetRowKey)
+          expandKeysList.value = expandKeysList.value.filter((_item: Key) => _item !== targetRowKey)
+      },
+      onMove({
+        dragged,
+               related,
+      }) {
+        const oldRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(dragged))]
+        const newRow: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(related))]
+
+        if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] !== newRow[newRow.length - 2])
+          return false
+
+        if (props.sortableMoveHook)
+          return props.sortableMoveHook(oldRow, newRow)
+      },
+      async onEnd({
+        item,
+                    newIndex,
+                    oldIndex,
+      }) {
+        if (newIndex === oldIndex || newIndex === undefined)
+          return
+
+        const indexDelta: number = Number(oldIndex) - Number(newIndex)
+        const direction: number = indexDelta > 0 ? +1 : -1
+
+        const rowIndex: number[] = rowsKeyIndexMap.value?.[Number(getRowKey(item))]
+        const newRow = getTargetData(dataSource.value, rowIndex)
+        const newRowParent = newRow.parent
+        const level: number = newRow.level
+
+        const currentRowIndex: number[] = [...rowsKeyIndexMap.value?.
+          [Number(getRowKey(table.children[Number(newIndex) + direction]))]]
+
+        // eslint-disable-next-line ts/no-explicit-any
+        const currentRow: any = getTargetData(dataSource.value, currentRowIndex)
+
+        // Reset parent
+        currentRow.parent = newRow.parent = null
+        newRowParent.children.splice(rowIndex[level], 1)
+        newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))
+
+        const changeIds: number[] = []
+
+        // eslint-disable-next-line ts/no-explicit-any
+        function processChanges(row: any, children = false, _newIndex: number | undefined = undefined) {
+          // Build changes ID list expect new row
+          if (children || _newIndex === undefined)
+            changeIds.push(row.id)
+
+          if (_newIndex !== undefined)
+            rowsKeyIndexMap.value[row.id][level] = _newIndex
+          else if (children)
+            rowsKeyIndexMap.value[row.id][level] += direction
+
+          row.parent = null
+          if (row.children) {
+            // eslint-disable-next-line ts/no-explicit-any
+            row.children.forEach((v: any) => processChanges(v, true, _newIndex))
+          }
         }
-      }
-
-      // Replace row index for new row
-      processChanges(newRow, false, currentRowIndex[level])
-
-      // Rebuild row index maps for changes row
-      for (let i = Number(oldIndex); i !== newIndex; i -= direction) {
-        const _rowIndex: number[] = rowsKeyIndexMap.value?.[getRowKey(table.children[i])]
-
-        _rowIndex[level] += direction
-        processChanges(getTargetData(dataSource.value, _rowIndex))
-      }
-
-      // console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
-      //   ', changes IDs:', changeIds
-
-      props.api.update_order({
-        target_id: newRow.id,
-        direction,
-        affected_ids: changeIds,
-      }).then(() => {
-        message.success($gettext('Updated successfully'))
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      }).catch((e: any) => {
-        message.error(e?.message ?? $gettext('Server error'))
-      })
-    },
-  })
+
+        // Replace row index for new row
+        processChanges(newRow, false, currentRowIndex[level])
+
+        // Rebuild row index maps for changes row
+        for (let i = Number(oldIndex); i >= newIndex; i -= direction) {
+          const _rowIndex: number[] = rowsKeyIndexMap.value?.[getRowKey(table.children[i])]
+
+          _rowIndex[level] += direction
+          processChanges(getTargetData(dataSource.value, _rowIndex))
+        }
+
+        // console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
+        //   ', changes IDs:', changeIds
+
+        props.api.update_order({
+          target_id: newRow.id,
+          direction,
+          affected_ids: changeIds,
+        }).then(() => {
+          message.success($gettext('Updated successfully'))
+          // eslint-disable-next-line ts/no-explicit-any
+        }).catch((e: any) => {
+          message.error(e?.message ?? $gettext('Server error'))
+        })
+      },
+    })
+  }
+  catch (e) {
+    console.error(e)
+  }
 }
 
 export default useSortable

+ 4 - 2
app/src/components/StdDesign/StdDataDisplay/types.d.ts

@@ -1,4 +1,6 @@
+/* eslint-disable ts/no-explicit-any */
+
 export interface StdTableSlots {
-  'append-search': ({}) => any
-  actions: ({}: Record<string, any>) => any
+  'append-search': (action) => any
+  'actions': (actions: Record<string, any>) => any
 }

+ 6 - 6
app/src/components/StdDesign/StdDataEntry/StdDataEntry.vue

@@ -1,13 +1,13 @@
 <script setup lang="tsx">
-import { Form } from 'ant-design-vue'
-import type { Ref } from 'vue'
 import type { Column, JSXElements, StdDesignEdit } from '@/components/StdDesign/types'
-import StdFormItem from '@/components/StdDesign/StdDataEntry/StdFormItem.vue'
+import type { Ref } from 'vue'
 import { labelRender } from '@/components/StdDesign/StdDataEntry'
+import StdFormItem from '@/components/StdDesign/StdDataEntry/StdFormItem.vue'
+import { Form } from 'ant-design-vue'
 
 const props = defineProps<{
   dataList: Column[]
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   dataSource: Record<string, any>
   errors?: Record<string, string>
   type?: 'search' | 'edit'
@@ -15,7 +15,7 @@ const props = defineProps<{
 }>()
 
 const emit = defineEmits<{
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   'update:dataSource': [data: Record<string, any>]
 }>()
 
@@ -95,7 +95,7 @@ function Render() {
   })
 
   if (slots.action)
-    template.push(<div class={'std-data-entry-action'}>{slots.action()}</div>)
+    template.push(<div class="std-data-entry-action">{slots.action()}</div>)
 
   return <Form ref={formRef} model={dataSource.value} layout={props.layout || 'vertical'}>{template}</Form>
 }

+ 1 - 1
app/src/components/StdDesign/StdDataEntry/StdFormItem.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import { computed } from 'vue'
 import type { Column } from '@/components/StdDesign/types'
+import { computed } from 'vue'
 
 const props = defineProps<Props>()
 

+ 4 - 4
app/src/components/StdDesign/StdDataEntry/components/StdSelect.vue

@@ -1,19 +1,19 @@
 <script setup lang="ts">
-import { ref } from 'vue'
 import type { SelectProps } from 'ant-design-vue'
+import { ref } from 'vue'
 
 const props = defineProps<{
   mask?: Record<string | number, string | (() => string)> | (() => Promise<Record<string | number, string>>)
   placeholder?: string
   multiple?: boolean
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   defaultValue?: any
 }>()
 
 const selectedValue = defineModel<string | number | string[] | number[]>('value')
 const options = ref<SelectProps['options']>([])
 
-const loadOptions = async () => {
+async function loadOptions() {
   options.value = []
   let actualValue: number | string
   if (typeof props.mask === 'function') {
@@ -45,7 +45,7 @@ const loadOptions = async () => {
   }
 }
 
-const init = () => {
+function init() {
   loadOptions()
 }
 

+ 9 - 9
app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue

@@ -1,28 +1,28 @@
 <script setup lang="ts">
-import _ from 'lodash'
-import type { Ref } from 'vue'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import type Curd from '@/api/curd'
 import type { Column } from '@/components/StdDesign/types'
+import type { Ref } from 'vue'
+import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
+import _ from 'lodash'
 
 const props = defineProps<{
   label?: string
   selectedKey: number | number[] | undefined | null
   selectionType: 'radio' | 'checkbox'
   recordValueIndex: string // to index the value of the record
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   api: Curd<any>
   columns: Column[]
   disableSearch?: boolean
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   getParams?: any
   description?: string
   errorMessages?: string
   itemKey?: string // default: id
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   value?: any | any[]
   disabled?: boolean
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   valueApi?: Curd<any>
 }>()
 
@@ -33,7 +33,7 @@ const getParams = computed(() => {
 })
 
 const visible = ref(false)
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const M_values = ref([]) as any
 
 const init = _.debounce(_init, 500, {
@@ -45,7 +45,7 @@ onMounted(() => {
   init()
 })
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 const records = ref([]) as Ref<any[]>
 
 async function _init() {

+ 18 - 18
app/src/components/StdDesign/StdDataEntry/index.tsx

@@ -1,4 +1,6 @@
-import { h } from 'vue'
+import type { StdDesignEdit } from '@/components/StdDesign/types'
+import type { Dayjs } from 'dayjs'
+import { DATE_FORMAT } from '@/constants'
 import {
   DatePicker,
   Input,
@@ -7,16 +9,14 @@ import {
   Switch,
   Textarea,
 } from 'ant-design-vue'
-import type { Dayjs } from 'dayjs'
 import dayjs from 'dayjs'
-import StdDataEntry from './StdDataEntry.vue'
-import StdSelector from './components/StdSelector.vue'
-import StdSelect from './components/StdSelect.vue'
+import { h } from 'vue'
 import StdPassword from './components/StdPassword.vue'
-import type { StdDesignEdit } from '@/components/StdDesign/types'
-import { DATE_FORMAT } from '@/constants'
+import StdSelect from './components/StdSelect.vue'
+import StdSelector from './components/StdSelector.vue'
+import StdDataEntry from './StdDataEntry.vue'
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function readonly(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return h('p', dataSource?.[dataIndex] ?? edit?.config?.defaultValue)
 }
@@ -32,7 +32,7 @@ export function placeholderHelper(edit: StdDesignEdit) {
   return typeof edit.config?.placeholder === 'function' ? edit.config?.placeholder() : edit.config?.placeholder
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function input(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return h(Input, {
     'autocomplete': 'off',
@@ -44,7 +44,7 @@ export function input(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function inputNumber(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   if (edit.config?.defaultValue !== undefined)
     dataSource[dataIndex] = edit.config.defaultValue
@@ -64,7 +64,7 @@ export function inputNumber(edit: StdDesignEdit, dataSource: any, dataIndex: any
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return h(Textarea, {
     'placeholder': placeholderHelper(edit),
@@ -75,7 +75,7 @@ export function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function password(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return (
     <StdPassword
@@ -87,7 +87,7 @@ export function password(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   )
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function select(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   const actualDataIndex = edit?.actualDataIndex ?? dataIndex
 
@@ -102,7 +102,7 @@ export function select(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   )
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function selector(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return (
     <StdSelector
@@ -119,18 +119,18 @@ export function selector(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   )
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function switcher(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return h(Switch, {
     'checked': dataSource?.[dataIndex] ?? edit?.config?.defaultValue,
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     'onUpdate:checked': (value: any) => {
       dataSource[dataIndex] = value
     },
   })
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function datePicker(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   const date: Dayjs | undefined = dataSource?.[dataIndex] ? dayjs(dataSource?.[dataIndex]) : undefined
 
@@ -143,7 +143,7 @@ export function datePicker(edit: StdDesignEdit, dataSource: any, dataIndex: any)
   )
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 export function dateRangePicker(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   const dates: [Dayjs, Dayjs] = dataSource
     ?.[dataIndex]

+ 15 - 12
app/src/components/StdDesign/types.d.ts

@@ -1,7 +1,8 @@
-import Curd, {Pagination} from '@/api/curd'
-import {Ref} from 'vue'
-import type {JSX} from 'vue/jsx'
-import {TableColumnType} from "ant-design-vue"
+import type { Pagination } from '@/api/curd'
+import type Curd from '@/api/curd'
+import type { TableColumnType } from 'ant-design-vue'
+import type { Ref } from 'vue'
+import type { JSX } from 'vue/jsx'
 
 export type JSXElements = JSX.Element[]
 
@@ -25,7 +26,7 @@ export interface StdDesignEdit {
   }
 
   selector?: {
-    getParams?: {}
+    getParams?: object
     recordValueIndex: any // relative to api return
     selectionType: any
     api: Curd
@@ -52,7 +53,7 @@ export interface StdDesignEdit {
     max?: number // max value for input number
     error_messages?: Ref
     required?: boolean
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     defaultValue?: any
     addonBefore?: string // for inputNumber
     addonAfter?: string // for inputNumber
@@ -63,12 +64,14 @@ export interface StdDesignEdit {
   flex?: Flex
 }
 
+type FlexType = string | number | boolean
+
 export interface Flex {
-  sm?: string | number | boolean
-  md?: string | number | boolean
-  lg?: string | number | boolean
-  xl?: string | number | boolean
-  xxl?: string | number | boolean
+  sm?: FlexType
+  md?: FlexType
+  lg?: FlexType
+  xl?: FlexType
+  xxl?: FlexType
 }
 
 export interface Column extends TableColumnType {
@@ -88,7 +91,7 @@ export interface Column extends TableColumnType {
   batch?: boolean
   customRender?: function
   selector?: {
-    getParams?: {}
+    getParams?: object
     recordValueIndex: any // relative to api return
     selectionType: any
     api: Curd

+ 2 - 2
app/src/components/SwitchAppearance/SwitchAppearance.vue

@@ -1,9 +1,9 @@
 <script lang="ts" setup>
 import type { Ref } from 'vue'
-import VPIconMoon from './icons/VPIconMoon.vue'
-import VPIconSun from './icons/VPIconSun.vue'
 import VPSwitch from '@/components/VPSwitch/VPSwitch.vue'
 import { useSettingsStore } from '@/pinia'
+import VPIconMoon from './icons/VPIconMoon.vue'
+import VPIconSun from './icons/VPIconSun.vue'
 
 const settings = useSettingsStore()
 const devicePrefersTheme = inject('devicePrefersTheme') as Ref<string>

+ 5 - 5
app/src/components/TwoFA/Authorization.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import { KeyOutlined } from '@ant-design/icons-vue'
-import { startAuthentication } from '@simplewebauthn/browser'
-import { message } from 'ant-design-vue'
-import OTPInput from '@/components/OTPInput/OTPInput.vue'
 import type { TwoFAStatusResponse } from '@/api/2fa'
 import twoFA from '@/api/2fa'
+import OTPInput from '@/components/OTPInput/OTPInput.vue'
 import { useUserStore } from '@/pinia'
+import { KeyOutlined } from '@ant-design/icons-vue'
+import { startAuthentication } from '@simplewebauthn/browser'
+import { message } from 'ant-design-vue'
 
 defineProps<{
   twoFAStatus: TwoFAStatusResponse
@@ -55,7 +55,7 @@ async function passkeyAuthenticate() {
 
     emit('submitSecureSessionID', r.session_id)
   }
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   catch (e: any) {
     message.error($gettext(e.message ?? 'Server error'))
   }

+ 4 - 4
app/src/components/TwoFA/use2FAModal.ts

@@ -1,10 +1,10 @@
-import { createVNode, render } from 'vue'
-import { Modal, message } from 'ant-design-vue'
-import Authorization from '@/components/TwoFA/Authorization.vue'
 import twoFA from '@/api/2fa'
+import Authorization from '@/components/TwoFA/Authorization.vue'
 import { useUserStore } from '@/pinia'
+import { message, Modal } from 'ant-design-vue'
+import { createVNode, render } from 'vue'
 
-const use2FAModal = () => {
+function use2FAModal() {
   const refOTPAuthorization = ref<typeof Authorization>()
   const randomId = Math.random().toString(36).substring(2, 8)
   const { secureSessionId } = storeToRefs(useUserStore())

+ 1 - 1
app/src/composables/useBreadcrumbs.ts

@@ -1,5 +1,5 @@
 import type { Bread } from '@/components/Breadcrumb/types'
 
-export const useBreadcrumbs = () => {
+export function useBreadcrumbs() {
   return inject('breadList') as Ref<Bread[]>
 }

+ 4 - 4
app/src/layouts/BaseLayout.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts">
+import settings from '@/api/settings'
+import PageHeader from '@/components/PageHeader/PageHeader.vue'
+import { useSettingsStore } from '@/pinia'
 import _ from 'lodash'
 import { storeToRefs } from 'pinia'
 import FooterLayout from './FooterLayout.vue'
-import SideBar from './SideBar.vue'
 import HeaderLayout from './HeaderLayout.vue'
-import PageHeader from '@/components/PageHeader/PageHeader.vue'
-import { useSettingsStore } from '@/pinia'
-import settings from '@/api/settings'
+import SideBar from './SideBar.vue'
 
 const drawer_visible = ref(false)
 const collapsed = ref(collapse())

+ 5 - 5
app/src/layouts/HeaderLayout.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
-import { HomeOutlined, LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
-import { useRouter } from 'vue-router'
-import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
 import auth from '@/api/auth'
 import NginxControl from '@/components/NginxControl/NginxControl.vue'
-import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
 import Notification from '@/components/Notification/Notification.vue'
+import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
+import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
+import { HomeOutlined, LogoutOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
+import { useRouter } from 'vue-router'
 
 const emit = defineEmits<{
   clickUnFold: [void]

+ 16 - 14
app/src/layouts/SideBar.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import type { ComputedRef, Ref } from 'vue'
-import type { AntdIconType } from '@ant-design/icons-vue/lib/components/AntdIcon'
 import type { IconComponentProps } from '@ant-design/icons-vue/es/components/Icon'
+import type { AntdIconType } from '@ant-design/icons-vue/lib/components/AntdIcon'
 import type { Key } from 'ant-design-vue/es/_util/type'
+import type { ComputedRef, Ref } from 'vue'
+import EnvIndicator from '@/components/EnvIndicator/EnvIndicator.vue'
 import Logo from '@/components/Logo/Logo.vue'
 import { routes } from '@/routes'
-import EnvIndicator from '@/components/EnvIndicator/EnvIndicator.vue'
 
 const route = useRoute()
 
@@ -33,41 +33,43 @@ const sidebars = computed(() => {
   return routes[0].children
 })
 
-interface meta {
+interface Meta {
   icon: AntdIconType
   hiddenInSidebar: boolean
   hideChildren: boolean
   name: () => string
 }
 
-interface sidebar {
+interface Sidebar {
   path: string
   name: string
-  meta: meta
-  children: sidebar[]
+  meta: Meta
+  children: Sidebar[]
 }
 
-const visible: ComputedRef<sidebar[]> = computed(() => {
-  const res: sidebar[] = [];
+const visible: ComputedRef<Sidebar[]> = computed(() => {
+  const res: Sidebar[] = [];
 
   (sidebars.value || []).forEach(s => {
     if (s.meta && ((typeof s.meta.hiddenInSidebar === 'boolean' && s.meta.hiddenInSidebar)
-      || (typeof s.meta.hiddenInSidebar === 'function' && s.meta.hiddenInSidebar())))
+      || (typeof s.meta.hiddenInSidebar === 'function' && s.meta.hiddenInSidebar()))) {
       return
+    }
 
-    const t: sidebar = {
+    const t: Sidebar = {
       path: s.path,
       name: s.name as string,
-      meta: s.meta as unknown as meta,
+      meta: s.meta as unknown as Meta,
       children: [],
     };
 
     (s.children || []).forEach(c => {
       if (c.meta && ((typeof c.meta.hiddenInSidebar === 'boolean' && c.meta.hiddenInSidebar)
-        || (typeof c.meta.hiddenInSidebar === 'function' && c.meta.hiddenInSidebar())))
+        || (typeof c.meta.hiddenInSidebar === 'function' && c.meta.hiddenInSidebar()))) {
         return
+      }
 
-      t.children.push((c as unknown as sidebar))
+      t.children.push((c as unknown as Sidebar))
     })
     res.push(t)
   })

+ 9 - 8
app/src/lib/helper/index.ts

@@ -13,7 +13,7 @@ function bytesToSize(bytes: number) {
 
   return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`
 }
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
+// eslint-disable-next-line ts/no-explicit-any
 function downloadCsv(header: any, data: any[], fileName: string) {
   if (!header || !Array.isArray(header) || !Array.isArray(data) || !header.length)
     return
@@ -25,8 +25,8 @@ function downloadCsv(header: any, data: any[], fileName: string) {
   csvContent += `${_header}\n`
   data.forEach((item, index) => {
     let dataString = ''
-    for (let i = 0; i < keys.length; i++)
-      dataString += `${item[keys[i]]},`
+    for (const element of keys)
+      dataString += `${item[element]},`
 
     csvContent += index < data.length ? dataString.replace(/,$/, '\n') : dataString.replace(/,$/, '')
   })
@@ -39,15 +39,16 @@ function downloadCsv(header: any, data: any[], fileName: string) {
   window.URL.revokeObjectURL(csvContent)
 }
 
-const urlJoin = (...args: string[]) =>
-  args
+function urlJoin(...args: string[]) {
+  return args
     .join('/')
-    .replace(/[\/]+/g, '/')
+    .replace(/\/+/g, '/')
     .replace(/^(.+):\//, '$1://')
     .replace(/^file:/, 'file:/')
     .replace(/\/(\?|&|#[^!])/g, '$1')
     .replace(/\?/g, '&')
     .replace('&', '?')
+}
 
 function fromNow(t: string) {
   dayjs.extend(relativeTime)
@@ -66,8 +67,8 @@ function formatDateTime(t: string) {
 export {
   bytesToSize,
   downloadCsv,
-  urlJoin,
-  fromNow,
   formatDate,
   formatDateTime,
+  fromNow,
+  urlJoin,
 }

+ 15 - 15
app/src/lib/http/index.ts

@@ -1,12 +1,12 @@
 import type { AxiosRequestConfig } from 'axios'
+import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { useSettingsStore, useUserStore } from '@/pinia'
+import router from '@/routes'
 import axios from 'axios'
-import { storeToRefs } from 'pinia'
 import NProgress from 'nprogress'
-import { useSettingsStore, useUserStore } from '@/pinia'
-import 'nprogress/nprogress.css'
 
-import router from '@/routes'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { storeToRefs } from 'pinia'
+import 'nprogress/nprogress.css'
 
 const user = useUserStore()
 const settings = useSettingsStore()
@@ -30,17 +30,17 @@ instance.interceptors.request.use(
   config => {
     NProgress.start()
     if (token.value) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      // eslint-disable-next-line ts/no-explicit-any
       (config.headers as any).Authorization = token.value
     }
 
     if (settings.environment.id) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      // eslint-disable-next-line ts/no-explicit-any
       (config.headers as any)['X-Node-ID'] = settings.environment.id
     }
 
     if (secureSessionId.value) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      // eslint-disable-next-line ts/no-explicit-any
       (config.headers as any)['X-Secure-Session-ID'] = secureSessionId.value
     }
 
@@ -78,25 +78,25 @@ instance.interceptors.response.use(
 
 const http = {
   get(url: string, config: AxiosRequestConfig = {}) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     return instance.get<any, any>(url, config)
   },
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   post(url: string, data: any = undefined, config: AxiosRequestConfig = {}) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     return instance.post<any, any>(url, data, config)
   },
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   put(url: string, data: any = undefined, config: AxiosRequestConfig = {}) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     return instance.put<any, any>(url, data, config)
   },
   delete(url: string, config: AxiosRequestConfig = {}) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     return instance.delete<any, any>(url, config)
   },
   patch(url: string, config: AxiosRequestConfig = {}) {
-    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    // eslint-disable-next-line ts/no-explicit-any
     return instance.patch<any, any>(url, config)
   },
 }

+ 4 - 5
app/src/lib/websocket/index.ts

@@ -1,7 +1,7 @@
-import ReconnectingWebSocket from 'reconnecting-websocket'
-import { storeToRefs } from 'pinia'
-import { useSettingsStore, useUserStore } from '@/pinia'
 import { urlJoin } from '@/lib/helper'
+import { useSettingsStore, useUserStore } from '@/pinia'
+import { storeToRefs } from 'pinia'
+import ReconnectingWebSocket from 'reconnecting-websocket'
 
 function ws(url: string, reconnect: boolean = true): ReconnectingWebSocket | WebSocket {
   const user = useUserStore()
@@ -12,8 +12,7 @@ function ws(url: string, reconnect: boolean = true): ReconnectingWebSocket | Web
 
   const node_id = (settings.environment.id > 0) ? (`&x_node_id=${settings.environment.id}`) : ''
 
-  const _url = urlJoin(protocol + window.location.host, window.location.pathname,
-    url, `?token=${btoa(token.value)}`, node_id)
+  const _url = urlJoin(protocol + window.location.host, window.location.pathname, url, `?token=${btoa(token.value)}`, node_id)
 
   if (reconnect)
     return new ReconnectingWebSocket(_url, undefined, { maxRetries: 10 })

+ 6 - 4
app/src/main.ts

@@ -1,11 +1,12 @@
-import { createApp } from 'vue'
+import { useSettingsStore } from '@/pinia'
+import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
 import { createPinia } from 'pinia'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
-import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
-import gettext from './gettext'
+import { createApp } from 'vue'
+import VueDOMPurifyHTML from 'vue-dompurify-html'
 import App from './App.vue'
+import gettext from './gettext'
 import router from './routes'
-import { useSettingsStore } from '@/pinia'
 import './style.css'
 
 const pinia = createPinia()
@@ -15,6 +16,7 @@ const app = createApp(App)
 pinia.use(piniaPluginPersistedstate)
 app.use(pinia)
 app.use(gettext)
+app.use(VueDOMPurifyHTML)
 
 // after pinia created
 const settings = useSettingsStore()

+ 2 - 2
app/src/pinia/index.ts

@@ -1,7 +1,7 @@
-import { useUserStore } from './moudule/user'
 import { useSettingsStore } from './moudule/settings'
+import { useUserStore } from './moudule/user'
 
 export {
-  useUserStore,
   useSettingsStore,
+  useUserStore,
 }

+ 1 - 1
app/src/pinia/moudule/global.ts

@@ -1,5 +1,5 @@
-import { defineStore } from 'pinia'
 import type { NginxStatus } from '@/constants'
+import { defineStore } from 'pinia'
 
 export const useGlobalStore = defineStore('global', () => {
   const nginxStatus:

+ 2 - 2
app/src/pinia/moudule/user.ts

@@ -1,6 +1,6 @@
-import { defineStore } from 'pinia'
-import { useCookies } from '@vueuse/integrations/useCookies'
 import type { CookieChangeOptions } from 'universal-cookie'
+import { useCookies } from '@vueuse/integrations/useCookies'
+import { defineStore } from 'pinia'
 
 export const useUserStore = defineStore('user', () => {
   const cookies = useCookies(['nginx-ui'])

+ 2 - 2
app/src/routes/index.ts

@@ -1,5 +1,5 @@
 import type { RouteRecordRaw } from 'vue-router'
-import { createRouter, createWebHashHistory } from 'vue-router'
+import { useSettingsStore, useUserStore } from '@/pinia'
 
 import {
   BellOutlined,
@@ -17,7 +17,7 @@ import {
 } from '@ant-design/icons-vue'
 import NProgress from 'nprogress'
 
-import { useSettingsStore, useUserStore } from '@/pinia'
+import { createRouter, createWebHashHistory } from 'vue-router'
 
 import 'nprogress/nprogress.css'
 

+ 2 - 2
app/src/routes/type.d.ts

@@ -1,8 +1,8 @@
+import type { AntDesignOutlinedIconType } from '@ant-design/icons-vue/lib/icons/AntDesignOutlined'
+
 // src/types/vue-router.d.ts
 import 'vue-router'
 
-import type {AntDesignOutlinedIconType} from '@ant-design/icons-vue/lib/icons/AntDesignOutlined'
-
 /**
  * @description Extend the types of router meta
  */

+ 21 - 14
app/src/views/certificate/ACMEUser.vue

@@ -1,12 +1,12 @@
 <script setup lang="tsx">
-import { Tag, message } from 'ant-design-vue'
-import type { Column } from '@/components/StdDesign/types'
-import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
 import type { AcmeUser } from '@/api/acme_user'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { Column } from '@/components/StdDesign/types'
 import acme_user from '@/api/acme_user'
-import { input, switcher } from '@/components/StdDesign/StdDataEntry'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
 import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { input, switcher } from '@/components/StdDesign/StdDataEntry'
+import { message, Tag } from 'ant-design-vue'
 
 const columns: Column[] = [
   {
@@ -20,7 +20,8 @@ const columns: Column[] = [
         required: true,
       },
     },
-  }, {
+  },
+  {
     title: () => $gettext('Email'),
     dataIndex: 'email',
     sorter: true,
@@ -31,7 +32,8 @@ const columns: Column[] = [
         required: true,
       },
     },
-  }, {
+  },
+  {
     title: () => $gettext('CA Dir'),
     dataIndex: 'ca_dir',
     sorter: true,
@@ -44,7 +46,8 @@ const columns: Column[] = [
         },
       },
     },
-  }, {
+  },
+  {
     title: () => $gettext('Proxy'),
     dataIndex: 'proxy',
     hiddenInTable: true,
@@ -57,10 +60,11 @@ const columns: Column[] = [
         },
       },
     },
-  }, {
+  },
+  {
     title: () => $gettext('Status'),
     dataIndex: ['registration', 'body', 'status'],
-    customRender: (args: customRender) => {
+    customRender: (args: CustomRenderProps) => {
       if (args.text === 'valid')
         return <Tag color="green">{$gettext('Valid')}</Tag>
 
@@ -68,7 +72,8 @@ const columns: Column[] = [
     },
     sorter: true,
     pithy: true,
-  }, {
+  },
+  {
     title: () => $gettext('Register On Startup'),
     dataIndex: 'register_on_startup',
     hiddenInTable: true,
@@ -76,15 +81,17 @@ const columns: Column[] = [
     edit: {
       type: switcher,
       hint: $gettext('When Enabled, Nginx UI will automatically re-register users upon startup. '
-          + 'Generally, do not enable this unless you are in a dev environment and using Pebble as CA.'),
+        + 'Generally, do not enable this unless you are in a dev environment and using Pebble as CA.'),
     },
-  }, {
+  },
+  {
     title: () => $gettext('Updated at'),
     dataIndex: 'updated_at',
     customRender: datetime,
     sorter: true,
     pithy: true,
-  }, {
+  },
+  {
     title: () => $gettext('Action'),
     dataIndex: 'action',
   },

+ 4 - 4
app/src/views/certificate/ACMEUserSelector.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
+import type { AcmeUser } from '@/api/acme_user'
+import type { AutoCertOptions } from '@/api/auto_cert'
 import type { SelectProps } from 'ant-design-vue'
 import type { Ref } from 'vue'
-import type { AcmeUser } from '@/api/acme_user'
 import acme_user from '@/api/acme_user'
-import type { AutoCertOptions } from '@/api/auto_cert'
 
 const users = ref([]) as Ref<AcmeUser[]>
 
@@ -51,7 +51,7 @@ onMounted(async () => {
         break
       page++
     }
-    catch (e) {
+    catch {
       break
     }
   }
@@ -75,7 +75,7 @@ const options = computed<SelectProps['options']>(() => {
   return list
 })
 
-const filterOption = (input: string, option: { label: string }) => {
+function filterOption(input: string, option: { label: string }) {
   return option.label.toLowerCase().includes(input.toLowerCase())
 }
 </script>

+ 10 - 11
app/src/views/certificate/CertificateEditor.vue

@@ -1,15 +1,15 @@
 <script setup lang="ts">
-import type { Ref } from 'vue'
-import { message } from 'ant-design-vue'
-import { AutoCertState } from '@/constants'
-import CertInfo from '@/views/site/cert/CertInfo.vue'
-import AutoCertStepOne from '@/views/site/cert/components/AutoCertStepOne.vue'
-import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import type { Cert } from '@/api/cert'
+import type { Ref } from 'vue'
 import cert from '@/api/cert'
+import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
-import RenewCert from '@/views/certificate/RenewCert.vue'
 import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
+import { AutoCertState } from '@/constants'
+import RenewCert from '@/views/certificate/RenewCert.vue'
+import CertInfo from '@/views/site/cert/CertInfo.vue'
+import AutoCertStepOne from '@/views/site/cert/components/AutoCertStepOne.vue'
+import { message } from 'ant-design-vue'
 
 const route = useRoute()
 
@@ -235,10 +235,9 @@ const isManaged = computed(() => {
         :md="12"
       >
         <ACard :title="$gettext('Log')">
-          <pre
-            class="log-container"
-            v-html="log"
-          />
+          <pre class="log-container">
+            {{ log }}
+          </pre>
         </ACard>
       </ACol>
     </ARow>

+ 3 - 3
app/src/views/certificate/CertificateList/Certificate.vue

@@ -1,9 +1,9 @@
 <script setup lang="tsx">
-import { CloudUploadOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
-import certColumns from './certColumns'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import cert from '@/api/cert'
+import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import WildcardCertificate from '@/views/certificate/WildcardCertificate.vue'
+import { CloudUploadOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
+import certColumns from './certColumns'
 
 const refWildcard = ref()
 const refTable = ref()

+ 25 - 17
app/src/views/certificate/CertificateList/certColumns.tsx

@@ -1,17 +1,17 @@
-import dayjs from 'dayjs'
-import { Badge, Tag } from 'ant-design-vue'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Column, JSXElements } from '@/components/StdDesign/types'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { input } from '@/components/StdDesign/StdDataEntry'
 import { PrivateKeyTypeMask } from '@/constants'
+import { Badge, Tag } from 'ant-design-vue'
+import dayjs from 'dayjs'
 
 const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
   sorter: true,
   pithy: true,
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const { text, record } = args
     if (!text)
       return h('div', record.domain)
@@ -24,26 +24,34 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Type'),
   dataIndex: 'auto_cert',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const template: JSXElements = []
     const { text } = args
     const sync = $gettext('Sync Certificate')
     const managed = $gettext('Managed Certificate')
     const general = $gettext('General Certificate')
     if (text === true || text === 1) {
-      template.push(<Tag bordered={false} color="processing">
-        { managed }
-        </Tag>)
+      template.push(
+        <Tag bordered={false} color="processing">
+          { managed }
+        </Tag>,
+      )
     }
     else if (text === 2) {
-      template.push(<Tag bordered={false} color="success">
-        { sync }
-        </Tag>)
+      template.push(
+        <Tag bordered={false} color="success">
+          { sync }
+        </Tag>,
+      )
     }
     else {
-      template.push(<Tag bordered={false} color="purple">{
-          general }
-        </Tag>)
+      template.push(
+        <Tag bordered={false} color="purple">
+          {
+            general
+          }
+        </Tag>,
+      )
     }
 
     return h('div', template)
@@ -60,7 +68,7 @@ const columns: Column[] = [{
   title: () => $gettext('Status'),
   dataIndex: 'certificate_info',
   pithy: true,
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const template: JSXElements = []
 
     const text = args.text?.not_before
@@ -69,11 +77,11 @@ const columns: Column[] = [{
       && !dayjs().isAfter(args.text?.not_after)
 
     if (text) {
-      template.push(<Badge status="success"/>)
+      template.push(<Badge status="success" />)
       template.push($gettext('Valid'))
     }
     else {
-      template.push(<Badge status="error"/>)
+      template.push(<Badge status="error" />)
       template.push($gettext('Expired'))
     }
 

+ 4 - 2
app/src/views/certificate/DNSChallenge.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import type { DNSProvider } from '@/api/auto_cert'
 import type { SelectProps } from 'ant-design-vue'
 import type { Ref } from 'vue'
-import type { DNSProvider } from '@/api/auto_cert'
 import auto_cert from '@/api/auto_cert'
 
 const providers = ref([]) as Ref<DNSProvider[]>
@@ -62,7 +62,7 @@ const options = computed<SelectProps['options']>(() => {
   return list
 })
 
-const filterOption = (input: string, option: { label: string }) => {
+function filterOption(input: string, option: { label: string }) {
   return option.label.toLowerCase().includes(input.toLowerCase())
 }
 </script>
@@ -78,6 +78,7 @@ const filterOption = (input: string, option: { label: string }) => {
       />
     </AFormItem>
     <AFormItem>
+      <!-- eslint-disable sonarjs/no-vue-bypass-sanitization -->
       <p v-if="current?.links?.api">
         {{ $gettext('API Document') }}: <a
           :href="current.links.api"
@@ -92,6 +93,7 @@ const filterOption = (input: string, option: { label: string }) => {
           rel="noopener noreferrer"
         >{{ current.links.go_client }}</a>
       </p>
+      <!-- eslint-enable -->
     </AFormItem>
     <template v-if="current?.configuration?.credentials">
       <h4>{{ $gettext('Credentials') }}</h4>

+ 5 - 5
app/src/views/certificate/DNSCredential.vue

@@ -1,11 +1,11 @@
 <script setup lang="tsx">
-import DNSChallenge from './DNSChallenge.vue'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { Column } from '@/components/StdDesign/types'
 import dns_credential from '@/api/dns_credential'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
+import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { input } from '@/components/StdDesign/StdDataEntry'
-import type { Column } from '@/components/StdDesign/types'
+import DNSChallenge from './DNSChallenge.vue'
 
 const columns: Column[] = [{
   title: () => $gettext('Name'),
@@ -18,7 +18,7 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Provider'),
   dataIndex: ['config', 'name'],
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     return args.record.provider
   },
   sorter: true,

+ 3 - 3
app/src/views/certificate/RenewCert.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
-import ObtainCertLive from '@/views/site/cert/components/ObtainCertLive.vue'
 import type { AutoCertOptions } from '@/api/auto_cert'
+import ObtainCertLive from '@/views/site/cert/components/ObtainCertLive.vue'
+import { message } from 'ant-design-vue'
 
 const props = defineProps<{
   options: AutoCertOptions
@@ -16,7 +16,7 @@ const modalClosable = ref(true)
 
 const refObtainCertLive = ref()
 
-const issueCert = () => {
+function issueCert() {
   modalVisible.value = true
 
   const { name, domains, key_type } = props.options

+ 5 - 6
app/src/views/certificate/WildcardCertificate.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import type { Ref } from 'vue'
-import { message } from 'ant-design-vue'
-import ObtainCertLive from '@/views/site/cert/components/ObtainCertLive.vue'
 import type { AutoCertOptions } from '@/api/auto_cert'
+import type { Ref } from 'vue'
 import AutoCertStepOne from '@/views/site/cert/components/AutoCertStepOne.vue'
+import ObtainCertLive from '@/views/site/cert/components/ObtainCertLive.vue'
+import { message } from 'ant-design-vue'
 
 const emit = defineEmits<{
   issued: [void]
@@ -39,12 +39,11 @@ const computedDomain = computed(() => {
   return `*.${domain.value}`
 })
 
-const issueCert = () => {
+function issueCert() {
   step.value++
   modalVisible.value = true
 
-  refObtainCertLive.value.issue_cert(computedDomain.value,
-    [computedDomain.value, domain.value], data.value.key_type)
+  refObtainCertLive.value.issue_cert(computedDomain.value, [computedDomain.value, domain.value], data.value.key_type)
     .then(() => {
       message.success($gettext('Renew successfully'))
       emit('issued')

+ 10 - 10
app/src/views/config/ConfigEditor.vue

@@ -1,20 +1,20 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
-import type { Ref } from 'vue'
-import { InfoCircleOutlined } from '@ant-design/icons-vue'
-import _ from 'lodash'
-import { formatDateTime } from '@/lib/helper'
-import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import type { Config } from '@/api/config'
+import type { ChatComplicationMessage } from '@/api/openai'
+import type { Ref } from 'vue'
 import config from '@/api/config'
-import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import ngx from '@/api/ngx'
-import InspectConfig from '@/views/config/InspectConfig.vue'
 import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
-import type { ChatComplicationMessage } from '@/api/openai'
-import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
+import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
+import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
+import { formatDateTime } from '@/lib/helper'
 import { useSettingsStore } from '@/pinia'
+import InspectConfig from '@/views/config/InspectConfig.vue'
+import { InfoCircleOutlined } from '@ant-design/icons-vue'
+import { message } from 'ant-design-vue'
+import _ from 'lodash'
 
 const settings = useSettingsStore()
 const route = useRoute()

+ 4 - 4
app/src/views/config/ConfigList.vue

@@ -1,13 +1,13 @@
 <script setup lang="ts">
-import { $gettext } from '../../gettext'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import config from '@/api/config'
-import configColumns from '@/views/config/configColumns'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
-import InspectConfig from '@/views/config/InspectConfig.vue'
+import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
 import Mkdir from '@/views/config/components/Mkdir.vue'
 import Rename from '@/views/config/components/Rename.vue'
+import configColumns from '@/views/config/configColumns'
+import InspectConfig from '@/views/config/InspectConfig.vue'
+import { $gettext } from '../../gettext'
 
 const table = ref()
 const route = useRoute()

+ 1 - 2
app/src/views/config/components/Mkdir.vue

@@ -1,8 +1,7 @@
 <script setup lang="ts">
-
-import { message } from 'ant-design-vue'
 import config from '@/api/config'
 import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { message } from 'ant-design-vue'
 
 const emit = defineEmits(['created'])
 const visible = ref(false)

+ 2 - 2
app/src/views/config/components/Rename.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
 import config from '@/api/config'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
 import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
+import use2FAModal from '@/components/TwoFA/use2FAModal'
+import { message } from 'ant-design-vue'
 
 const emit = defineEmits(['renamed'])
 const visible = ref(false)

+ 4 - 4
app/src/views/config/configColumns.ts

@@ -1,8 +1,8 @@
-import { h } from 'vue'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { JSXElements } from '@/components/StdDesign/types'
+import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { input } from '@/components/StdDesign/StdDataEntry'
+import { h } from 'vue'
 
 const configColumns = [{
   title: () => $gettext('Name'),
@@ -15,7 +15,7 @@ const configColumns = [{
 }, {
   title: () => $gettext('Type'),
   dataIndex: 'is_dir',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const template: JSXElements = []
     const { text } = args
     if (text === true || text > 0)

+ 1 - 1
app/src/views/dashboard/DashBoard.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
-import ServerAnalytic from '@/views/dashboard/ServerAnalytic.vue'
 import Environments from '@/views/dashboard/Environments.vue'
+import ServerAnalytic from '@/views/dashboard/ServerAnalytic.vue'
 
 const key = ref(0)
 

+ 5 - 5
app/src/views/dashboard/Environments.vue

@@ -1,16 +1,16 @@
 <script setup lang="ts">
-import Icon, { LinkOutlined, SendOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
+import type { Node } from '@/api/environment'
 import type ReconnectingWebSocket from 'reconnecting-websocket'
 import type { Ref } from 'vue'
-import { useSettingsStore } from '@/pinia'
-import type { Node } from '@/api/environment'
+import analytic from '@/api/analytic'
 import environment from '@/api/environment'
 import logo from '@/assets/img/logo.png'
 import pulse from '@/assets/svg/pulse.svg?component'
 import { formatDateTime } from '@/lib/helper'
-import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
-import analytic from '@/api/analytic'
+import { useSettingsStore } from '@/pinia'
 import { version } from '@/version.json'
+import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
+import Icon, { LinkOutlined, SendOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
 
 const data = ref([]) as Ref<Node[]>
 

+ 7 - 9
app/src/views/dashboard/ServerAnalytic.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
+import type { CPUInfoStat, DiskStat, HostInfoStat, LoadStat, MemStat } from '@/api/analytic'
+import type { Series } from '@/components/Chart/types'
 import type ReconnectingWebSocket from 'reconnecting-websocket'
+import analytic from '@/api/analytic'
 import AreaChart from '@/components/Chart/AreaChart.vue'
 import RadialBarChart from '@/components/Chart/RadialBarChart.vue'
-import type { CPUInfoStat, DiskStat, HostInfoStat, LoadStat, MemStat } from '@/api/analytic'
-import analytic from '@/api/analytic'
 import { bytesToSize } from '@/lib/helper'
-import type { Series } from '@/components/Chart/types'
 
 let websocket: ReconnectingWebSocket | WebSocket
 
@@ -21,11 +21,9 @@ const cpu = ref('0.0')
 const cpu_info = reactive([]) as CPUInfoStat[]
 const cpu_analytic_series = reactive([{ name: 'User', data: [] }, { name: 'Total', data: [] }]) as Series[]
 
-const net_analytic = reactive([{ name: $gettext('Receive'), data: [] },
-  { name: $gettext('Send'), data: [] }]) as Series[]
+const net_analytic = reactive([{ name: $gettext('Receive'), data: [] }, { name: $gettext('Send'), data: [] }]) as Series[]
 
-const disk_io_analytic = reactive([{ name: $gettext('Writes'), data: [] },
-  { name: $gettext('Reads'), data: [] }]) as Series[]
+const disk_io_analytic = reactive([{ name: $gettext('Writes'), data: [] }, { name: $gettext('Reads'), data: [] }]) as Series[]
 
 const memory = reactive({
   total: '',
@@ -45,11 +43,11 @@ const uptime = ref('')
 const loadavg = reactive({ load1: 0, load5: 0, load15: 0 }) as LoadStat
 const net = reactive({ recv: 0, sent: 0, last_recv: 0, last_sent: 0 })
 
-const net_formatter = (bytes: number) => {
+function net_formatter(bytes: number) {
   return `${bytesToSize(bytes)}/s`
 }
 
-const cpu_formatter = (usage: number) => {
+function cpu_formatter(usage: number) {
   return usage.toFixed(2)
 }
 

+ 2 - 3
app/src/views/dashboard/components/NodeAnalyticItem.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import Icon, { ArrowDownOutlined, ArrowUpOutlined, DatabaseOutlined, LineChartOutlined } from '@ant-design/icons-vue'
 import cpu from '@/assets/svg/cpu.svg?component'
 import memory from '@/assets/svg/memory.svg?component'
-import { bytesToSize } from '@/lib/helper'
 import UsageProgressLine from '@/components/Chart/UsageProgressLine.vue'
+import { bytesToSize } from '@/lib/helper'
+import Icon, { ArrowDownOutlined, ArrowUpOutlined, DatabaseOutlined, LineChartOutlined } from '@ant-design/icons-vue'
 
 defineProps<{
   item: {
@@ -157,5 +157,4 @@ defineProps<{
     }
   }
 }
-
 </style>

+ 5 - 7
app/src/views/environment/BatchUpgrader.vue

@@ -1,12 +1,11 @@
 <script setup lang="ts">
-import _ from 'lodash'
-import { message } from 'ant-design-vue'
-import type { Ref } from 'vue'
-import { marked } from 'marked'
-import { useRoute } from 'vue-router'
 import type { Environment } from '@/api/environment'
+import type { Ref } from 'vue'
 import upgrade, { type RuntimeInfo } from '@/api/upgrade'
 import websocket from '@/lib/websocket'
+import { message } from 'ant-design-vue'
+import _ from 'lodash'
+import { useRoute } from 'vue-router'
 
 const route = useRoute()
 const visible = ref(false)
@@ -80,7 +79,6 @@ const dryRun = computed(() => {
   return !!route.query.dry_run
 })
 
-// eslint-disable-next-line sonarjs/cognitive-complexity
 async function performUpgrade() {
   showLogContainer.value = true
   progressStatus.value = 'active'
@@ -224,7 +222,7 @@ async function performUpgrade() {
               {{ $gettext('Pre-release') }}
             </ATag>
           </h1>
-          <div v-html="marked.parse(data.body)" />
+          <div vue-dompurify-html="marked.parse(data.body)" />
         </div>
 
         <div class="flex justify-end">

+ 3 - 3
app/src/views/environment/Environment.vue

@@ -1,10 +1,10 @@
 <script setup lang="tsx">
-import { message } from 'ant-design-vue'
 import environment from '@/api/environment'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
-import envColumns from '@/views/environment/envColumns'
 import FooterToolBar from '@/components/FooterToolbar'
+import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import BatchUpgrader from '@/views/environment/BatchUpgrader.vue'
+import envColumns from '@/views/environment/envColumns'
+import { message } from 'ant-design-vue'
 
 const curd = ref()
 function loadFromSettings() {

+ 15 - 21
app/src/views/environment/envColumns.tsx

@@ -1,9 +1,9 @@
-import { h } from 'vue'
-import { Badge, Tag } from 'ant-design-vue'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Column, JSXElements } from '@/components/StdDesign/types'
-import { input, switcher } from '@/components/StdDesign/StdDataEntry'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { input, switcher } from '@/components/StdDesign/StdDataEntry'
+import { Badge, Tag } from 'ant-design-vue'
+import { h } from 'vue'
 
 const columns: Column[] = [{
   title: () => $gettext('Name'),
@@ -14,8 +14,7 @@ const columns: Column[] = [{
     type: input,
   },
   search: true,
-},
-{
+}, {
   title: () => $gettext('URL'),
   dataIndex: 'url',
   sorter: true,
@@ -26,13 +25,11 @@ const columns: Column[] = [{
       placeholder: () => 'https://10.0.0.1:9000',
     },
   },
-},
-{
+}, {
   title: () => $gettext('Version'),
   dataIndex: 'version',
   pithy: true,
-},
-{
+}, {
   title: () => 'NodeSecret',
   dataIndex: 'token',
   sorter: true,
@@ -77,21 +74,21 @@ const columns: Column[] = [{
 {
   title: () => $gettext('Status'),
   dataIndex: 'status',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const template: JSXElements = []
     const { text } = args
     if (args.record.enabled) {
       if (text === true || text > 0) {
-        template.push(<Badge status="success"/>)
+        template.push(<Badge status="success" />)
         template.push($gettext('Online'))
       }
       else {
-        template.push(<Badge status="error"/>)
+        template.push(<Badge status="error" />)
         template.push($gettext('Offline'))
       }
     }
     else {
-      template.push(<Badge status="default"/>)
+      template.push(<Badge status="default" />)
       template.push($gettext('Disabled'))
     }
 
@@ -99,11 +96,10 @@ const columns: Column[] = [{
   },
   sorter: true,
   pithy: true,
-},
-{
+}, {
   title: () => $gettext('Enabled'),
   dataIndex: 'enabled',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     const template: JSXElements = []
     const { text } = args
     if (text === true || text > 0)
@@ -119,15 +115,13 @@ const columns: Column[] = [{
   },
   sorter: true,
   pithy: true,
-},
-{
+}, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   customRender: datetime,
   sorter: true,
   pithy: true,
-},
-{
+}, {
   title: () => $gettext('Action'),
   dataIndex: 'action',
 }]

+ 9 - 6
app/src/views/nginx_log/NginxLog.vue

@@ -1,10 +1,10 @@
 <script setup lang="ts">
-import type ReconnectingWebSocket from 'reconnecting-websocket'
-import { debounce } from 'lodash'
-import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import type { INginxLogData } from '@/api/nginx_log'
+import type ReconnectingWebSocket from 'reconnecting-websocket'
 import nginx_log from '@/api/nginx_log'
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import ws from '@/lib/websocket'
+import { debounce } from 'lodash'
 
 const logContainer = ref()
 let websocket: ReconnectingWebSocket | WebSocket
@@ -24,7 +24,9 @@ const control: INginxLogData = reactive({
 })
 
 function logType() {
-  return route.path.indexOf('access') > 0 ? 'access' : route.path.indexOf('error') > 0 ? 'error' : 'site'
+  if (route.path.indexOf('access') > 0)
+    return 'access'
+  return route.path.indexOf('error') > 0 ? 'error' : 'site'
 }
 
 function openWs() {
@@ -162,8 +164,9 @@ const computedBuffer = computed(() => {
         ref="logContainer"
         class="nginx-log-container"
         @scroll="debounce_scroll_log"
-        v-html="computedBuffer"
-      />
+      >
+        {{ computedBuffer }}
+      </pre>
     </ACard>
     <FooterToolBar v-if="control.type === 'site'">
       <AButton @click="router.go(-1)">

+ 2 - 2
app/src/views/notification/Notification.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import notification from '@/api/notification'
+import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import { useUserStore } from '@/pinia'
 import notificationColumns from '@/views/notification/notificationColumns'
+import { message } from 'ant-design-vue'
 
 const { unreadCount } = storeToRefs(useUserStore())
 

+ 25 - 17
app/src/views/notification/notificationColumns.tsx

@@ -1,33 +1,41 @@
-import { Tag } from 'ant-design-vue'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import type { Column } from '@/components/StdDesign/types'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { detailRender } from '@/components/Notification/detailRender'
 import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { NotificationTypeT } from '@/constants'
-import { detailRender } from '@/components/Notification/detailRender'
+import { Tag } from 'ant-design-vue'
 
 const columns: Column[] = [{
   title: () => $gettext('Type'),
   dataIndex: 'type',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     if (args.text === NotificationTypeT.Error) {
-      return <Tag color="error">
-        { $gettext('Error') }
-      </Tag>
+      return (
+        <Tag color="error">
+          { $gettext('Error') }
+        </Tag>
+      )
     }
     else if (args.text === NotificationTypeT.Warning) {
-      return <Tag color="warning">
-      { $gettext('Warning') }
-    </Tag>
+      return (
+        <Tag color="warning">
+          { $gettext('Warning') }
+        </Tag>
+      )
     }
     else if (args.text === NotificationTypeT.Info) {
-      return <Tag color="info">
-      { $gettext('Info')}
-    </Tag>
+      return (
+        <Tag color="info">
+          { $gettext('Info')}
+        </Tag>
+      )
     }
     else if (args.text === NotificationTypeT.Success) {
-      return <Tag color="success">
-      { $gettext('Success') }
-    </Tag>
+      return (
+        <Tag color="success">
+          { $gettext('Success') }
+        </Tag>
+      )
     }
   },
   sorter: true,
@@ -35,7 +43,7 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Title'),
   dataIndex: 'title',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     return h('span', $gettext(args.text))
   },
   pithy: true,

+ 0 - 1
app/src/views/other/Error.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-
 const route = useRoute()
 
 const info = computed(() => {

+ 7 - 9
app/src/views/other/Install.vue

@@ -1,11 +1,11 @@
 <script setup lang="ts">
-import { Form, message } from 'ant-design-vue'
-import { useRouter } from 'vue-router'
-import { DatabaseOutlined, LockOutlined, MailOutlined, UserOutlined } from '@ant-design/icons-vue'
-import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
-
 import install from '@/api/install'
+import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
 import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
+import { DatabaseOutlined, LockOutlined, MailOutlined, UserOutlined } from '@ant-design/icons-vue'
+
+import { Form, message } from 'ant-design-vue'
+import { useRouter } from 'vue-router'
 
 const thisYear = new Date().getFullYear()
 const loading = ref(false)
@@ -47,16 +47,14 @@ const rulesRef = reactive({
   database: [
     {
       message: () =>
-        $gettext('The filename cannot contain the following characters: %{c}',
-          { c: '& &quot; ? < > # {} % ~ / \\' },
-        ),
+        $gettext('The filename cannot contain the following characters: %{c}', { c: '& &quot; ? < > # {} % ~ / \\' }),
     },
   ],
 })
 
 const { validate, validateInfos } = Form.useForm(modelRef, rulesRef)
 
-const onSubmit = () => {
+function onSubmit() {
   validate().then(() => {
     // modelRef
     loading.value = true

+ 8 - 8
app/src/views/other/Login.vue

@@ -1,15 +1,15 @@
 <script setup lang="ts">
-import { KeyOutlined, LockOutlined, UserOutlined } from '@ant-design/icons-vue'
-import { Form, message } from 'ant-design-vue'
-import { startAuthentication } from '@simplewebauthn/browser'
-import { useUserStore } from '@/pinia'
 import auth from '@/api/auth'
 import install from '@/api/install'
+import passkey from '@/api/passkey'
 import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
 import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
 import Authorization from '@/components/TwoFA/Authorization.vue'
 import gettext from '@/gettext'
-import passkey from '@/api/passkey'
+import { useUserStore } from '@/pinia'
+import { KeyOutlined, LockOutlined, UserOutlined } from '@ant-design/icons-vue'
+import { startAuthentication } from '@simplewebauthn/browser'
+import { Form, message } from 'ant-design-vue'
 
 const thisYear = new Date().getFullYear()
 
@@ -53,7 +53,7 @@ const userStore = useUserStore()
 const { login, passkeyLogin } = userStore
 const { secureSessionId } = storeToRefs(userStore)
 
-const onSubmit = () => {
+function onSubmit() {
   validate().then(async () => {
     loading.value = true
 
@@ -121,7 +121,7 @@ auth.get_casdoor_uri()
     message.error($gettext(e.message ?? 'Server error'))
   })
 
-const loginWithCasdoor = () => {
+function loginWithCasdoor() {
   window.location.href = casdoor_uri.value
 }
 
@@ -172,7 +172,7 @@ async function handlePasskeyLogin() {
       await router.push(next)
     }
   }
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   catch (e: any) {
     message.error($gettext(e.message ?? 'Server error'))
   }

+ 6 - 6
app/src/views/preference/AuthSettings.vue

@@ -1,12 +1,12 @@
 <script setup lang="tsx">
-import { message } from 'ant-design-vue'
-import type { Ref } from 'vue'
-import dayjs from 'dayjs'
-import PasskeyRegistration from './components/Passkey.vue'
 import type { BannedIP, Settings } from '@/api/settings'
+import type { CustomRenderProps } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { Ref } from 'vue'
 import setting from '@/api/settings'
-import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import TOTP from '@/views/preference/components/TOTP.vue'
+import { message } from 'ant-design-vue'
+import dayjs from 'dayjs'
+import PasskeyRegistration from './components/Passkey.vue'
 
 const data: Settings = inject('data') as Settings
 
@@ -19,7 +19,7 @@ const bannedIPColumns = [{
 }, {
   title: $gettext('Banned Until'),
   dataIndex: 'expired_at',
-  customRender: (args: customRender) => {
+  customRender: (args: CustomRenderProps) => {
     return dayjs.unix(args.text).format('YYYY-MM-DD HH:mm:ss')
   },
 }, {

+ 2 - 2
app/src/views/preference/CertSettings.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import Draggable from 'vuedraggable'
-import { DeleteOutlined, HolderOutlined } from '@ant-design/icons-vue'
 import type { Settings } from '@/api/settings'
+import { DeleteOutlined, HolderOutlined } from '@ant-design/icons-vue'
+import Draggable from 'vuedraggable'
 
 const data: Settings = inject('data') as Settings
 const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>

+ 9 - 10
app/src/views/preference/Preference.vue

@@ -1,18 +1,18 @@
 <script setup lang="ts">
-import { message } from 'ant-design-vue'
+import type { Settings } from '@/api/settings'
 import type { Ref } from 'vue'
-import { storeToRefs } from 'pinia'
-import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import settings from '@/api/settings'
-import BasicSettings from '@/views/preference/BasicSettings.vue'
-import OpenAISettings from '@/views/preference/OpenAISettings.vue'
-import NginxSettings from '@/views/preference/NginxSettings.vue'
-import type { Settings } from '@/api/settings'
-import LogrotateSettings from '@/views/preference/LogrotateSettings.vue'
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
+import use2FAModal from '@/components/TwoFA/use2FAModal'
 import { useSettingsStore } from '@/pinia'
 import AuthSettings from '@/views/preference/AuthSettings.vue'
-import use2FAModal from '@/components/TwoFA/use2FAModal'
+import BasicSettings from '@/views/preference/BasicSettings.vue'
 import CertSettings from '@/views/preference/CertSettings.vue'
+import LogrotateSettings from '@/views/preference/LogrotateSettings.vue'
+import NginxSettings from '@/views/preference/NginxSettings.vue'
+import OpenAISettings from '@/views/preference/OpenAISettings.vue'
+import { message } from 'ant-design-vue'
+import { storeToRefs } from 'pinia'
 
 const data = ref<Settings>({
   app: {
@@ -135,7 +135,6 @@ onMounted(() => {
   if (route.query?.tab)
     activeKey.value = route.query.tab.toString()
 })
-
 </script>
 
 <template>

+ 3 - 3
app/src/views/preference/components/AddPasskey.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { startRegistration } from '@simplewebauthn/browser'
-import { message } from 'ant-design-vue'
 import passkey from '@/api/passkey'
 import { useUserStore } from '@/pinia'
+import { startRegistration } from '@simplewebauthn/browser'
+import { message } from 'ant-design-vue'
 
 const emit = defineEmits(['created'])
 
@@ -28,7 +28,7 @@ async function registerPasskey() {
 
     user.passkeyRawId = attestationResponse.rawId
   }
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // eslint-disable-next-line ts/no-explicit-any
   catch (e: any) {
     message.error($gettext(e.message ?? 'Server error'))
   }

部分文件因为文件数量过多而无法显示