Browse Source

refactor: use uozi admin-kit

Jacky 2 months ago
parent
commit
517f239e66
78 changed files with 1417 additions and 1115 deletions
  1. 2 0
      app/eslint.config.mjs
  2. 4 3
      app/package.json
  3. 168 245
      app/pnpm-lock.yaml
  4. 1 1
      app/src/api/2fa.ts
  5. 5 12
      app/src/api/acme_user.ts
  6. 1 1
      app/src/api/analytic.ts
  7. 1 1
      app/src/api/auth.ts
  8. 1 1
      app/src/api/auto_cert.ts
  9. 1 1
      app/src/api/backup.ts
  10. 2 2
      app/src/api/cert.ts
  11. 12 30
      app/src/api/config.ts
  12. 7 1
      app/src/api/curd.ts
  13. 4 2
      app/src/api/dns_credential.ts
  14. 11 4
      app/src/api/env_group.ts
  15. 5 12
      app/src/api/environment.ts
  16. 3 7
      app/src/api/external_notify.ts
  17. 1 1
      app/src/api/install.ts
  18. 2 2
      app/src/api/nginx_log.ts
  19. 1 1
      app/src/api/ngx.ts
  20. 1 1
      app/src/api/node.ts
  21. 5 8
      app/src/api/notification.ts
  22. 1 1
      app/src/api/openai.ts
  23. 1 1
      app/src/api/otp.ts
  24. 1 1
      app/src/api/passkey.ts
  25. 1 1
      app/src/api/public.ts
  26. 1 1
      app/src/api/recovery.ts
  27. 1 1
      app/src/api/self_check.ts
  28. 1 1
      app/src/api/settings.ts
  29. 14 42
      app/src/api/site.ts
  30. 9 25
      app/src/api/stream.ts
  31. 9 26
      app/src/api/template.ts
  32. 1 1
      app/src/api/upgrade.ts
  33. 4 2
      app/src/api/user.ts
  34. 2 2
      app/src/components/AutoCertForm/DNSChallenge.vue
  35. 1 1
      app/src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue
  36. 1 1
      app/src/components/Notification/Notification.vue
  37. 2 2
      app/src/components/Notification/detailRender.tsx
  38. 3 3
      app/src/components/StdDesign/StdDataDisplay/StdCurd.vue
  39. 1 1
      app/src/components/StdDesign/StdDataDisplay/StdTable.vue
  40. 2 2
      app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue
  41. 1 1
      app/src/components/StdDesign/StdDetail/StdDetail.vue
  42. 256 171
      app/src/language/ar/app.po
  43. 55 0
      app/src/language/curd.ts
  44. 250 156
      app/src/language/de_DE/app.po
  45. 105 15
      app/src/language/en/app.po
  46. 106 0
      app/src/language/messages.pot
  47. 0 38
      app/src/lib/http/client.ts
  48. 1 6
      app/src/lib/http/index.ts
  49. 12 7
      app/src/lib/http/interceptors.ts
  50. 1 1
      app/src/lib/http/types.ts
  51. 50 9
      app/src/main.ts
  52. 2 0
      app/src/types.d.ts
  53. 1 1
      app/src/version.json
  54. 27 31
      app/src/views/certificate/ACMEUser.vue
  55. 1 1
      app/src/views/certificate/CertificateEditor.vue
  56. 5 4
      app/src/views/certificate/CertificateList/Certificate.vue
  57. 19 21
      app/src/views/certificate/CertificateList/certColumns.tsx
  58. 15 16
      app/src/views/certificate/DNSCredential.vue
  59. 1 1
      app/src/views/certificate/components/ACMEUserSelector.vue
  60. 1 0
      app/src/views/certificate/components/RemoveCert.vue
  61. 1 1
      app/src/views/config/ConfigEditor.vue
  62. 8 8
      app/src/views/config/ConfigList.vue
  63. 12 14
      app/src/views/config/configColumns.tsx
  64. 1 1
      app/src/views/dashboard/Environments.vue
  65. 18 5
      app/src/views/environments/group/EnvGroup.vue
  66. 12 14
      app/src/views/environments/group/columns.ts
  67. 3 3
      app/src/views/environments/list/Environment.vue
  68. 20 21
      app/src/views/environments/list/envColumns.tsx
  69. 25 16
      app/src/views/nginx_log/NginxLogList.vue
  70. 12 11
      app/src/views/notification/notificationColumns.tsx
  71. 1 1
      app/src/views/preference/components/AuthSettings/Passkey.vue
  72. 1 1
      app/src/views/site/site_edit/components/SiteEditor/store.ts
  73. 11 8
      app/src/views/site/site_list/SiteList.vue
  74. 38 30
      app/src/views/site/site_list/columns.tsx
  75. 29 29
      app/src/views/stream/StreamList.vue
  76. 1 1
      app/src/views/stream/store.ts
  77. 2 1
      app/src/views/user/User.vue
  78. 19 20
      app/src/views/user/userColumns.tsx

+ 2 - 0
app/eslint.config.mjs

@@ -51,6 +51,8 @@ export default createConfig(
       'sonarjs/no-nested-template-literals': 'off',
       'sonarjs/no-nested-template-literals': 'off',
       'sonarjs/pseudo-random': 'warn',
       'sonarjs/pseudo-random': 'warn',
       'sonarjs/no-nested-functions': 'off',
       'sonarjs/no-nested-functions': 'off',
+
+      'eslint-comments/no-unlimited-disable': 'off',
     },
     },
   },
   },
 )
 )

+ 4 - 3
app/package.json

@@ -1,8 +1,8 @@
 {
 {
   "name": "nginx-ui-app-next",
   "name": "nginx-ui-app-next",
   "type": "module",
   "type": "module",
-  "version": "2.0.0",
-  "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977",
+  "version": "2.1.0-beta.1",
+  "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
   "scripts": {
   "scripts": {
     "dev": "vite --host",
     "dev": "vite --host",
     "typecheck": "vue-tsc --noEmit",
     "typecheck": "vue-tsc --noEmit",
@@ -10,7 +10,7 @@
     "lint:fix": "eslint --fix .",
     "lint:fix": "eslint --fix .",
     "build": "vite build",
     "build": "vite build",
     "preview": "vite preview",
     "preview": "vite preview",
-    "gettext:extract": "vue-gettext-extract"
+    "gettext:extract": "generate-curd-translations --output src/language/curd.ts && vue-gettext-extract"
   },
   },
   "dependencies": {
   "dependencies": {
     "@0xjacky/vue-github-button": "^3.1.1",
     "@0xjacky/vue-github-button": "^3.1.1",
@@ -18,6 +18,7 @@
     "@formkit/auto-animate": "^0.8.2",
     "@formkit/auto-animate": "^0.8.2",
     "@simplewebauthn/browser": "^13.1.0",
     "@simplewebauthn/browser": "^13.1.0",
     "@uozi-admin/curd": "^4.1.4",
     "@uozi-admin/curd": "^4.1.4",
+    "@uozi-admin/request": "^2.6.0",
     "@vue/reactivity": "^3.5.14",
     "@vue/reactivity": "^3.5.14",
     "@vue/shared": "^3.5.14",
     "@vue/shared": "^3.5.14",
     "@vueuse/components": "^13.2.0",
     "@vueuse/components": "^13.2.0",

File diff suppressed because it is too large
+ 168 - 245
app/pnpm-lock.yaml


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

@@ -1,5 +1,5 @@
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface TwoFAStatus {
 export interface TwoFAStatus {
   enabled: boolean
   enabled: boolean

+ 5 - 12
app/src/api/acme_user.ts

@@ -1,6 +1,5 @@
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface AcmeUser extends ModelBase {
 export interface AcmeUser extends ModelBase {
   name: string
   name: string
@@ -9,16 +8,10 @@ export interface AcmeUser extends ModelBase {
   registration: { body?: { status: string } }
   registration: { body?: { status: string } }
 }
 }
 
 
-class ACMEUserCurd extends Curd<AcmeUser> {
-  constructor() {
-    super('acme_users')
-  }
+const baseUrl = '/acme_users'
 
 
-  public async register(id: number) {
-    return http.post(`${this.baseUrl}/${id}/register`)
-  }
-}
-
-const acme_user = new ACMEUserCurd()
+const acme_user = useCurdApi<AcmeUser>(baseUrl, {
+  register: (id: number) => http.post(`${baseUrl}/${id}/register`),
+})
 
 
 export default acme_user
 export default acme_user

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import ws from '@/lib/websocket'
 import ws from '@/lib/websocket'
 
 
 export interface CPUInfoStat {
 export interface CPUInfoStat {

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

@@ -1,5 +1,5 @@
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import { useUserStore } from '@/pinia'
 import { useUserStore } from '@/pinia'
 
 
 const { login, logout } = useUserStore()
 const { login, logout } = useUserStore()

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export const AutoCertChallengeMethod = {
 export const AutoCertChallengeMethod = {
   http01: 'http01',
   http01: 'http01',

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 /**
 /**
  * Interface for restore backup response
  * Interface for restore backup response

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

@@ -3,7 +3,7 @@ import type { AcmeUser } from '@/api/acme_user'
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
 import type { DnsCredential } from '@/api/dns_credential'
 import type { DnsCredential } from '@/api/dns_credential'
 import type { PrivateKeyType } from '@/constants'
 import type { PrivateKeyType } from '@/constants'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 
 export interface Cert extends ModelBase {
 export interface Cert extends ModelBase {
   name: string
   name: string
@@ -39,6 +39,6 @@ export interface CertificateResult {
   key_type: PrivateKeyType
   key_type: PrivateKeyType
 }
 }
 
 
-const cert: Curd<Cert> = new Curd('/certs')
+const cert = useCurdApi<Cert>('/certs')
 
 
 export default cert
 export default cert

+ 12 - 30
app/src/api/config.ts

@@ -1,7 +1,6 @@
 import type { GetListResponse } from '@/api/curd'
 import type { GetListResponse } from '@/api/curd'
 import type { ChatComplicationMessage } from '@/api/openai'
 import type { ChatComplicationMessage } from '@/api/openai'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface ModelBase {
 export interface ModelBase {
   id: number
   id: number
@@ -26,33 +25,16 @@ export interface ConfigBackup extends ModelBase {
   content: string
   content: string
 }
 }
 
 
-class ConfigCurd extends Curd<Config> {
-  constructor() {
-    super('/configs')
-  }
-
-  get_base_path() {
-    return http.get('/config_base_path')
-  }
-
-  mkdir(basePath: string, name: string) {
-    return http.post('/config_mkdir', { base_path: basePath, folder_name: name })
-  }
-
-  rename(basePath: string, origName: string, newName: string, syncNodeIds?: number[]) {
-    return http.post('/config_rename', {
-      base_path: basePath,
-      orig_name: origName,
-      new_name: newName,
-      sync_node_ids: syncNodeIds,
-    })
-  }
-
-  get_history(filepath: string) {
-    return http.get<GetListResponse<ConfigBackup>>('/config_histories', { params: { filepath } })
-  }
-}
-
-const config: ConfigCurd = new ConfigCurd()
+const config = useCurdApi<Config>('/configs', {
+  get_base_path: () => http.get('/config_base_path'),
+  mkdir: (basePath: string, name: string) => http.post('/config_mkdir', { base_path: basePath, folder_name: name }),
+  rename: (basePath: string, origName: string, newName: string, syncNodeIds?: number[]) => http.post('/config_rename', {
+    base_path: basePath,
+    orig_name: origName,
+    new_name: newName,
+    sync_node_ids: syncNodeIds,
+  }),
+  get_history: (filepath: string) => http.get<GetListResponse<ConfigBackup>>('/config_histories', { params: { filepath } }),
+})
 
 
 export default config
 export default config

+ 7 - 1
app/src/api/curd.ts

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface ModelBase {
 export interface ModelBase {
   id: number
   id: number
@@ -18,6 +18,12 @@ export interface GetListResponse<T> {
   pagination?: Pagination
   pagination?: Pagination
 }
 }
 
 
+export interface UpdateOrderRequest {
+  target_id: number
+  direction: number
+  affected_ids: number[]
+}
+
 class Curd<T> {
 class Curd<T> {
   protected readonly baseUrl: string
   protected readonly baseUrl: string
 
 

+ 4 - 2
app/src/api/dns_credential.ts

@@ -1,6 +1,6 @@
 import type { DNSProvider } from '@/api/auto_cert'
 import type { DNSProvider } from '@/api/auto_cert'
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 
 export interface DnsCredential extends ModelBase {
 export interface DnsCredential extends ModelBase {
   name: string
   name: string
@@ -17,6 +17,8 @@ export interface DnsCredential extends ModelBase {
   }
   }
 }
 }
 
 
-const dns_credential: Curd<DnsCredential> = new Curd('/dns_credentials')
+const baseUrl = '/dns_credentials'
+
+const dns_credential = useCurdApi<DnsCredential>(baseUrl)
 
 
 export default dns_credential
 export default dns_credential

+ 11 - 4
app/src/api/env_group.ts

@@ -1,6 +1,5 @@
-import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
-
+import type { ModelBase, UpdateOrderRequest } from '@/api/curd'
+import { http, useCurdApi } from '@uozi-admin/request'
 // Post-sync action types
 // Post-sync action types
 export const PostSyncAction = {
 export const PostSyncAction = {
   None: 'none',
   None: 'none',
@@ -13,4 +12,12 @@ export interface EnvGroup extends ModelBase {
   post_sync_action?: string
   post_sync_action?: string
 }
 }
 
 
-export default new Curd<EnvGroup>('/env_groups')
+const baseUrl = '/env_groups'
+
+const env_group = useCurdApi<EnvGroup>(baseUrl, {
+  updateOrder(data: UpdateOrderRequest) {
+    return http.post('/env_groups/order', data)
+  },
+})
+
+export default env_group

+ 5 - 12
app/src/api/environment.ts

@@ -1,6 +1,5 @@
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface Environment extends ModelBase {
 export interface Environment extends ModelBase {
   name: string
   name: string
@@ -16,16 +15,10 @@ export interface Node {
   response_at?: Date
   response_at?: Date
 }
 }
 
 
-class EnvironmentCurd extends Curd<Environment> {
-  constructor() {
-    super('/environments')
-  }
+const baseUrl = '/environments'
 
 
-  load_from_settings() {
-    return http.post(`${this.baseUrl}/load_from_settings`)
-  }
-}
-
-const environment: EnvironmentCurd = new EnvironmentCurd()
+const environment = useCurdApi<Environment>(baseUrl, {
+  load_from_settings: () => http.post(`${baseUrl}/load_from_settings`),
+})
 
 
 export default environment
 export default environment

+ 3 - 7
app/src/api/external_notify.ts

@@ -1,17 +1,13 @@
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 
 export interface ExternalNotify extends ModelBase {
 export interface ExternalNotify extends ModelBase {
   type: string
   type: string
   config: Record<string, string>
   config: Record<string, string>
 }
 }
 
 
-class ExternalNotifyCurd extends Curd<ExternalNotify> {
-  constructor() {
-    super('/external_notifies')
-  }
-}
+const baseUrl = '/external_notifies'
 
 
-const externalNotify: ExternalNotifyCurd = new ExternalNotifyCurd()
+const externalNotify = useCurdApi<ExternalNotify>(baseUrl)
 
 
 export default externalNotify
 export default externalNotify

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface InstallRequest {
 export interface InstallRequest {
   email: string
   email: string

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface INginxLogData {
 export interface INginxLogData {
   type: string
   type: string
@@ -10,7 +10,7 @@ const nginx_log = {
     return http.post(`/nginx_log?page=${page}`, data)
     return http.post(`/nginx_log?page=${page}`, data)
   },
   },
 
 
-  get_list(params: {
+  getList(params: {
     type?: string
     type?: string
     name?: string
     name?: string
     path?: string
     path?: string

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface NgxConfig {
 export interface NgxConfig {
   file_name?: string
   file_name?: string

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 function reloadNginx(nodeIds: number[]) {
 function reloadNginx(nodeIds: number[]) {
   return http.post('/environments/reload_nginx', { node_ids: nodeIds })
   return http.post('/environments/reload_nginx', { node_ids: nodeIds })

+ 5 - 8
app/src/api/notification.ts

@@ -1,6 +1,5 @@
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface Notification extends ModelBase {
 export interface Notification extends ModelBase {
   type: string
   type: string
@@ -8,12 +7,10 @@ export interface Notification extends ModelBase {
   details: string
   details: string
 }
 }
 
 
-class NotificationCurd extends Curd<Notification> {
-  public clear() {
-    return http.delete(this.baseUrl)
-  }
-}
+const baseUrl = '/notifications'
 
 
-const notification = new NotificationCurd('/notifications')
+const notification = useCurdApi<Notification>(baseUrl, {
+  clear: () => http.delete(baseUrl),
+})
 
 
 export default notification
 export default notification

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import ws from '@/lib/websocket'
 import ws from '@/lib/websocket'
 
 
 export interface ChatComplicationMessage {
 export interface ChatComplicationMessage {

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

@@ -1,5 +1,5 @@
 import type { RecoveryCodesResponse } from '@/api/recovery'
 import type { RecoveryCodesResponse } from '@/api/recovery'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface OTPGenerateSecretResponse {
 export interface OTPGenerateSecretResponse {
   secret: string
   secret: string

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

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

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface ICP {
 export interface ICP {
   icp_number: string
   icp_number: string

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface RecoveryCode {
 export interface RecoveryCode {
   code: string
   code: string

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

@@ -1,6 +1,6 @@
 import type { Container } from '@/language'
 import type { Container } from '@/language'
 import type { CosyError } from '@/lib/http'
 import type { CosyError } from '@/lib/http'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import ws from '@/lib/websocket'
 import ws from '@/lib/websocket'
 
 
 export const ReportStatus = {
 export const ReportStatus = {

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface AppSettings {
 export interface AppSettings {
   page_size: number
   page_size: number

+ 14 - 42
app/src/api/site.ts

@@ -4,8 +4,7 @@ import type { EnvGroup } from '@/api/env_group'
 import type { NgxConfig } from '@/api/ngx'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
 import type { ChatComplicationMessage } from '@/api/openai'
 import type { ConfigStatus, PrivateKeyType } from '@/constants'
 import type { ConfigStatus, PrivateKeyType } from '@/constants'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export type SiteStatus = ConfigStatus.Enabled | ConfigStatus.Disabled | ConfigStatus.Maintenance
 export type SiteStatus = ConfigStatus.Enabled | ConfigStatus.Disabled | ConfigStatus.Maintenance
 
 
@@ -34,45 +33,18 @@ export interface AutoCertRequest {
   key_type: PrivateKeyType
   key_type: PrivateKeyType
 }
 }
 
 
-class SiteCurd extends Curd<Site> {
-  // eslint-disable-next-line ts/no-explicit-any
-  enable(name: string, config?: any) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/enable`, undefined, config)
-  }
-
-  disable(name: string) {
-    return http.post(`${this.baseUrl}/${name}/disable`)
-  }
-
-  rename(oldName: string, newName: string) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(oldName)}/rename`, { new_name: newName })
-  }
-
-  get_default_template() {
-    return http.get('default_site_template')
-  }
-
-  add_auto_cert(domain: string, data: AutoCertRequest) {
-    return http.post(`auto_cert/${encodeURIComponent(domain)}`, data)
-  }
-
-  remove_auto_cert(domain: string) {
-    return http.delete(`auto_cert/${encodeURIComponent(domain)}`)
-  }
-
-  duplicate(name: string, data: { name: string }): Promise<{ dst: string }> {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/duplicate`, data)
-  }
-
-  advance_mode(name: string, data: { advanced: boolean }) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/advance`, data)
-  }
-
-  enableMaintenance(name: string) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/maintenance`)
-  }
-}
-
-const site = new SiteCurd('/sites')
+const baseUrl = '/sites'
+
+const site = useCurdApi<Site>(baseUrl, {
+  enable: (name: string) => http.post(`${baseUrl}/${encodeURIComponent(name)}/enable`),
+  disable: (name: string) => http.post(`${baseUrl}/${name}/disable`),
+  rename: (oldName: string, newName: string) => http.post(`${baseUrl}/${encodeURIComponent(oldName)}/rename`, { new_name: newName }),
+  get_default_template: () => http.get('default_site_template'),
+  add_auto_cert: (domain: string, data: AutoCertRequest) => http.post(`auto_cert/${encodeURIComponent(domain)}`, data),
+  remove_auto_cert: (domain: string) => http.delete(`auto_cert/${encodeURIComponent(domain)}`),
+  duplicate: (name: string, data: { name: string }) => http.post(`${baseUrl}/${encodeURIComponent(name)}/duplicate`, data),
+  advance_mode: (name: string, data: { advanced: boolean }) => http.post(`${baseUrl}/${encodeURIComponent(name)}/advance`, data),
+  enableMaintenance: (name: string) => http.post(`${baseUrl}/${encodeURIComponent(name)}/maintenance`),
+})
 
 
 export default site
 export default site

+ 9 - 25
app/src/api/stream.ts

@@ -1,8 +1,7 @@
 import type { EnvGroup } from './env_group'
 import type { EnvGroup } from './env_group'
 import type { NgxConfig } from '@/api/ngx'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
 import type { ChatComplicationMessage } from '@/api/openai'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface Stream {
 export interface Stream {
   modified_at: string
   modified_at: string
@@ -18,29 +17,14 @@ export interface Stream {
   sync_node_ids: number[]
   sync_node_ids: number[]
 }
 }
 
 
-class StreamCurd extends Curd<Stream> {
-  // eslint-disable-next-line ts/no-explicit-any
-  enable(name: string, config?: any) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/enable`, undefined, config)
-  }
+const baseUrl = '/streams'
 
 
-  disable(name: string) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/disable`)
-  }
-
-  duplicate(name: string, data: { name: string }): Promise<{ dst: string }> {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/duplicate`, data)
-  }
-
-  advance_mode(name: string, data: { advanced: boolean }) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/advance`, data)
-  }
-
-  rename(name: string, newName: string) {
-    return http.post(`${this.baseUrl}/${encodeURIComponent(name)}/rename`, { new_name: newName })
-  }
-}
-
-const stream = new StreamCurd('/streams')
+const stream = useCurdApi<Stream>(baseUrl, {
+  enable: (name: string) => http.post(`${baseUrl}/${encodeURIComponent(name)}/enable`),
+  disable: (name: string) => http.post(`${baseUrl}/${encodeURIComponent(name)}/disable`),
+  duplicate: (name: string, data: { name: string }) => http.post(`${baseUrl}/${encodeURIComponent(name)}/duplicate`, data),
+  advance_mode: (name: string, data: { advanced: boolean }) => http.post(`${baseUrl}/${encodeURIComponent(name)}/advance`, data),
+  rename: (name: string, newName: string) => http.post(`${baseUrl}/${encodeURIComponent(name)}/rename`, { new_name: newName }),
+})
 
 
 export default stream
 export default stream

+ 9 - 26
app/src/api/template.ts

@@ -1,6 +1,5 @@
 import type { NgxDirective, NgxLocation, NgxServer } from '@/api/ngx'
 import type { NgxDirective, NgxLocation, NgxServer } from '@/api/ngx'
-import Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface Variable {
 export interface Variable {
   type?: string
   type?: string
@@ -21,30 +20,14 @@ export interface Template extends NgxServer {
   directives?: NgxDirective[]
   directives?: NgxDirective[]
 }
 }
 
 
-class TemplateApi extends Curd<Template> {
-  get_config_list() {
-    return http.get('templates/configs')
-  }
+const baseUrl = '/templates'
 
 
-  get_block_list() {
-    return http.get<{
-      data: Template[]
-    }>('templates/blocks')
-  }
-
-  get_config(name: string) {
-    return http.get(`templates/config/${name}`)
-  }
-
-  get_block(name: string) {
-    return http.get<Template>(`templates/block/${name}`)
-  }
-
-  build_block(name: string, data: Variable) {
-    return http.post(`templates/block/${name}`, data)
-  }
-}
-
-const template = new TemplateApi('/templates')
+const template = useCurdApi<Template>(baseUrl, {
+  get_config_list: () => http.get(`${baseUrl}/configs`),
+  get_block_list: () => http.get(`${baseUrl}/blocks`),
+  get_config: (name: string) => http.get(`${baseUrl}/config/${name}`),
+  get_block: (name: string) => http.get(`${baseUrl}/block/${name}`),
+  build_block: (name: string, data: Variable) => http.post(`${baseUrl}/block/${name}`, data),
+})
 
 
 export default template
 export default template

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

@@ -1,4 +1,4 @@
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 
 export interface RuntimeInfo {
 export interface RuntimeInfo {
   name: string
   name: string

+ 4 - 2
app/src/api/user.ts

@@ -1,11 +1,13 @@
 import type { ModelBase } from '@/api/curd'
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 
 export interface User extends ModelBase {
 export interface User extends ModelBase {
   name: string
   name: string
   password: string
   password: string
+  enabled_2fa: boolean
+  status: boolean
 }
 }
 
 
-const user: Curd<User> = new Curd('users')
+const user = useCurdApi<User>('/users')
 
 
 export default user
 export default user

+ 2 - 2
app/src/components/AutoCertForm/DNSChallenge.vue

@@ -42,7 +42,7 @@ watch(current, () => {
   if (mounted.value)
   if (mounted.value)
     data.value.dns_credential_id = undefined
     data.value.dns_credential_id = undefined
 
 
-  dns_credential.get_list({ provider: data.value.provider }).then(r => {
+  dns_credential.getList({ provider: data.value.provider }).then(r => {
     r.data.forEach(v => {
     r.data.forEach(v => {
       credentials.value?.push({
       credentials.value?.push({
         value: v.id,
         value: v.id,
@@ -60,7 +60,7 @@ onMounted(async () => {
   })
   })
 
 
   if (data.value.dns_credential_id) {
   if (data.value.dns_credential_id) {
-    await dns_credential.get(data.value.dns_credential_id).then(r => {
+    await dns_credential.getItem(data.value.dns_credential_id).then(r => {
       data.value.code = r.code
       data.value.code = r.code
       data.value.provider = r.provider
       data.value.provider = r.provider
       providerIdx.value = providers.value.findIndex(v => v.code === r.code)
       providerIdx.value = providers.value.findIndex(v => v.code === r.code)

+ 1 - 1
app/src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue

@@ -27,7 +27,7 @@ const content = ref('')
 
 
 function init() {
 function init() {
   if (directive.value.directive === Include) {
   if (directive.value.directive === Include) {
-    config.get(directive.value.params).then(r => {
+    config.getItem(directive.value.params).then(r => {
       content.value = r.content
       content.value = r.content
     })
     })
   }
   }

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

@@ -46,7 +46,7 @@ connect({
 
 
 function init() {
 function init() {
   loading.value = true
   loading.value = true
-  notificationApi.get_list({ sort: 'desc', order_by: 'created_at' }).then(r => {
+  notificationApi.getList({ sort: 'desc', order_by: 'created_at' }).then(r => {
     data.value = r.data
     data.value = r.data
     unreadCount.value = r.pagination?.total || 0
     unreadCount.value = r.pagination?.total || 0
   }).finally(() => {
   }).finally(() => {

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

@@ -1,8 +1,8 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { CustomRenderArgs } from '@uozi-admin/curd'
 import { NotificationTypeT } from '@/constants'
 import { NotificationTypeT } from '@/constants'
 import notifications from './notifications'
 import notifications from './notifications'
 
 
-export function detailRender(args: CustomRender) {
+export function detailRender(args: CustomRenderArgs) {
   try {
   try {
     return (
     return (
       <div>
       <div>

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

@@ -66,7 +66,7 @@ const inTrash = ref(false)
 const getParams = reactive(props.getParams ?? {})
 const getParams = reactive(props.getParams ?? {})
 
 
 function get_list() {
 function get_list() {
-  table.value?.get_list()
+  table.value?.getList()
 }
 }
 
 
 defineExpose({
 defineExpose({
@@ -137,7 +137,7 @@ function view(id: number | string) {
 
 
 async function get(id: number | string) {
 async function get(id: number | string) {
   return props
   return props
-    .api!.get(id, { ...props.overwriteParams }).then(async r => {
+    .api!.getItem(id, { ...props.overwriteParams }).then(async r => {
     Object.keys(data).forEach(k => {
     Object.keys(data).forEach(k => {
       delete data[k]
       delete data[k]
     })
     })
@@ -160,7 +160,7 @@ async function handleClickBatchEdit(batchColumns: Column[]) {
 }
 }
 
 
 function handleBatchUpdated() {
 function handleBatchUpdated() {
-  table.value?.get_list()
+  table.value?.getList()
   table.value?.resetSelection()
   table.value?.resetSelection()
 }
 }
 </script>
 </script>

+ 1 - 1
app/src/components/StdDesign/StdDataDisplay/StdTable.vue

@@ -212,7 +212,7 @@ async function _get_list() {
   loading.value = true
   loading.value = true
 
 
   // eslint-disable-next-line ts/no-explicit-any
   // eslint-disable-next-line ts/no-explicit-any
-  await props.api?.get_list({ ...params.value, ...paginationParams.value }).then(async (r: GetListResponse<any>) => {
+  await props.api?.getList({ ...params.value, ...paginationParams.value }).then(async (r: GetListResponse<any>) => {
     dataSource.value = r.data
     dataSource.value = r.data
     rowsKeyIndexMap.value = {}
     rowsKeyIndexMap.value = {}
     if (props.sortable)
     if (props.sortable)

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

@@ -70,7 +70,7 @@ async function _init() {
     // M_values.value = [props.value]
     // M_values.value = [props.value]
     // not init value, we need to fetch them from api
     // not init value, we need to fetch them from api
     if (!props.value && selectedKey.value && selectedKey.value !== '0') {
     if (!props.value && selectedKey.value && selectedKey.value !== '0') {
-      api.get(selectedKey.value, props.getParams).then(r => {
+      api.getItem(selectedKey.value, props.getParams).then(r => {
         M_values.value = [r]
         M_values.value = [r]
         records.value = [r]
         records.value = [r]
       })
       })
@@ -80,7 +80,7 @@ async function _init() {
     // M_values.value = props.value || []
     // M_values.value = props.value || []
     // not init value, we need to fetch them from api
     // not init value, we need to fetch them from api
     if (!props.value && (selectedKey.value?.length || 0) > 0) {
     if (!props.value && (selectedKey.value?.length || 0) > 0) {
-      api.get_list({
+      api.getList({
         ...props.getParams,
         ...props.getParams,
         id: selectedKey.value,
         id: selectedKey.value,
       }).then(r => {
       }).then(r => {

+ 1 - 1
app/src/components/StdDesign/StdDetail/StdDetail.vue

@@ -60,7 +60,7 @@ onMounted(() => {
     return
     return
   }
   }
 
 
-  props.api.get(route.params.id).then(res => {
+  props.api.getItem(route.params.id).then(res => {
     detail.value = res
     detail.value = res
   })
   })
 })
 })

File diff suppressed because it is too large
+ 256 - 171
app/src/language/ar/app.po


+ 55 - 0
app/src/language/curd.ts

@@ -0,0 +1,55 @@
+/* eslint-disable */
+// This file is auto-generated. Do not edit manually.
+
+export const translations = {
+  total: $gettext('Total'),
+  'item(s)': $gettext('item(s)'),
+  view: $gettext('View'),
+  edit: $gettext('Edit'),
+  delete: $gettext('Delete'),
+  restore: $gettext('Restore'),
+  deletePermanently: $gettext('Delete Permanently'),
+  search: $gettext('Search'),
+  reset: $gettext('Reset'),
+  close: $gettext('Close'),
+  ok: $gettext('OK'),
+  selectorTitle: $gettext('Selector'),
+  generate: $gettext('Generate'),
+  save: $gettext('Save'),
+  add: $gettext('Add'),
+  trash: $gettext('Trash'),
+  backToList: $gettext('Back to List'),
+  exportExcel: $gettext('Export Excel'),
+  list: $gettext('List'),
+  formValidateError: $gettext('Please fill all fields correctly'),
+  deleteConfirm: $gettext('Are you sure you want to delete?'),
+  restoreConfirm: $gettext('Are you sure you want to restore?'),
+  deletePermanentlyConfirm: $gettext('Are you sure you want to delete permanently?'),
+  savedSuccessfully: $gettext('Saved successfully'),
+  deletedSuccessfully: $gettext('Deleted successfully'),
+  restoredSuccessfully: $gettext('Restored successfully'),
+  selectAll: $gettext('Select all'),
+  validate: {
+    required: $gettext('This field should not be empty'),
+    email: $gettext('This field should be a valid email address'),
+    db_unique: $gettext('This value is already taken'),
+    hostname: $gettext('This field should be a valid hostname'),
+    safety_text: $gettext('This field should only contain letters, unicode characters, numbers, and -_./:')
+  },
+  upload: {
+    uploadFiles: $gettext('Upload Files'),
+    uploadFolders: $gettext('Upload Folders'),
+    clickOrDragFilesToThisAreaToUpload: $gettext('Click or drag files to this area to upload'),
+    clickOrDragFoldersToThisAreaToUpload: $gettext('Click or drag folders to this area to upload'),
+    supportForSingleOrBatchUploadOfFiles: $gettext('Support single or batch upload of files'),
+    supportForUploadingEntireFolders: $gettext('Support uploading entire folders'),
+    mainTipsForFiles: $gettext('Click or drag files to this area to upload'),
+    mainTipsForFolders: $gettext('Click or drag folders to this area to upload'),
+    subTipsForFiles: $gettext('Support single or batch upload of files'),
+    subTipsForFolders: $gettext('Support uploading entire folders'),
+    selectedFiles: $gettext('Selected {count} files'),
+    processing: $gettext('Processing {count}/{total}'),
+    path: $gettext('Path'),
+    size: $gettext('Size')
+  }
+}

File diff suppressed because it is too large
+ 250 - 156
app/src/language/de_DE/app.po


+ 105 - 15
app/src/language/en/app.po

@@ -141,7 +141,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:155
 #: src/components/NgxConfigEditor/NgxUpstream.vue:155
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:151
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:151
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:186
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:186
-#: src/views/preference/tabs/CertSettings.vue:45
+#: src/language/curd.ts:19 src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/stream/StreamList.vue:180
 #: src/views/stream/StreamList.vue:180
 msgid "Add"
 msgid "Add"
@@ -266,6 +266,10 @@ msgstr ""
 msgid "Are you sure you want to clear the record of chat?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:27
+msgid "Are you sure you want to delete permanently?"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:540
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:540
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr ""
 msgstr ""
@@ -274,7 +278,7 @@ msgstr ""
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr ""
 msgstr ""
 
 
-#: src/views/site/site_list/SiteList.vue:110
+#: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:110
 #: src/views/stream/StreamList.vue:231
 #: src/views/stream/StreamList.vue:231
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
 msgstr ""
 msgstr ""
@@ -303,6 +307,10 @@ msgstr ""
 msgid "Are you sure you want to restart Nginx on the following sync nodes?"
 msgid "Are you sure you want to restart Nginx on the following sync nodes?"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:26
+msgid "Are you sure you want to restore?"
+msgstr ""
+
 #: src/components/ChatGPT/ChatGPT.vue:318
 #: src/components/ChatGPT/ChatGPT.vue:318
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr ""
 msgstr ""
@@ -386,6 +394,10 @@ msgstr ""
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:21
+msgid "Back to List"
+msgstr ""
+
 #: src/routes/modules/system.ts:26
 #: src/routes/modules/system.ts:26
 msgid "Backup"
 msgid "Backup"
 msgstr ""
 msgstr ""
@@ -774,6 +786,14 @@ msgstr ""
 msgid "Click or drag backup file to this area to upload"
 msgid "Click or drag backup file to this area to upload"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:42 src/language/curd.ts:46
+msgid "Click or drag files to this area to upload"
+msgstr ""
+
+#: src/language/curd.ts:43 src/language/curd.ts:47
+msgid "Click or drag folders to this area to upload"
+msgstr ""
+
 #: src/views/preference/components/AuthSettings/TOTP.vue:110
 #: src/views/preference/components/AuthSettings/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
@@ -798,7 +818,7 @@ msgstr ""
 msgid "Client request header buffer size"
 msgid "Client request header buffer size"
 msgstr ""
 msgstr ""
 
 
-#: src/components/ConfigHistory/ConfigHistory.vue:169
+#: src/components/ConfigHistory/ConfigHistory.vue:169 src/language/curd.ts:14
 msgid "Close"
 msgid "Close"
 msgstr ""
 msgstr ""
 
 
@@ -1056,7 +1076,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:21
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:21
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:519
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:519
-#: src/views/certificate/components/RemoveCert.vue:87
+#: src/language/curd.ts:9 src/views/certificate/components/RemoveCert.vue:87
 #: src/views/site/site_list/SiteList.vue:119
 #: src/views/site/site_list/SiteList.vue:119
 #: src/views/stream/StreamList.vue:240
 #: src/views/stream/StreamList.vue:240
 msgid "Delete"
 msgid "Delete"
@@ -1068,6 +1088,7 @@ msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:35
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:35
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:547
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:547
+#: src/language/curd.ts:11
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -1112,6 +1133,7 @@ msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:183
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:183
+#: src/language/curd.ts:29
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1373,7 +1395,7 @@ msgstr ""
 msgid "Dynamic"
 msgid "Dynamic"
 msgstr ""
 msgstr ""
 
 
-#: src/components/StdDesign/StdDetail/StdDetail.vue:110
+#: src/components/StdDesign/StdDetail/StdDetail.vue:110 src/language/curd.ts:8
 msgid "Edit"
 msgid "Edit"
 msgstr ""
 msgstr ""
 
 
@@ -1578,6 +1600,10 @@ msgstr ""
 msgid "Export"
 msgid "Export"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:22
+msgid "Export Excel"
+msgstr ""
+
 #: src/views/preference/tabs/NginxSettings.vue:49
 #: src/views/preference/tabs/NginxSettings.vue:49
 msgid "External Docker Container"
 msgid "External Docker Container"
 msgstr ""
 msgstr ""
@@ -1947,6 +1973,7 @@ msgid "General Certificate"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:55
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:55
+#: src/language/curd.ts:17
 msgid "Generate"
 msgid "Generate"
 msgstr ""
 msgstr ""
 
 
@@ -2230,6 +2257,10 @@ msgstr ""
 msgid "Issuer: %{issuer}"
 msgid "Issuer: %{issuer}"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:6
+msgid "item(s)"
+msgstr ""
+
 #: src/views/preference/tabs/AppSettings.vue:11
 #: src/views/preference/tabs/AppSettings.vue:11
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr ""
 msgstr ""
@@ -2312,6 +2343,7 @@ msgid "Link Start"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:173
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:173
+#: src/language/curd.ts:23
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -3006,7 +3038,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:36
 #: src/components/NgxConfigEditor/NgxUpstream.vue:36
 #: src/components/Notification/Notification.vue:111
 #: src/components/Notification/Notification.vue:111
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:95
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:95
-#: src/views/notification/Notification.vue:38
+#: src/language/curd.ts:15 src/views/notification/Notification.vue:38
 #: src/views/site/components/SiteStatusSegmented.vue:96
 #: src/views/site/components/SiteStatusSegmented.vue:96
 #: src/views/site/site_edit/components/Cert/IssueCert.vue:39
 #: src/views/site/site_edit/components/Cert/IssueCert.vue:39
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:142
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:142
@@ -3131,7 +3163,7 @@ msgstr ""
 
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
-#: src/views/config/ConfigEditor.vue:316
+#: src/language/curd.ts:52 src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:38
 #: src/views/nginx_log/NginxLogList.vue:38
 msgid "Path"
 msgid "Path"
 msgstr ""
 msgstr ""
@@ -3201,6 +3233,10 @@ msgstr ""
 msgid "Please enter the security token received during backup"
 msgid "Please enter the security token received during backup"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:24
+msgid "Please fill all fields correctly"
+msgstr ""
+
 #: src/views/certificate/DNSCredential.vue:53
 #: src/views/certificate/DNSCredential.vue:53
 msgid ""
 msgid ""
 "Please fill in the API authentication credentials provided by your DNS "
 "Please fill in the API authentication credentials provided by your DNS "
@@ -3328,6 +3364,10 @@ msgstr ""
 msgid "Process information"
 msgid "Process information"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:51
+msgid "Processing {count}/{total}"
+msgstr ""
+
 #: src/language/constants.ts:3
 #: src/language/constants.ts:3
 msgid "Prohibit changing root password in demo"
 msgid "Prohibit changing root password in demo"
 msgstr ""
 msgstr ""
@@ -3610,6 +3650,7 @@ msgid "Requests Per Connection"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:442
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:442
+#: src/language/curd.ts:13
 msgid "Reset"
 msgid "Reset"
 msgstr ""
 msgstr ""
 
 
@@ -3663,6 +3704,10 @@ msgstr ""
 msgid "Restarting"
 msgid "Restarting"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:10
+msgid "Restore"
+msgstr ""
+
 #: src/components/SystemRestore/SystemRestoreContent.vue:135
 #: src/components/SystemRestore/SystemRestoreContent.vue:135
 msgid "Restore completed successfully"
 msgid "Restore completed successfully"
 msgstr ""
 msgstr ""
@@ -3686,6 +3731,10 @@ msgstr ""
 msgid "Restore this version"
 msgid "Restore this version"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:30
+msgid "Restored successfully"
+msgstr ""
+
 #: src/views/certificate/components/RemoveCert.vue:26
 #: src/views/certificate/components/RemoveCert.vue:26
 #: src/views/certificate/components/RemoveCert.vue:95
 #: src/views/certificate/components/RemoveCert.vue:95
 msgid "Revoke"
 msgid "Revoke"
@@ -3728,7 +3777,7 @@ msgstr ""
 #: src/components/ChatGPT/ChatGPT.vue:355
 #: src/components/ChatGPT/ChatGPT.vue:355
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:129
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:64
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:64
-#: src/components/StdDesign/StdDetail/StdDetail.vue:93
+#: src/components/StdDesign/StdDetail/StdDetail.vue:93 src/language/curd.ts:18
 #: src/views/certificate/CertificateEditor.vue:266
 #: src/views/certificate/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
 #: src/views/config/components/ConfigName.vue:59
 #: src/views/config/ConfigEditor.vue:275
 #: src/views/config/ConfigEditor.vue:275
@@ -3792,7 +3841,8 @@ msgid "Save successfully"
 msgstr ""
 msgstr ""
 
 
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:43
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:43
-#: src/views/config/ConfigEditor.vue:194 src/views/site/site_add/SiteAdd.vue:29
+#: src/language/curd.ts:28 src/views/config/ConfigEditor.vue:194
+#: src/views/site/site_add/SiteAdd.vue:29
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:40
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:40
 msgid "Saved successfully"
 msgid "Saved successfully"
 msgstr ""
 msgstr ""
@@ -3809,7 +3859,7 @@ msgstr ""
 msgid "SDK"
 msgid "SDK"
 msgstr ""
 msgstr ""
 
 
-#: src/language/constants.ts:62
+#: src/language/constants.ts:62 src/language/curd.ts:12
 msgid "Search"
 msgid "Search"
 msgstr ""
 msgstr ""
 
 
@@ -3830,11 +3880,20 @@ msgstr ""
 msgid "Security Token Information"
 msgid "Security Token Information"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:31
+msgid "Select all"
+msgstr ""
+
 #: src/views/environments/group/EnvGroup.vue:29
 #: src/views/environments/group/EnvGroup.vue:29
 msgid "Select an action after sync"
 msgid "Select an action after sync"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:50
+msgid "Selected {count} files"
+msgstr ""
+
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:189
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:189
+#: src/language/curd.ts:16
 msgid "Selector"
 msgid "Selector"
 msgstr ""
 msgstr ""
 
 
@@ -3970,6 +4029,10 @@ msgstr ""
 msgid "Sites-enabled directory not exist"
 msgid "Sites-enabled directory not exist"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:53
+msgid "Size"
+msgstr ""
+
 #: src/views/preference/tabs/NodeSettings.vue:23
 #: src/views/preference/tabs/NodeSettings.vue:23
 msgid "Skip Installation"
 msgid "Skip Installation"
 msgstr ""
 msgstr ""
@@ -4109,6 +4172,14 @@ msgid ""
 "guide/nginx-proxy-example.html"
 "guide/nginx-proxy-example.html"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:44 src/language/curd.ts:48
+msgid "Support single or batch upload of files"
+msgstr ""
+
+#: src/language/curd.ts:45 src/language/curd.ts:49
+msgid "Support uploading entire folders"
+msgstr ""
+
 #: src/components/SystemRestore/SystemRestoreContent.vue:197
 #: src/components/SystemRestore/SystemRestoreContent.vue:197
 #: src/components/SystemRestore/SystemRestoreContent.vue:274
 #: src/components/SystemRestore/SystemRestoreContent.vue:274
 msgid "Supported file type: .zip"
 msgid "Supported file type: .zip"
@@ -4351,16 +4422,16 @@ msgstr ""
 msgid "This field is required"
 msgid "This field is required"
 msgstr ""
 msgstr ""
 
 
-#: src/constants/form_errors.ts:3
+#: src/constants/form_errors.ts:3 src/language/curd.ts:34
 msgid "This field should be a valid email address"
 msgid "This field should be a valid email address"
 msgstr ""
 msgstr ""
 
 
-#: src/constants/form_errors.ts:5
+#: src/constants/form_errors.ts:5 src/language/curd.ts:36
 msgid "This field should be a valid hostname"
 msgid "This field should be a valid hostname"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataEntry/StdFormItem.vue:39
 #: src/components/StdDesign/StdDataEntry/StdFormItem.vue:39
-#: src/constants/form_errors.ts:2
+#: src/constants/form_errors.ts:2 src/language/curd.ts:33
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr ""
 msgstr ""
 
 
@@ -4369,6 +4440,12 @@ msgid ""
 "This field should only contain letters, unicode characters, numbers, and -_."
 "This field should only contain letters, unicode characters, numbers, and -_."
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:37
+msgid ""
+"This field should only contain letters, unicode characters, numbers, and -"
+"_./:"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:150
 #: src/views/dashboard/NginxDashBoard.vue:150
 msgid ""
 msgid ""
 "This module provides Nginx request statistics, connection count, etc. data. "
 "This module provides Nginx request statistics, connection count, etc. data. "
@@ -4387,7 +4464,7 @@ msgid ""
 "make sure to save it in a secure location."
 "make sure to save it in a secure location."
 msgstr ""
 msgstr ""
 
 
-#: src/constants/form_errors.ts:4
+#: src/constants/form_errors.ts:4 src/language/curd.ts:35
 msgid "This value is already taken"
 msgid "This value is already taken"
 msgstr ""
 msgstr ""
 
 
@@ -4471,6 +4548,10 @@ msgstr ""
 msgid "Token is not valid"
 msgid "Token is not valid"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:5
+msgid "Total"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdPagination.vue:40
 #: src/components/StdDesign/StdDataDisplay/StdPagination.vue:40
 msgid "Total %{total} item"
 msgid "Total %{total} item"
 msgid_plural "Total %{total} items"
 msgid_plural "Total %{total} items"
@@ -4515,6 +4596,7 @@ msgid ""
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:197
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:197
+#: src/language/curd.ts:20
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -4574,6 +4656,14 @@ msgstr ""
 msgid "Upgrading Nginx UI, please wait..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:40
+msgid "Upload Files"
+msgstr ""
+
+#: src/language/curd.ts:41
+msgid "Upload Folders"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:173
 #: src/components/NgxConfigEditor/NgxUpstream.vue:173
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr ""
 msgstr ""
@@ -4646,7 +4736,7 @@ msgid "Version"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
-#: src/views/nginx_log/NginxLogList.vue:74
+#: src/language/curd.ts:7 src/views/nginx_log/NginxLogList.vue:74
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:83
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:83
 msgid "View"
 msgid "View"
 msgstr ""
 msgstr ""

+ 106 - 0
app/src/language/messages.pot

@@ -146,6 +146,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:155
 #: src/components/NgxConfigEditor/NgxUpstream.vue:155
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:151
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:151
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:186
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:186
+#: src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/stream/StreamList.vue:180
 #: src/views/stream/StreamList.vue:180
@@ -273,6 +274,10 @@ msgstr ""
 msgid "Are you sure you want to clear the record of chat?"
 msgid "Are you sure you want to clear the record of chat?"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:27
+msgid "Are you sure you want to delete permanently?"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:540
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:540
 msgid "Are you sure you want to delete this item permanently?"
 msgid "Are you sure you want to delete this item permanently?"
 msgstr ""
 msgstr ""
@@ -281,6 +286,7 @@ msgstr ""
 msgid "Are you sure you want to delete this item?"
 msgid "Are you sure you want to delete this item?"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:25
 #: src/views/site/site_list/SiteList.vue:110
 #: src/views/site/site_list/SiteList.vue:110
 #: src/views/stream/StreamList.vue:231
 #: src/views/stream/StreamList.vue:231
 msgid "Are you sure you want to delete?"
 msgid "Are you sure you want to delete?"
@@ -310,6 +316,10 @@ msgstr ""
 msgid "Are you sure you want to restart Nginx on the following sync nodes?"
 msgid "Are you sure you want to restart Nginx on the following sync nodes?"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:26
+msgid "Are you sure you want to restore?"
+msgstr ""
+
 #: src/components/ChatGPT/ChatGPT.vue:318
 #: src/components/ChatGPT/ChatGPT.vue:318
 msgid "Ask ChatGPT for Help"
 msgid "Ask ChatGPT for Help"
 msgstr ""
 msgstr ""
@@ -395,6 +405,10 @@ msgstr ""
 msgid "Back to list"
 msgid "Back to list"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:21
+msgid "Back to List"
+msgstr ""
+
 #: src/routes/modules/system.ts:26
 #: src/routes/modules/system.ts:26
 msgid "Backup"
 msgid "Backup"
 msgstr ""
 msgstr ""
@@ -749,6 +763,16 @@ msgstr ""
 msgid "Click or drag backup file to this area to upload"
 msgid "Click or drag backup file to this area to upload"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:42
+#: src/language/curd.ts:46
+msgid "Click or drag files to this area to upload"
+msgstr ""
+
+#: src/language/curd.ts:43
+#: src/language/curd.ts:47
+msgid "Click or drag folders to this area to upload"
+msgstr ""
+
 #: src/views/preference/components/AuthSettings/TOTP.vue:110
 #: src/views/preference/components/AuthSettings/TOTP.vue:110
 msgid "Click to copy"
 msgid "Click to copy"
 msgstr ""
 msgstr ""
@@ -774,6 +798,7 @@ msgid "Client request header buffer size"
 msgstr ""
 msgstr ""
 
 
 #: src/components/ConfigHistory/ConfigHistory.vue:169
 #: src/components/ConfigHistory/ConfigHistory.vue:169
+#: src/language/curd.ts:14
 msgid "Close"
 msgid "Close"
 msgstr ""
 msgstr ""
 
 
@@ -1030,6 +1055,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:21
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:21
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:519
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:519
+#: src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:87
 #: src/views/certificate/components/RemoveCert.vue:87
 #: src/views/site/site_list/SiteList.vue:119
 #: src/views/site/site_list/SiteList.vue:119
 #: src/views/stream/StreamList.vue:240
 #: src/views/stream/StreamList.vue:240
@@ -1042,6 +1068,7 @@ msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:35
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:35
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:547
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:547
+#: src/language/curd.ts:11
 msgid "Delete Permanently"
 msgid "Delete Permanently"
 msgstr ""
 msgstr ""
 
 
@@ -1088,6 +1115,7 @@ msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:183
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:183
+#: src/language/curd.ts:29
 msgid "Deleted successfully"
 msgid "Deleted successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1352,6 +1380,7 @@ msgid "Dynamic"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDetail/StdDetail.vue:110
 #: src/components/StdDesign/StdDetail/StdDetail.vue:110
+#: src/language/curd.ts:8
 msgid "Edit"
 msgid "Edit"
 msgstr ""
 msgstr ""
 
 
@@ -1561,6 +1590,10 @@ msgstr ""
 msgid "Export"
 msgid "Export"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:22
+msgid "Export Excel"
+msgstr ""
+
 #: src/views/preference/tabs/NginxSettings.vue:49
 #: src/views/preference/tabs/NginxSettings.vue:49
 msgid "External Docker Container"
 msgid "External Docker Container"
 msgstr ""
 msgstr ""
@@ -1929,6 +1962,7 @@ msgid "General Certificate"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:55
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:55
+#: src/language/curd.ts:17
 msgid "Generate"
 msgid "Generate"
 msgstr ""
 msgstr ""
 
 
@@ -2203,6 +2237,10 @@ msgstr ""
 msgid "Issuer: %{issuer}"
 msgid "Issuer: %{issuer}"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:6
+msgid "item(s)"
+msgstr ""
+
 #: src/views/preference/tabs/AppSettings.vue:11
 #: src/views/preference/tabs/AppSettings.vue:11
 msgid "Jwt Secret"
 msgid "Jwt Secret"
 msgstr ""
 msgstr ""
@@ -2283,6 +2321,7 @@ msgid "Link Start"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:173
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:173
+#: src/language/curd.ts:23
 msgid "List"
 msgid "List"
 msgstr ""
 msgstr ""
 
 
@@ -2974,6 +3013,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:36
 #: src/components/NgxConfigEditor/NgxUpstream.vue:36
 #: src/components/Notification/Notification.vue:111
 #: src/components/Notification/Notification.vue:111
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:95
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:95
+#: src/language/curd.ts:15
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/site/components/SiteStatusSegmented.vue:96
 #: src/views/site/components/SiteStatusSegmented.vue:96
 #: src/views/site/site_edit/components/Cert/IssueCert.vue:39
 #: src/views/site/site_edit/components/Cert/IssueCert.vue:39
@@ -3098,6 +3138,7 @@ msgstr ""
 
 
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:110
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
 #: src/components/NgxConfigEditor/LocationEditor.vue:138
+#: src/language/curd.ts:52
 #: src/views/config/ConfigEditor.vue:316
 #: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:38
 #: src/views/nginx_log/NginxLogList.vue:38
 msgid "Path"
 msgid "Path"
@@ -3165,6 +3206,10 @@ msgstr ""
 msgid "Please enter the security token received during backup"
 msgid "Please enter the security token received during backup"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:24
+msgid "Please fill all fields correctly"
+msgstr ""
+
 #: src/views/certificate/DNSCredential.vue:53
 #: src/views/certificate/DNSCredential.vue:53
 msgid "Please fill in the API authentication credentials provided by your DNS provider."
 msgid "Please fill in the API authentication credentials provided by your DNS provider."
 msgstr ""
 msgstr ""
@@ -3285,6 +3330,10 @@ msgstr ""
 msgid "Process information"
 msgid "Process information"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:51
+msgid "Processing {count}/{total}"
+msgstr ""
+
 #: src/language/constants.ts:3
 #: src/language/constants.ts:3
 msgid "Prohibit changing root password in demo"
 msgid "Prohibit changing root password in demo"
 msgstr ""
 msgstr ""
@@ -3567,6 +3616,7 @@ msgid "Requests Per Connection"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:442
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:442
+#: src/language/curd.ts:13
 msgid "Reset"
 msgid "Reset"
 msgstr ""
 msgstr ""
 
 
@@ -3617,6 +3667,10 @@ msgstr ""
 msgid "Restarting"
 msgid "Restarting"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:10
+msgid "Restore"
+msgstr ""
+
 #: src/components/SystemRestore/SystemRestoreContent.vue:135
 #: src/components/SystemRestore/SystemRestoreContent.vue:135
 msgid "Restore completed successfully"
 msgid "Restore completed successfully"
 msgstr ""
 msgstr ""
@@ -3640,6 +3694,10 @@ msgstr ""
 msgid "Restore this version"
 msgid "Restore this version"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:30
+msgid "Restored successfully"
+msgstr ""
+
 #: src/views/certificate/components/RemoveCert.vue:26
 #: src/views/certificate/components/RemoveCert.vue:26
 #: src/views/certificate/components/RemoveCert.vue:95
 #: src/views/certificate/components/RemoveCert.vue:95
 msgid "Revoke"
 msgid "Revoke"
@@ -3681,6 +3739,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:129
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:64
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:64
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
+#: src/language/curd.ts:18
 #: src/views/certificate/CertificateEditor.vue:266
 #: src/views/certificate/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
 #: src/views/config/components/ConfigName.vue:59
 #: src/views/config/ConfigEditor.vue:275
 #: src/views/config/ConfigEditor.vue:275
@@ -3744,6 +3803,7 @@ msgid "Save successfully"
 msgstr ""
 msgstr ""
 
 
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:43
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:43
+#: src/language/curd.ts:28
 #: src/views/config/ConfigEditor.vue:194
 #: src/views/config/ConfigEditor.vue:194
 #: src/views/site/site_add/SiteAdd.vue:29
 #: src/views/site/site_add/SiteAdd.vue:29
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:40
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:40
@@ -3763,6 +3823,7 @@ msgid "SDK"
 msgstr ""
 msgstr ""
 
 
 #: src/language/constants.ts:62
 #: src/language/constants.ts:62
+#: src/language/curd.ts:12
 msgid "Search"
 msgid "Search"
 msgstr ""
 msgstr ""
 
 
@@ -3783,11 +3844,20 @@ msgstr ""
 msgid "Security Token Information"
 msgid "Security Token Information"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:31
+msgid "Select all"
+msgstr ""
+
 #: src/views/environments/group/EnvGroup.vue:29
 #: src/views/environments/group/EnvGroup.vue:29
 msgid "Select an action after sync"
 msgid "Select an action after sync"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:50
+msgid "Selected {count} files"
+msgstr ""
+
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:189
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:189
+#: src/language/curd.ts:16
 msgid "Selector"
 msgid "Selector"
 msgstr ""
 msgstr ""
 
 
@@ -3919,6 +3989,10 @@ msgstr ""
 msgid "Sites-enabled directory not exist"
 msgid "Sites-enabled directory not exist"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:53
+msgid "Size"
+msgstr ""
+
 #: src/views/preference/tabs/NodeSettings.vue:23
 #: src/views/preference/tabs/NodeSettings.vue:23
 msgid "Skip Installation"
 msgid "Skip Installation"
 msgstr ""
 msgstr ""
@@ -4053,6 +4127,16 @@ msgstr ""
 msgid "Support communication with the backend through the WebSocket protocol. If your Nginx UI is being used via an Nginx reverse proxy, please refer to this link to write the corresponding configuration file: https://nginxui.com/guide/nginx-proxy-example.html"
 msgid "Support communication with the backend through the WebSocket protocol. If your Nginx UI is being used via an Nginx reverse proxy, please refer to this link to write the corresponding configuration file: https://nginxui.com/guide/nginx-proxy-example.html"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:44
+#: src/language/curd.ts:48
+msgid "Support single or batch upload of files"
+msgstr ""
+
+#: src/language/curd.ts:45
+#: src/language/curd.ts:49
+msgid "Support uploading entire folders"
+msgstr ""
+
 #: src/components/SystemRestore/SystemRestoreContent.vue:197
 #: src/components/SystemRestore/SystemRestoreContent.vue:197
 #: src/components/SystemRestore/SystemRestoreContent.vue:274
 #: src/components/SystemRestore/SystemRestoreContent.vue:274
 msgid "Supported file type: .zip"
 msgid "Supported file type: .zip"
@@ -4280,15 +4364,18 @@ msgid "This field is required"
 msgstr ""
 msgstr ""
 
 
 #: src/constants/form_errors.ts:3
 #: src/constants/form_errors.ts:3
+#: src/language/curd.ts:34
 msgid "This field should be a valid email address"
 msgid "This field should be a valid email address"
 msgstr ""
 msgstr ""
 
 
 #: src/constants/form_errors.ts:5
 #: src/constants/form_errors.ts:5
+#: src/language/curd.ts:36
 msgid "This field should be a valid hostname"
 msgid "This field should be a valid hostname"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataEntry/StdFormItem.vue:39
 #: src/components/StdDesign/StdDataEntry/StdFormItem.vue:39
 #: src/constants/form_errors.ts:2
 #: src/constants/form_errors.ts:2
+#: src/language/curd.ts:33
 msgid "This field should not be empty"
 msgid "This field should not be empty"
 msgstr ""
 msgstr ""
 
 
@@ -4296,6 +4383,10 @@ msgstr ""
 msgid "This field should only contain letters, unicode characters, numbers, and -_."
 msgid "This field should only contain letters, unicode characters, numbers, and -_."
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:37
+msgid "This field should only contain letters, unicode characters, numbers, and -_./:"
+msgstr ""
+
 #: src/views/dashboard/NginxDashBoard.vue:150
 #: src/views/dashboard/NginxDashBoard.vue:150
 msgid "This module provides Nginx request statistics, connection count, etc. data. After enabling it, you can view performance statistics"
 msgid "This module provides Nginx request statistics, connection count, etc. data. After enabling it, you can view performance statistics"
 msgstr ""
 msgstr ""
@@ -4309,6 +4400,7 @@ msgid "This token will only be shown once and cannot be retrieved later. Please
 msgstr ""
 msgstr ""
 
 
 #: src/constants/form_errors.ts:4
 #: src/constants/form_errors.ts:4
+#: src/language/curd.ts:35
 msgid "This value is already taken"
 msgid "This value is already taken"
 msgstr ""
 msgstr ""
 
 
@@ -4373,6 +4465,10 @@ msgstr ""
 msgid "Token is not valid"
 msgid "Token is not valid"
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:5
+msgid "Total"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdPagination.vue:40
 #: src/components/StdDesign/StdDataDisplay/StdPagination.vue:40
 msgid "Total %{total} item"
 msgid "Total %{total} item"
 msgid_plural "Total %{total} items"
 msgid_plural "Total %{total} items"
@@ -4415,6 +4511,7 @@ msgid "TOTP is a two-factor authentication method that uses a time-based one-tim
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:197
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:197
+#: src/language/curd.ts:20
 msgid "Trash"
 msgid "Trash"
 msgstr ""
 msgstr ""
 
 
@@ -4477,6 +4574,14 @@ msgstr ""
 msgid "Upgrading Nginx UI, please wait..."
 msgid "Upgrading Nginx UI, please wait..."
 msgstr ""
 msgstr ""
 
 
+#: src/language/curd.ts:40
+msgid "Upload Files"
+msgstr ""
+
+#: src/language/curd.ts:41
+msgid "Upload Folders"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:173
 #: src/components/NgxConfigEditor/NgxUpstream.vue:173
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr ""
 msgstr ""
@@ -4550,6 +4655,7 @@ msgid "Version"
 msgstr ""
 msgstr ""
 
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/language/curd.ts:7
 #: src/views/nginx_log/NginxLogList.vue:74
 #: src/views/nginx_log/NginxLogList.vue:74
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:83
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:83
 msgid "View"
 msgid "View"

+ 0 - 38
app/src/lib/http/client.ts

@@ -1,38 +0,0 @@
-import type { HttpConfig } from './types'
-import axios from 'axios'
-
-const instance = axios.create({
-  baseURL: import.meta.env.VITE_API_ROOT,
-  timeout: 50000,
-  headers: { 'Content-Type': 'application/json' },
-})
-
-const http = {
-  // eslint-disable-next-line ts/no-explicit-any
-  get<T = any>(url: string, config: HttpConfig = {}) {
-    // eslint-disable-next-line ts/no-explicit-any
-    return instance.get<any, T>(url, config)
-  },
-  // eslint-disable-next-line ts/no-explicit-any
-  post<T = any>(url: string, data: any = undefined, config: HttpConfig = {}) {
-    // eslint-disable-next-line ts/no-explicit-any
-    return instance.post<any, T>(url, data, config)
-  },
-  // eslint-disable-next-line ts/no-explicit-any
-  put<T = any>(url: string, data: any = undefined, config: HttpConfig = {}) {
-    // eslint-disable-next-line ts/no-explicit-any
-    return instance.put<any, T>(url, data, config)
-  },
-  // eslint-disable-next-line ts/no-explicit-any
-  delete<T = any>(url: string, config: HttpConfig = {}) {
-    // eslint-disable-next-line ts/no-explicit-any
-    return instance.delete<any, T>(url, config)
-  },
-  // eslint-disable-next-line ts/no-explicit-any
-  patch<T = any>(url: string, config: HttpConfig = {}) {
-    // eslint-disable-next-line ts/no-explicit-any
-    return instance.patch<any, T>(url, config)
-  },
-}
-
-export { http, instance }

+ 1 - 6
app/src/lib/http/index.ts

@@ -1,18 +1,13 @@
 import type { CosyError, CosyErrorRecord, HttpConfig } from './types'
 import type { CosyError, CosyErrorRecord, HttpConfig } from './types'
-import { http } from './client'
 import { registerError, useMessageDedupe } from './error'
 import { registerError, useMessageDedupe } from './error'
-import { setupInterceptors } from './interceptors'
-
-// Initialize interceptors
-setupInterceptors()
 
 
 // Export everything needed from this module
 // Export everything needed from this module
-export default http
 export type {
 export type {
   CosyError,
   CosyError,
   CosyErrorRecord,
   CosyErrorRecord,
   HttpConfig,
   HttpConfig,
 }
 }
+
 export {
 export {
   registerError,
   registerError,
   useMessageDedupe,
   useMessageDedupe,

+ 12 - 7
app/src/lib/http/interceptors.ts

@@ -1,17 +1,15 @@
 import type { CosyError } from './types'
 import type { CosyError } from './types'
+import { http, useAxios } from '@uozi-admin/request'
 import JSEncrypt from 'jsencrypt'
 import JSEncrypt from 'jsencrypt'
 import { storeToRefs } from 'pinia'
 import { storeToRefs } from 'pinia'
 import use2FAModal from '@/components/TwoFA/use2FAModal'
 import use2FAModal from '@/components/TwoFA/use2FAModal'
 import { useNProgress } from '@/lib/nprogress/nprogress'
 import { useNProgress } from '@/lib/nprogress/nprogress'
 import { useSettingsStore, useUserStore } from '@/pinia'
 import { useSettingsStore, useUserStore } from '@/pinia'
 import router from '@/routes'
 import router from '@/routes'
-import { http, instance } from './client'
 import { handleApiError, useMessageDedupe } from './error'
 import { handleApiError, useMessageDedupe } from './error'
 
 
-// Setup stores and refs
-const user = useUserStore()
-const settings = useSettingsStore()
-const { token, secureSessionId } = storeToRefs(user)
+const { setRequestInterceptor, setResponseInterceptor } = useAxios()
+
 const nprogress = useNProgress()
 const nprogress = useNProgress()
 const dedupe = useMessageDedupe()
 const dedupe = useMessageDedupe()
 
 
@@ -68,7 +66,11 @@ async function handleEncryptedFormData(formData: FormData): Promise<FormData> {
 
 
 // Setup request interceptor
 // Setup request interceptor
 export function setupRequestInterceptor() {
 export function setupRequestInterceptor() {
-  instance.interceptors.request.use(
+  // Setup stores and refs
+  const user = useUserStore()
+  const settings = useSettingsStore()
+  const { token, secureSessionId } = storeToRefs(user)
+  setRequestInterceptor(
     async config => {
     async config => {
       nprogress.start()
       nprogress.start()
       if (token.value) {
       if (token.value) {
@@ -106,7 +108,7 @@ export function setupRequestInterceptor() {
 
 
 // Setup response interceptor
 // Setup response interceptor
 export function setupResponseInterceptor() {
 export function setupResponseInterceptor() {
-  instance.interceptors.response.use(
+  setResponseInterceptor(
     response => {
     response => {
       nprogress.done()
       nprogress.done()
 
 
@@ -118,6 +120,9 @@ export function setupResponseInterceptor() {
     },
     },
 
 
     async error => {
     async error => {
+      // Setup stores and refs
+      const user = useUserStore()
+      const { secureSessionId } = storeToRefs(user)
       nprogress.done()
       nprogress.done()
       const otpModal = use2FAModal()
       const otpModal = use2FAModal()
 
 

+ 1 - 1
app/src/lib/http/types.ts

@@ -18,7 +18,7 @@ export interface HttpConfig extends AxiosRequestConfig {
 
 
 // Extend InternalAxiosRequestConfig type
 // Extend InternalAxiosRequestConfig type
 declare module 'axios' {
 declare module 'axios' {
-  interface InternalAxiosRequestConfig {
+  interface AxiosRequestConfig {
     returnFullResponse?: boolean
     returnFullResponse?: boolean
     crypto?: boolean
     crypto?: boolean
   }
   }

+ 50 - 9
app/src/main.ts

@@ -1,30 +1,71 @@
 import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
 import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
+import { createCurdConfig } from '@uozi-admin/curd'
 import { createPinia } from 'pinia'
 import { createPinia } from 'pinia'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 import { createApp } from 'vue'
 import { createApp } from 'vue'
 import VueDOMPurifyHTML from 'vue-dompurify-html'
 import VueDOMPurifyHTML from 'vue-dompurify-html'
+import { setupInterceptors } from '@/lib/http/interceptors'
 import { useSettingsStore } from '@/pinia'
 import { useSettingsStore } from '@/pinia'
+import i18n from '../i18n.json'
 import App from './App.vue'
 import App from './App.vue'
 import gettext from './gettext'
 import gettext from './gettext'
 import router from './routes'
 import router from './routes'
+import '@uozi-admin/curd/dist/index.css'
 import 'virtual:uno.css'
 import 'virtual:uno.css'
 
 
 const pinia = createPinia()
 const pinia = createPinia()
 
 
 const app = createApp(App)
 const app = createApp(App)
 
 
+function setupTranslations() {
+  return Object.keys(i18n).reduce((acc, cur) => {
+    acc[cur] = gettext.translations[cur]
+    return acc
+  }, {})
+}
+
 pinia.use(piniaPluginPersistedstate)
 pinia.use(piniaPluginPersistedstate)
+
 app.use(pinia)
 app.use(pinia)
-app.use(gettext)
-app.use(VueDOMPurifyHTML, {
-  hooks: {
-    uponSanitizeElement: (node, data) => {
-      if (node.tagName && node.tagName.toLowerCase() === 'think') {
-        data.allowedTags.think = true
-      }
+  .use(gettext)
+  .use(VueDOMPurifyHTML, {
+    hooks: {
+      uponSanitizeElement: (node, data) => {
+        if (node.tagName && node.tagName.toLowerCase() === 'think') {
+          data.allowedTags.think = true
+        }
+      },
+    },
+  })
+  .use(setupInterceptors)
+  .use(createCurdConfig({
+    listApi: {
+      paginationMap: {
+        params: {
+          current: 'page',
+          pageSize: 'page_size',
+        },
+        response: {
+          total: 'total',
+          current: 'current_page',
+          pageSize: 'per_page',
+          totalPages: 'total_pages',
+        },
+      },
+    },
+    i18n: {
+      legacy: false,
+      locale: 'zh-CN',
+      fallbackLocale: 'en-US',
+      messages: setupTranslations(),
+    },
+    time: {
+      timestamp: false,
+    },
+    selector: {
+      omitZeroString: true,
     },
     },
-  },
-})
+  }))
 
 
 // after pinia created
 // after pinia created
 const settings = useSettingsStore()
 const settings = useSettingsStore()

+ 2 - 0
app/src/types.d.ts

@@ -3,3 +3,5 @@ export type CheckedType = boolean | string | number
 interface Window {
 interface Window {
   inWorkspace?: boolean
   inWorkspace?: boolean
 }
 }
+
+export type JSXElements = JSX.Element[]

+ 1 - 1
app/src/version.json

@@ -1 +1 @@
-{"version":"2.0.0","build_id":1,"total_build":421}
+{"version":"2.1.0-beta.1","build_id":1,"total_build":421}

+ 27 - 31
app/src/views/certificate/ACMEUser.vue

@@ -1,34 +1,33 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
 import type { AcmeUser } from '@/api/acme_user'
 import type { AcmeUser } from '@/api/acme_user'
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column } from '@/components/StdDesign/types'
+import { datetimeRender, StdCurd } from '@uozi-admin/curd'
 import { message, Tag } from 'ant-design-vue'
 import { message, Tag } from 'ant-design-vue'
+
 import acme_user from '@/api/acme_user'
 import acme_user from '@/api/acme_user'
-import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input, switcher } from '@/components/StdDesign/StdDataEntry'
 
 
-const columns: Column[] = [
+const columns: StdTableColumn[] = [
   {
   {
     title: () => $gettext('Name'),
     title: () => $gettext('Name'),
     dataIndex: 'name',
     dataIndex: 'name',
     sorter: true,
     sorter: true,
-    pithy: true,
+    pure: true,
     edit: {
     edit: {
-      type: input,
-      config: {
+      type: 'input',
+      formItem: {
         required: true,
         required: true,
       },
       },
     },
     },
+    search: true,
   },
   },
   {
   {
     title: () => $gettext('Email'),
     title: () => $gettext('Email'),
     dataIndex: 'email',
     dataIndex: 'email',
     sorter: true,
     sorter: true,
-    pithy: true,
+    pure: true,
     edit: {
     edit: {
-      type: input,
-      config: {
+      type: 'input',
+      formItem: {
         required: true,
         required: true,
       },
       },
     },
     },
@@ -37,13 +36,11 @@ const columns: Column[] = [
     title: () => $gettext('CA Dir'),
     title: () => $gettext('CA Dir'),
     dataIndex: 'ca_dir',
     dataIndex: 'ca_dir',
     sorter: true,
     sorter: true,
-    pithy: true,
+    pure: true,
     edit: {
     edit: {
-      type: input,
-      config: {
-        placeholder() {
-          return $gettext('If left blank, the default CA Dir will be used.')
-        },
+      type: 'input',
+      input: {
+        placeholder: () => $gettext('If left blank, the default CA Dir will be used.'),
       },
       },
     },
     },
   },
   },
@@ -52,26 +49,24 @@ const columns: Column[] = [
     dataIndex: 'proxy',
     dataIndex: 'proxy',
     hiddenInTable: true,
     hiddenInTable: true,
     edit: {
     edit: {
-      type: input,
+      type: 'input',
       hint: $gettext('Register a user or use this account to issue a certificate through an HTTP proxy.'),
       hint: $gettext('Register a user or use this account to issue a certificate through an HTTP proxy.'),
-      config: {
-        placeholder() {
-          return $gettext('Leave blank if you don\'t need this.')
-        },
+      input: {
+        placeholder: $gettext('Leave blank if you don\'t need this.'),
       },
       },
     },
     },
   },
   },
   {
   {
     title: () => $gettext('Status'),
     title: () => $gettext('Status'),
     dataIndex: ['registration', 'body', 'status'],
     dataIndex: ['registration', 'body', 'status'],
-    customRender: (args: CustomRender) => {
-      if (args.text === 'valid')
+    customRender: ({ text }: CustomRenderArgs) => {
+      if (text === 'valid')
         return <Tag color="green">{$gettext('Valid')}</Tag>
         return <Tag color="green">{$gettext('Valid')}</Tag>
 
 
       return <Tag color="red">{$gettext('Invalid')}</Tag>
       return <Tag color="red">{$gettext('Invalid')}</Tag>
     },
     },
     sorter: true,
     sorter: true,
-    pithy: true,
+    pure: true,
   },
   },
   {
   {
     title: () => $gettext('Register On Startup'),
     title: () => $gettext('Register On Startup'),
@@ -79,7 +74,7 @@ const columns: Column[] = [
     hiddenInTable: true,
     hiddenInTable: true,
     hiddenInDetail: true,
     hiddenInDetail: true,
     edit: {
     edit: {
-      type: switcher,
+      type: 'switch',
       hint: $gettext('When Enabled, Nginx UI will automatically re-register users upon startup. '
       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.'),
     },
     },
@@ -87,13 +82,13 @@ const columns: Column[] = [
   {
   {
     title: () => $gettext('Updated at'),
     title: () => $gettext('Updated at'),
     dataIndex: 'updated_at',
     dataIndex: 'updated_at',
-    customRender: datetime,
+    customRender: datetimeRender,
     sorter: true,
     sorter: true,
-    pithy: true,
+    pure: true,
   },
   },
   {
   {
-    title: () => $gettext('Action'),
-    dataIndex: 'action',
+    title: () => $gettext('Actions'),
+    dataIndex: 'actions',
     fixed: 'right',
     fixed: 'right',
   },
   },
 ]
 ]
@@ -112,6 +107,7 @@ function register(id: number, data: AcmeUser) {
   <StdCurd
   <StdCurd
     :title="$gettext('ACME User')"
     :title="$gettext('ACME User')"
     :columns="columns"
     :columns="columns"
+    disable-export
     :api="acme_user"
     :api="acme_user"
   >
   >
     <template #edit="{ data }: {data: AcmeUser}">
     <template #edit="{ data }: {data: AcmeUser}">

+ 1 - 1
app/src/views/certificate/CertificateEditor.vue

@@ -25,7 +25,7 @@ const notShowInAutoCert = computed(() => {
 
 
 function init() {
 function init() {
   if (id.value > 0) {
   if (id.value > 0) {
-    cert.get(id.value).then(r => {
+    cert.getItem(id.value).then(r => {
       data.value = r
       data.value = r
     })
     })
   }
   }

+ 5 - 4
app/src/views/certificate/CertificateList/Certificate.vue

@@ -1,7 +1,7 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
 import { CloudUploadOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
 import { CloudUploadOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
+import { StdTable } from '@uozi-admin/curd'
 import cert from '@/api/cert'
 import cert from '@/api/cert'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { useGlobalStore } from '@/pinia'
 import { useGlobalStore } from '@/pinia'
 import RemoveCert from '../components/RemoveCert.vue'
 import RemoveCert from '../components/RemoveCert.vue'
 import WildcardCertificate from '../components/WildcardCertificate.vue'
 import WildcardCertificate from '../components/WildcardCertificate.vue'
@@ -41,15 +41,16 @@ const { processingStatus } = storeToRefs(globalStore)
       ref="refTable"
       ref="refTable"
       :api="cert"
       :api="cert"
       :columns="certColumns"
       :columns="certColumns"
+      :get-list-api="cert.getList"
       disable-view
       disable-view
       :scroll-x="1000"
       :scroll-x="1000"
       disable-delete
       disable-delete
-      @click-edit="id => $router.push(`/certificates/${id}`)"
+      @edit-item="record => $router.push(`/certificates/${record.id}`)"
     >
     >
-      <template #actions="{ record }">
+      <template #afterActions="{ record }">
         <RemoveCert
         <RemoveCert
           :id="record.id"
           :id="record.id"
-          @removed="() => refTable.get_list()"
+          @removed="() => refTable.refresh()"
         />
         />
       </template>
       </template>
     </StdTable>
     </StdTable>

+ 19 - 21
app/src/views/certificate/CertificateList/certColumns.tsx

@@ -1,17 +1,16 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import type { JSXElements } from '@/types'
+import { datetimeRender, maskRender } from '@uozi-admin/curd'
 import { Badge, Tag } from 'ant-design-vue'
 import { Badge, Tag } from 'ant-design-vue'
 import dayjs from 'dayjs'
 import dayjs from 'dayjs'
-import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input } from '@/components/StdDesign/StdDataEntry'
 import { PrivateKeyTypeMask } from '@/constants'
 import { PrivateKeyTypeMask } from '@/constants'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
-  customRender: (args: CustomRender) => {
+  pure: true,
+  customRender: (args: CustomRenderArgs) => {
     const { text, record } = args
     const { text, record } = args
     if (!text)
     if (!text)
       return h('div', record.domain)
       return h('div', record.domain)
@@ -19,14 +18,13 @@ const columns: Column[] = [{
     return h('div', text)
     return h('div', text)
   },
   },
   search: {
   search: {
-    type: input,
+    type: 'input',
   },
   },
 }, {
 }, {
   title: () => $gettext('Type'),
   title: () => $gettext('Type'),
   dataIndex: 'auto_cert',
   dataIndex: 'auto_cert',
-  customRender: (args: CustomRender) => {
+  customRender: ({ text }: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
-    const { text } = args
     const sync = $gettext('Sync Certificate')
     const sync = $gettext('Sync Certificate')
     const managed = $gettext('Managed Certificate')
     const managed = $gettext('Managed Certificate')
     const general = $gettext('General Certificate')
     const general = $gettext('General Certificate')
@@ -54,18 +52,18 @@ const columns: Column[] = [{
     return h('div', template)
     return h('div', template)
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
   title: () => $gettext('Key Type'),
   title: () => $gettext('Key Type'),
   dataIndex: 'key_type',
   dataIndex: 'key_type',
-  customRender: mask(PrivateKeyTypeMask),
+  customRender: maskRender(PrivateKeyTypeMask),
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
   title: () => $gettext('Status'),
   title: () => $gettext('Status'),
   dataIndex: 'certificate_info',
   dataIndex: 'certificate_info',
-  pithy: true,
-  customRender: (args: CustomRender) => {
+  pure: true,
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
 
 
     const text = args.text?.not_before
     const text = args.text?.not_before
@@ -75,11 +73,11 @@ const columns: Column[] = [{
 
 
     if (text) {
     if (text) {
       template.push(<Badge status="success" />)
       template.push(<Badge status="success" />)
-      template.push($gettext('Valid'))
+      template.push(h('span', $gettext('Valid')))
     }
     }
     else {
     else {
       template.push(<Badge status="error" />)
       template.push(<Badge status="error" />)
-      template.push($gettext('Expired'))
+      template.push(h('span', $gettext('Expired')))
     }
     }
 
 
     return h('div', template)
     return h('div', template)
@@ -87,12 +85,12 @@ const columns: Column[] = [{
 }, {
 }, {
   title: () => $gettext('Not After'),
   title: () => $gettext('Not After'),
   dataIndex: ['certificate_info', 'not_after'],
   dataIndex: ['certificate_info', 'not_after'],
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   fixed: 'right',
 }]
 }]
 
 

+ 15 - 16
app/src/views/certificate/DNSCredential.vue

@@ -1,37 +1,35 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import { datetimeRender, StdCurd } from '@uozi-admin/curd'
 import dns_credential from '@/api/dns_credential'
 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 DNSChallenge from './components/DNSChallenge.vue'
 import DNSChallenge from './components/DNSChallenge.vue'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
 }, {
 }, {
   title: () => $gettext('Provider'),
   title: () => $gettext('Provider'),
   dataIndex: ['config', 'name'],
   dataIndex: ['config', 'name'],
-  customRender: (args: CustomRender) => {
-    return args.record.provider
+  customRender: ({ record }: CustomRenderArgs) => {
+    return record.provider
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   dataIndex: 'updated_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
+  fixed: 'right',
 }]
 }]
 </script>
 </script>
 
 
@@ -40,8 +38,9 @@ const columns: Column[] = [{
     :title="$gettext('DNS Credentials')"
     :title="$gettext('DNS Credentials')"
     :api="dns_credential"
     :api="dns_credential"
     :columns="columns"
     :columns="columns"
+    disable-export
   >
   >
-    <template #beforeEdit>
+    <template #beforeForm>
       <AAlert
       <AAlert
         class="mb-4"
         class="mb-4"
         type="info"
         type="info"

+ 1 - 1
app/src/views/certificate/components/ACMEUserSelector.vue

@@ -44,7 +44,7 @@ onMounted(async () => {
   let page = 1
   let page = 1
   while (true) {
   while (true) {
     try {
     try {
-      const r = await acme_user.get_list({ page })
+      const r = await acme_user.getList({ page })
 
 
       users.value.push(...r.data)
       users.value.push(...r.data)
       if (r?.data?.length < (r?.pagination?.per_page ?? 0))
       if (r?.data?.length < (r?.pagination?.per_page ?? 0))

+ 1 - 0
app/src/views/certificate/components/RemoveCert.vue

@@ -82,6 +82,7 @@ function handleCancel() {
     <AButton
     <AButton
       type="link"
       type="link"
       size="small"
       size="small"
+      danger
       @click="handleDelete"
       @click="handleDelete"
     >
     >
       {{ $gettext('Delete') }}
       {{ $gettext('Delete') }}

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

@@ -70,7 +70,7 @@ async function init() {
   data.value.name = name?.[name?.length - 1] ?? ''
   data.value.name = name?.[name?.length - 1] ?? ''
   origName.value = data.value.name
   origName.value = data.value.name
   if (!addMode.value) {
   if (!addMode.value) {
-    config.get(relativePath.value).then(r => {
+    config.getItem(relativePath.value).then(r => {
       data.value = r
       data.value = r
       historyChatgptRecord.value = r.chatgpt_messages
       historyChatgptRecord.value = r.chatgpt_messages
       modifiedAt.value = r.modified_at
       modifiedAt.value = r.modified_at

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

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import { StdTable } from '@uozi-admin/curd'
 import config from '@/api/config'
 import config from '@/api/config'
 import FooterToolBar from '@/components/FooterToolbar'
 import FooterToolBar from '@/components/FooterToolbar'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
 import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
 import Mkdir from './components/Mkdir.vue'
 import Mkdir from './components/Mkdir.vue'
 import Rename from './components/Rename.vue'
 import Rename from './components/Rename.vue'
@@ -133,17 +133,17 @@ const refRename = useTemplateRef('refRename')
     <StdTable
     <StdTable
       :key="update"
       :key="update"
       ref="table"
       ref="table"
-      :api="config"
+      :get-list-api="config.getList"
       :columns="configColumns"
       :columns="configColumns"
       disable-delete
       disable-delete
       disable-view
       disable-view
       row-key="name"
       row-key="name"
-      :get-params="getParams"
-      disable-query-params
-      disable-modify
+      :custom-query-params="getParams"
+      disable-router-query
+      disable-edit
       :scroll-x="880"
       :scroll-x="880"
     >
     >
-      <template #actions="{ record }">
+      <template #beforeActions="{ record }">
         <AButton
         <AButton
           type="link"
           type="link"
           size="small"
           size="small"
@@ -184,11 +184,11 @@ const refRename = useTemplateRef('refRename')
     </StdTable>
     </StdTable>
     <Mkdir
     <Mkdir
       ref="refMkdir"
       ref="refMkdir"
-      @created="() => table?.get_list()"
+      @created="() => table?.refresh()"
     />
     />
     <Rename
     <Rename
       ref="refRename"
       ref="refRename"
-      @renamed="() => table?.get_list()"
+      @renamed="() => table?.refresh()"
     />
     />
     <FooterToolBar v-if="basePath">
     <FooterToolBar v-if="basePath">
       <AButton @click="goBack">
       <AButton @click="goBack">

+ 12 - 14
app/src/views/config/configColumns.tsx

@@ -1,17 +1,16 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
 import { FileFilled, FolderFilled } from '@ant-design/icons-vue'
 import { FileFilled, FolderFilled } from '@ant-design/icons-vue'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input } from '@/components/StdDesign/StdDataEntry'
+import { datetimeRender } from '@uozi-admin/curd'
 
 
-const configColumns = [{
+const configColumns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   search: {
   search: {
-    type: input,
+    type: 'input',
   },
   },
-  customRender: (args: CustomRender) => {
+  customRender: ({ text, record }: CustomRenderArgs) => {
     function renderIcon(isDir: boolean) {
     function renderIcon(isDir: boolean) {
       return (
       return (
         <div class="mr-2 text-truegray-5">
         <div class="mr-2 text-truegray-5">
@@ -22,11 +21,11 @@ const configColumns = [{
       )
       )
     }
     }
 
 
-    const displayName = args.text || ''
+    const displayName = text || ''
 
 
     return (
     return (
       <div class="flex">
       <div class="flex">
-        {renderIcon(args.record.is_dir)}
+        {renderIcon(record.is_dir)}
         {displayName}
         {displayName}
       </div>
       </div>
     )
     )
@@ -35,14 +34,13 @@ const configColumns = [{
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
   dataIndex: 'modified_at',
-  customRender: datetime,
-  datetime: true,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 200,
   width: 200,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   fixed: 'right',
   width: 180,
   width: 180,
 }]
 }]

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

@@ -30,7 +30,7 @@ onMounted(async () => {
   let hasMore = true
   let hasMore = true
   let page = 1
   let page = 1
   while (hasMore) {
   while (hasMore) {
-    await environment.get_list({ page, enabled: true }).then(r => {
+    await environment.getList({ page, enabled: true }).then(r => {
       data.value.push(...r.data)
       data.value.push(...r.data)
       hasMore = r.data.length === r.pagination?.per_page
       hasMore = r.data.length === r.pagination?.per_page
       page++
       page++

+ 18 - 5
app/src/views/environments/group/EnvGroup.vue

@@ -1,31 +1,44 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import type { UpdateOrderRequest } from '@/api/curd'
+import { StdCurd } from '@uozi-admin/curd'
 import env_group, { PostSyncAction } from '@/api/env_group'
 import env_group, { PostSyncAction } from '@/api/env_group'
 import NodeSelector from '@/components/NodeSelector'
 import NodeSelector from '@/components/NodeSelector'
-import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
 import columns from '@/views/environments/group/columns'
 import columns from '@/views/environments/group/columns'
+
+const table = useTemplateRef('table')
+
+async function handleDragEnd(data: UpdateOrderRequest) {
+  await env_group.updateOrder(data)
+  table.value?.refresh()
+}
 </script>
 </script>
 
 
 <template>
 <template>
   <StdCurd
   <StdCurd
+    ref="table"
     :title="$gettext('Node Groups')"
     :title="$gettext('Node Groups')"
     :api="env_group"
     :api="env_group"
     :columns="columns"
     :columns="columns"
     :scroll-x="600"
     :scroll-x="600"
-    sortable
+    disable-export
+    row-draggable
+    :row-draggable-options="{
+      onEnd: handleDragEnd,
+    }"
   >
   >
-    <template #edit="{ data }">
+    <template #afterForm="{ record }">
       <div class="mb-2">
       <div class="mb-2">
         {{ $gettext('Sync Nodes') }}
         {{ $gettext('Sync Nodes') }}
       </div>
       </div>
       <NodeSelector
       <NodeSelector
-        v-model:target="data.sync_node_ids"
+        v-model:target="record.sync_node_ids"
         hidden-local
         hidden-local
       />
       />
 
 
       <AForm class="mt-4" layout="vertical">
       <AForm class="mt-4" layout="vertical">
         <AFormItem :label="$gettext('Post-sync Action')">
         <AFormItem :label="$gettext('Post-sync Action')">
           <ASelect
           <ASelect
-            v-model:value="data.post_sync_action"
+            v-model:value="record.post_sync_action"
             :placeholder="$gettext('Select an action after sync')"
             :placeholder="$gettext('Select an action after sync')"
             :default-value="PostSyncAction.ReloadNginx"
             :default-value="PostSyncAction.ReloadNginx"
             class="w-full"
             class="w-full"

+ 12 - 14
app/src/views/environments/group/columns.ts

@@ -1,17 +1,15 @@
-import type { Column } from '@/components/StdDesign/types'
+import type { StdTableColumn } from '@uozi-admin/curd'
+import { datetimeRender } from '@uozi-admin/curd'
 import { PostSyncAction } from '@/api/env_group'
 import { PostSyncAction } from '@/api/env_group'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input } from '@/components/StdDesign/StdDataEntry'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   dataIndex: 'name',
   dataIndex: 'name',
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   search: true,
   search: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
-  handle: true,
-  pithy: true,
+  pure: true,
   width: 120,
   width: 120,
 }, {
 }, {
   title: () => $gettext('Post-sync Action'),
   title: () => $gettext('Post-sync Action'),
@@ -25,23 +23,23 @@ const columns: Column[] = [{
     }
     }
     return text
     return text
   },
   },
-  pithy: true,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Created at'),
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
   dataIndex: 'created_at',
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   dataIndex: 'updated_at',
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   fixed: 'right',
   width: 150,
   width: 150,
 }]
 }]

+ 3 - 3
app/src/views/environments/list/Environment.vue

@@ -1,9 +1,9 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
+import { StdCurd } from '@uozi-admin/curd'
 import { message } from 'ant-design-vue'
 import { message } from 'ant-design-vue'
 import environment from '@/api/environment'
 import environment from '@/api/environment'
 import node from '@/api/node'
 import node from '@/api/node'
 import FooterToolBar from '@/components/FooterToolbar'
 import FooterToolBar from '@/components/FooterToolbar'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import BatchUpgrader from './BatchUpgrader.vue'
 import BatchUpgrader from './BatchUpgrader.vue'
 import envColumns from './envColumns'
 import envColumns from './envColumns'
 
 
@@ -16,7 +16,7 @@ const loadingRestart = ref(false)
 function loadFromSettings() {
 function loadFromSettings() {
   loadingFromSettings.value = true
   loadingFromSettings.value = true
   environment.load_from_settings().then(() => {
   environment.load_from_settings().then(() => {
-    curd.value.get_list()
+    curd.value.getList()
     message.success($gettext('Load successfully'))
     message.success($gettext('Load successfully'))
   }).finally(() => {
   }).finally(() => {
     loadingFromSettings.value = false
     loadingFromSettings.value = false
@@ -70,7 +70,7 @@ const inTrash = computed(() => {
       v-model:selected-row-keys="selectedNodeIds"
       v-model:selected-row-keys="selectedNodeIds"
       v-model:selected-rows="selectedNodes"
       v-model:selected-rows="selectedNodes"
       :scroll-x="1000"
       :scroll-x="1000"
-      selection-type="checkbox"
+      row-selection-type="checkbox"
       :title="$gettext('Environments')"
       :title="$gettext('Environments')"
       :api="environment"
       :api="environment"
       :columns="envColumns"
       :columns="envColumns"

+ 20 - 21
app/src/views/environments/list/envColumns.tsx

@@ -1,17 +1,16 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import type { JSXElements } from '@/components/StdDesign/types'
+import { datetimeRender } from '@uozi-admin/curd'
 import { Badge, Tag } from 'ant-design-vue'
 import { Badge, Tag } from 'ant-design-vue'
 import { h } from 'vue'
 import { h } from 'vue'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input, switcher } from '@/components/StdDesign/StdDataEntry'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
   search: true,
   search: true,
   width: 200,
   width: 200,
@@ -19,10 +18,10 @@ const columns: Column[] = [{
   title: () => $gettext('URL'),
   title: () => $gettext('URL'),
   dataIndex: 'url',
   dataIndex: 'url',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
-    config: {
+    type: 'input',
+    input: {
       placeholder: () => 'https://10.0.0.1:9000',
       placeholder: () => 'https://10.0.0.1:9000',
     },
     },
   },
   },
@@ -30,7 +29,7 @@ const columns: Column[] = [{
 }, {
 }, {
   title: () => $gettext('Version'),
   title: () => $gettext('Version'),
   dataIndex: 'version',
   dataIndex: 'version',
-  pithy: true,
+  pure: true,
   width: 120,
   width: 120,
 }, {
 }, {
   title: () => 'NodeSecret',
   title: () => 'NodeSecret',
@@ -38,12 +37,12 @@ const columns: Column[] = [{
   sorter: true,
   sorter: true,
   hiddenInTable: true,
   hiddenInTable: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
 }, {
 }, {
   title: () => $gettext('Status'),
   title: () => $gettext('Status'),
   dataIndex: 'status',
   dataIndex: 'status',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
     const { text } = args
     const { text } = args
     if (args.record.enabled) {
     if (args.record.enabled) {
@@ -64,12 +63,12 @@ const columns: Column[] = [{
     return h('div', template)
     return h('div', template)
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 120,
   width: 120,
 }, {
 }, {
   title: () => $gettext('Enabled'),
   title: () => $gettext('Enabled'),
   dataIndex: 'enabled',
   dataIndex: 'enabled',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
     const { text } = args
     const { text } = args
     if (text === true || text > 0)
     if (text === true || text > 0)
@@ -81,21 +80,21 @@ const columns: Column[] = [{
     return h('div', template)
     return h('div', template)
   },
   },
   edit: {
   edit: {
-    type: switcher,
+    type: 'switch',
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 120,
   width: 120,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   dataIndex: 'updated_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   fixed: 'right',
   width: 200,
   width: 200,
 }]
 }]

+ 25 - 16
app/src/views/nginx_log/NginxLogList.vue

@@ -1,27 +1,33 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import { StdCurd } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import { Tag } from 'ant-design-vue'
 import nginxLog from '@/api/nginx_log'
 import nginxLog from '@/api/nginx_log'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
-import { input, select } from '@/components/StdDesign/StdDataEntry'
 
 
 const router = useRouter()
 const router = useRouter()
 const stdCurdRef = ref()
 const stdCurdRef = ref()
 
 
-const columns: Column[] = [
+const columns: StdTableColumn[] = [
   {
   {
     title: () => $gettext('Type'),
     title: () => $gettext('Type'),
     dataIndex: 'type',
     dataIndex: 'type',
-    customRender: (args: CustomRender) => {
+    customRender: (args: CustomRenderArgs) => {
       return args.record?.type === 'access' ? <Tag color="success">{ $gettext('Access Log') }</Tag> : <Tag color="orange">{ $gettext('Error Log') }</Tag>
       return args.record?.type === 'access' ? <Tag color="success">{ $gettext('Access Log') }</Tag> : <Tag color="orange">{ $gettext('Error Log') }</Tag>
     },
     },
     sorter: true,
     sorter: true,
     search: {
     search: {
-      type: select,
-      mask: {
-        access: () => $gettext('Access Log'),
-        error: () => $gettext('Error Log'),
+      type: 'select',
+      select: {
+        options: [
+          {
+            label: () => $gettext('Access Log'),
+            value: 'access',
+          },
+          {
+            label: () => $gettext('Error Log'),
+            value: 'error',
+          },
+        ],
       },
       },
     },
     },
     width: 200,
     width: 200,
@@ -31,7 +37,7 @@ const columns: Column[] = [
     dataIndex: 'name',
     dataIndex: 'name',
     sorter: true,
     sorter: true,
     search: {
     search: {
-      type: input,
+      type: 'input',
     },
     },
   },
   },
   {
   {
@@ -39,12 +45,14 @@ const columns: Column[] = [
     dataIndex: 'path',
     dataIndex: 'path',
     sorter: true,
     sorter: true,
     search: {
     search: {
-      type: input,
+      type: 'input',
     },
     },
   },
   },
   {
   {
-    title: () => $gettext('Action'),
-    dataIndex: 'action',
+    title: () => $gettext('Actions'),
+    dataIndex: 'actions',
+    fixed: 'right',
+    width: 200,
   },
   },
 ]
 ]
 
 
@@ -65,11 +73,12 @@ function viewLog(record: { type: string, path: string }) {
     :columns="columns"
     :columns="columns"
     :api="nginxLog"
     :api="nginxLog"
     disable-add
     disable-add
+    disable-export
     disable-delete
     disable-delete
     disable-view
     disable-view
-    disable-modify
+    disable-edit
   >
   >
-    <template #actions="{ record }">
+    <template #beforeActions="{ record }">
       <AButton type="link" size="small" @click="viewLog(record)">
       <AButton type="link" size="small" @click="viewLog(record)">
         {{ $gettext('View') }}
         {{ $gettext('View') }}
       </AButton>
       </AButton>

+ 12 - 11
app/src/views/notification/notificationColumns.tsx

@@ -1,14 +1,13 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import { datetimeRender } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import { Tag } from 'ant-design-vue'
 import { detailRender } from '@/components/Notification/detailRender'
 import { detailRender } from '@/components/Notification/detailRender'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { NotificationTypeT } from '@/constants'
 import { NotificationTypeT } from '@/constants'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Type'),
   title: () => $gettext('Type'),
   dataIndex: 'type',
   dataIndex: 'type',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     if (args.text === NotificationTypeT.Error) {
     if (args.text === NotificationTypeT.Error) {
       return (
       return (
         <Tag color="error">
         <Tag color="error">
@@ -37,35 +36,37 @@ const columns: Column[] = [{
         </Tag>
         </Tag>
       )
       )
     }
     }
+    return args.text
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 100,
   width: 100,
 }, {
 }, {
   title: () => $gettext('Created at'),
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
   dataIndex: 'created_at',
   sorter: true,
   sorter: true,
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 180,
   width: 180,
 }, {
 }, {
   title: () => $gettext('Title'),
   title: () => $gettext('Title'),
   dataIndex: 'title',
   dataIndex: 'title',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     return h('span', $gettext(args.text))
     return h('span', $gettext(args.text))
   },
   },
-  pithy: true,
+  pure: true,
   width: 250,
   width: 250,
 }, {
 }, {
   title: () => $gettext('Details'),
   title: () => $gettext('Details'),
   dataIndex: 'details',
   dataIndex: 'details',
   customRender: detailRender,
   customRender: detailRender,
-  pithy: true,
+  pure: true,
   width: 500,
   width: 500,
 }, {
 }, {
   title: () => $gettext('Action'),
   title: () => $gettext('Action'),
   dataIndex: 'action',
   dataIndex: 'action',
   fixed: 'right',
   fixed: 'right',
+  width: 200,
 }]
 }]
 
 
 export default columns
 export default columns

+ 1 - 1
app/src/views/preference/components/AuthSettings/Passkey.vue

@@ -20,7 +20,7 @@ const passkeyName = ref('')
 
 
 function getList() {
 function getList() {
   getListLoading.value = true
   getListLoading.value = true
-  passkey.get_list().then(r => {
+  passkey.getList().then(r => {
     data.value = r
     data.value = r
   }).finally(() => {
   }).finally(() => {
     getListLoading.value = false
     getListLoading.value = false

+ 1 - 1
app/src/views/site/site_edit/components/SiteEditor/store.ts

@@ -32,7 +32,7 @@ export const useSiteEditorStore = defineStore('siteEditor', () => {
 
 
     if (name.value) {
     if (name.value) {
       try {
       try {
-        const r = await site.get(name.value)
+        const r = await site.getItem(name.value)
         handleResponse(r)
         handleResponse(r)
       }
       }
       catch (error) {
       catch (error) {

+ 11 - 8
app/src/views/site/site_list/SiteList.vue

@@ -2,12 +2,12 @@
 import type { EnvGroup } from '@/api/env_group'
 import type { EnvGroup } from '@/api/env_group'
 import type { Site } from '@/api/site'
 import type { Site } from '@/api/site'
 import type { Column } from '@/components/StdDesign/types'
 import type { Column } from '@/components/StdDesign/types'
+import { StdTable } from '@uozi-admin/curd'
 import { message } from 'ant-design-vue'
 import { message } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import env_group from '@/api/env_group'
 import site from '@/api/site'
 import site from '@/api/site'
 import EnvGroupTabs from '@/components/EnvGroupTabs'
 import EnvGroupTabs from '@/components/EnvGroupTabs'
 import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
 import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { ConfigStatus } from '@/constants'
 import { ConfigStatus } from '@/constants'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import columns from '@/views/site/site_list/columns'
 import columns from '@/views/site/site_list/columns'
@@ -47,7 +47,7 @@ onMounted(async () => {
 
 
 function destroy(site_name: string) {
 function destroy(site_name: string) {
   site.destroy(site_name).then(() => {
   site.destroy(site_name).then(() => {
-    table.value.get_list()
+    table.value.getList()
     message.success($gettext('Delete site: %{site_name}', { site_name }))
     message.success($gettext('Delete site: %{site_name}', { site_name }))
     inspectConfig.value?.test()
     inspectConfig.value?.test()
   })
   })
@@ -82,21 +82,24 @@ function handleBatchUpdated() {
 
 
     <StdTable
     <StdTable
       ref="table"
       ref="table"
-      :api="site"
+      :get-list-api="site.getList"
       :columns="columns"
       :columns="columns"
-      row-key="name"
+      :table-props="{
+        rowKey: 'name',
+      }"
       disable-delete
       disable-delete
       disable-view
       disable-view
-      :get-params="{
+      row-selection-type="checkbox"
+      :custom-query-params="{
         env_group_id: envGroupId,
         env_group_id: envGroupId,
       }"
       }"
       :scroll-x="1600"
       :scroll-x="1600"
-      @click-edit="(r: string) => router.push({
-        path: `/sites/${encodeURIComponent(r)}`,
+      @edit-item="record => router.push({
+        path: `/sites/${encodeURIComponent(record.name)}`,
       })"
       })"
       @click-batch-modify="handleClickBatchEdit"
       @click-batch-modify="handleClickBatchEdit"
     >
     >
-      <template #actions="{ record }">
+      <template #afterActions="{ record }">
         <AButton
         <AButton
           type="link"
           type="link"
           size="small"
           size="small"

+ 38 - 30
app/src/views/site/site_list/columns.tsx

@@ -1,30 +1,27 @@
-import type { SiteStatus } from '@/api/site'
 import type {
 import type {
-  CustomRender,
-} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
+  CustomRenderArgs,
+  StdTableColumn,
+} from '@uozi-admin/curd'
+import type { SiteStatus } from '@/api/site'
+import type { JSXElements } from '@/types'
+import { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import { Tag } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import env_group from '@/api/env_group'
-import {
-  actualValueRender,
-  datetime,
-} from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
 import { ConfigStatus } from '@/constants'
 import { ConfigStatus } from '@/constants'
 import envGroupColumns from '@/views/environments/group/columns'
 import envGroupColumns from '@/views/environments/group/columns'
 import SiteStatusSegmented from '@/views/site/components/SiteStatusSegmented.vue'
 import SiteStatusSegmented from '@/views/site/components/SiteStatusSegmented.vue'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
   search: true,
   search: true,
   width: 150,
   width: 150,
-  customRender: ({ text, record }) => {
+  customRender: ({ text, record }: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
 
 
     // Add site name
     // Add site name
@@ -65,31 +62,31 @@ const columns: Column[] = [{
 }, {
 }, {
   title: () => $gettext('Node Group'),
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
   dataIndex: 'env_group_id',
-  customRender: actualValueRender('env_group.name'),
+  customRender: actualFieldRender('env_group.name'),
   edit: {
   edit: {
-    type: selector,
+    type: 'selector',
     selector: {
     selector: {
-      api: env_group,
+      getListApi: env_group.getList,
       columns: envGroupColumns,
       columns: envGroupColumns,
-      recordValueIndex: 'name',
+      valueKey: 'id',
+      displayKey: 'name',
       selectionType: 'radio',
       selectionType: 'radio',
     },
     },
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
-  batch: true,
+  pure: true,
   width: 100,
   width: 100,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
   dataIndex: 'modified_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Status'),
   title: () => $gettext('Status'),
   dataIndex: 'status',
   dataIndex: 'status',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const { text, record } = args
     const { text, record } = args
     return h(SiteStatusSegmented, {
     return h(SiteStatusSegmented, {
       'modelValue': text,
       'modelValue': text,
@@ -105,20 +102,31 @@ const columns: Column[] = [{
     })
     })
   },
   },
   search: {
   search: {
-    type: select,
-    mask: {
-      [ConfigStatus.Enabled]: $gettext('Enabled'),
-      [ConfigStatus.Disabled]: $gettext('Disabled'),
-      [ConfigStatus.Maintenance]: $gettext('Maintenance'),
+    type: 'select',
+    select: {
+      options: [
+        {
+          label: $gettext('Enabled'),
+          value: ConfigStatus.Enabled,
+        },
+        {
+          label: $gettext('Disabled'),
+          value: ConfigStatus.Disabled,
+        },
+        {
+          label: $gettext('Maintenance'),
+          value: ConfigStatus.Maintenance,
+        },
+      ],
     },
     },
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 110,
   width: 110,
   fixed: 'right',
   fixed: 'right',
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   width: 80,
   width: 80,
   fixed: 'right',
   fixed: 'right',
 }]
 }]

+ 29 - 29
app/src/views/stream/StreamList.vue

@@ -1,78 +1,76 @@
 <script setup lang="tsx">
 <script setup lang="tsx">
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
 import type { EnvGroup } from '@/api/env_group'
 import type { EnvGroup } from '@/api/env_group'
 import type { Stream } from '@/api/stream'
 import type { Stream } from '@/api/stream'
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
+import type { JSXElements } from '@/types'
+import { actualFieldRender, datetimeRender, StdTable } from '@uozi-admin/curd'
 import { Badge, message } from 'ant-design-vue'
 import { Badge, message } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import env_group from '@/api/env_group'
 import stream from '@/api/stream'
 import stream from '@/api/stream'
 import EnvGroupTabs from '@/components/EnvGroupTabs'
 import EnvGroupTabs from '@/components/EnvGroupTabs'
 import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
 import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
-import { actualValueRender, datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input, selector } from '@/components/StdDesign/StdDataEntry'
 import { ConfigStatus } from '@/constants'
 import { ConfigStatus } from '@/constants'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import envGroupColumns from '@/views/environments/group/columns'
 import envGroupColumns from '@/views/environments/group/columns'
 import StreamDuplicate from '@/views/stream/components/StreamDuplicate.vue'
 import StreamDuplicate from '@/views/stream/components/StreamDuplicate.vue'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
   search: true,
   search: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Node Group'),
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
   dataIndex: 'env_group_id',
-  customRender: actualValueRender('env_group.name'),
+  customRender: actualFieldRender('env_group.name'),
   edit: {
   edit: {
-    type: selector,
+    type: 'selector',
     selector: {
     selector: {
-      api: env_group,
+      getListApi: env_group.getList,
       columns: envGroupColumns,
       columns: envGroupColumns,
-      recordValueIndex: 'name',
+      valueKey: 'id',
+      displayKey: 'name',
       selectionType: 'radio',
       selectionType: 'radio',
     },
     },
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
-  batch: true,
+  pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Status'),
   title: () => $gettext('Status'),
   dataIndex: 'status',
   dataIndex: 'status',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
     const { text } = args
     const { text } = args
     if (text === ConfigStatus.Enabled) {
     if (text === ConfigStatus.Enabled) {
       template.push(<Badge status="success" />)
       template.push(<Badge status="success" />)
-      template.push($gettext('Enabled'))
+      template.push(h('span', $gettext('Enabled')))
     }
     }
     else if (text === ConfigStatus.Disabled) {
     else if (text === ConfigStatus.Disabled) {
       template.push(<Badge status="warning" />)
       template.push(<Badge status="warning" />)
-      template.push($gettext('Disabled'))
+      template.push(h('span', $gettext('Disabled')))
     }
     }
 
 
     return h('div', template)
     return h('div', template)
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 200,
   width: 200,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
   dataIndex: 'modified_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 200,
   width: 200,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   width: 250,
   width: 250,
   fixed: 'right',
   fixed: 'right',
 }]
 }]
@@ -127,7 +125,7 @@ onMounted(async () => {
   let page = 1
   let page = 1
   while (true) {
   while (true) {
     try {
     try {
-      const { data, pagination } = await env_group.get_list({ page })
+      const { data, pagination } = await env_group.getList({ page })
       if (!data || !pagination)
       if (!data || !pagination)
         return
         return
       envGroups.value.push(...data)
       envGroups.value.push(...data)
@@ -163,7 +161,7 @@ function handleAddStream() {
 
 
 const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
 const stdBatchEditRef = useTemplateRef('stdBatchEditRef')
 
 
-async function handleClickBatchEdit(batchColumns: Column[], selectedRowKeys: string[], selectedRows: Stream[]) {
+async function handleClickBatchEdit(batchColumns: StdTableColumn[], selectedRowKeys: string[], selectedRows: Stream[]) {
   stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys, selectedRows)
   stdBatchEditRef.value?.showModal(batchColumns, selectedRowKeys, selectedRows)
 }
 }
 
 
@@ -187,21 +185,23 @@ function handleBatchUpdated() {
 
 
     <StdTable
     <StdTable
       ref="table"
       ref="table"
-      :api="stream"
+      :get-list-api="stream.getList"
       :columns="columns"
       :columns="columns"
-      row-key="name"
+      :table-props="{
+        rowKey: 'name',
+      }"
       disable-delete
       disable-delete
       disable-view
       disable-view
       :scroll-x="800"
       :scroll-x="800"
       :get-params="{
       :get-params="{
         env_group_id: envGroupId,
         env_group_id: envGroupId,
       }"
       }"
-      @click-edit="r => $router.push({
-        path: `/streams/${encodeURIComponent(r)}`,
+      @edit-item="record => $router.push({
+        path: `/streams/${encodeURIComponent(record.name)}`,
       })"
       })"
       @click-batch-modify="handleClickBatchEdit"
       @click-batch-modify="handleClickBatchEdit"
     >
     >
-      <template #actions="{ record }">
+      <template #afterActions="{ record }">
         <AButton
         <AButton
           v-if="record.enabled"
           v-if="record.enabled"
           type="link"
           type="link"

+ 1 - 1
app/src/views/stream/store.ts

@@ -32,7 +32,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
 
 
     if (name.value) {
     if (name.value) {
       try {
       try {
-        const r = await stream.get(name.value)
+        const r = await stream.getItem(name.value)
         handleResponse(r)
         handleResponse(r)
       }
       }
       catch (error) {
       catch (error) {

+ 2 - 1
app/src/views/user/User.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 <script setup lang="ts">
+import { StdCurd } from '@uozi-admin/curd'
 import user from '@/api/user'
 import user from '@/api/user'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import userColumns from '@/views/user/userColumns'
 import userColumns from '@/views/user/userColumns'
 </script>
 </script>
 
 
@@ -9,6 +9,7 @@ import userColumns from '@/views/user/userColumns'
     :scroll-x="1000"
     :scroll-x="1000"
     :title="$gettext('Manage Users')"
     :title="$gettext('Manage Users')"
     :columns="userColumns"
     :columns="userColumns"
+    disable-export
     :api="user"
     :api="user"
   />
   />
 </template>
 </template>

+ 19 - 20
app/src/views/user/userColumns.tsx

@@ -1,28 +1,26 @@
-import type { CustomRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import type { Column, JSXElements } from '@/components/StdDesign/types'
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import type { JSXElements } from '@/types'
+import { datetimeRender } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import { Tag } from 'ant-design-vue'
-import { h } from 'vue'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input, password } from '@/components/StdDesign/StdDataEntry'
 
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Username'),
   title: () => $gettext('Username'),
   dataIndex: 'name',
   dataIndex: 'name',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: input,
+    type: 'input',
   },
   },
   search: true,
   search: true,
 }, {
 }, {
   title: () => $gettext('Password'),
   title: () => $gettext('Password'),
   dataIndex: 'password',
   dataIndex: 'password',
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
   edit: {
-    type: password,
-    config: {
-      placeholder: () => $gettext('Leave blank for no change'),
+    type: 'password',
+    password: {
+      placeholder: $gettext('Leave blank for no change'),
       generate: true,
       generate: true,
     },
     },
   },
   },
@@ -31,7 +29,7 @@ const columns: Column[] = [{
 }, {
 }, {
   title: () => $gettext('2FA'),
   title: () => $gettext('2FA'),
   dataIndex: 'enabled_2fa',
   dataIndex: 'enabled_2fa',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const template: JSXElements = []
     const { text } = args
     const { text } = args
     if (text === true || text > 0)
     if (text === true || text > 0)
@@ -43,23 +41,24 @@ const columns: Column[] = [{
     return h('div', template)
     return h('div', template)
   },
   },
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
   title: () => $gettext('Created at'),
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
   dataIndex: 'created_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
   title: () => $gettext('Updated at'),
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   dataIndex: 'updated_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   fixed: 'right',
+  width: 250,
 }]
 }]
 
 
 export default columns
 export default columns

Some files were not shown because too many files changed in this diff