Browse Source

refactor: use uozi admin-kit

Jacky 1 month 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/pseudo-random': 'warn',
       '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",
   "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": {
     "dev": "vite --host",
     "typecheck": "vue-tsc --noEmit",
@@ -10,7 +10,7 @@
     "lint:fix": "eslint --fix .",
     "build": "vite build",
     "preview": "vite preview",
-    "gettext:extract": "vue-gettext-extract"
+    "gettext:extract": "generate-curd-translations --output src/language/curd.ts && vue-gettext-extract"
   },
   "dependencies": {
     "@0xjacky/vue-github-button": "^3.1.1",
@@ -18,6 +18,7 @@
     "@formkit/auto-animate": "^0.8.2",
     "@simplewebauthn/browser": "^13.1.0",
     "@uozi-admin/curd": "^4.1.4",
+    "@uozi-admin/request": "^2.6.0",
     "@vue/reactivity": "^3.5.14",
     "@vue/shared": "^3.5.14",
     "@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 http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 export interface TwoFAStatus {
   enabled: boolean

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

@@ -1,6 +1,5 @@
 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 {
   name: string
@@ -9,16 +8,10 @@ export interface AcmeUser extends ModelBase {
   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

+ 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'
 
 export interface CPUInfoStat {

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

@@ -1,5 +1,5 @@
 import type { AuthenticationResponseJSON } from '@simplewebauthn/browser'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import { useUserStore } from '@/pinia'
 
 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 = {
   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

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

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

@@ -1,7 +1,6 @@
 import type { GetListResponse } from '@/api/curd'
 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 {
   id: number
@@ -26,33 +25,16 @@ export interface ConfigBackup extends ModelBase {
   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

+ 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 {
   id: number
@@ -18,6 +18,12 @@ export interface GetListResponse<T> {
   pagination?: Pagination
 }
 
+export interface UpdateOrderRequest {
+  target_id: number
+  direction: number
+  affected_ids: number[]
+}
+
 class Curd<T> {
   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 { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 export interface DnsCredential extends ModelBase {
   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

+ 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
 export const PostSyncAction = {
   None: 'none',
@@ -13,4 +12,12 @@ export interface EnvGroup extends ModelBase {
   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 Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 export interface Environment extends ModelBase {
   name: string
@@ -16,16 +15,10 @@ export interface Node {
   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

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

@@ -1,17 +1,13 @@
 import type { ModelBase } from '@/api/curd'
-import Curd from '@/api/curd'
+import { useCurdApi } from '@uozi-admin/request'
 
 export interface ExternalNotify extends ModelBase {
   type: 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

+ 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 {
   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 {
   type: string
@@ -10,7 +10,7 @@ const nginx_log = {
     return http.post(`/nginx_log?page=${page}`, data)
   },
 
-  get_list(params: {
+  getList(params: {
     type?: string
     name?: 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 {
   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[]) {
   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 Curd from '@/api/curd'
-import http from '@/lib/http'
+import { http, useCurdApi } from '@uozi-admin/request'
 
 export interface Notification extends ModelBase {
   type: string
@@ -8,12 +7,10 @@ export interface Notification extends ModelBase {
   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

+ 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'
 
 export interface ChatComplicationMessage {

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

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

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

@@ -1,6 +1,6 @@
 import type { RegistrationResponseJSON } from '@simplewebauthn/browser'
 import type { ModelBase } from '@/api/curd'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 
 export interface Passkey extends ModelBase {
   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 {
   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 {
   code: string

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

@@ -1,6 +1,6 @@
 import type { Container } from '@/language'
 import type { CosyError } from '@/lib/http'
-import http from '@/lib/http'
+import { http } from '@uozi-admin/request'
 import ws from '@/lib/websocket'
 
 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 {
   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 { ChatComplicationMessage } from '@/api/openai'
 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
 
@@ -34,45 +33,18 @@ export interface AutoCertRequest {
   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

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

@@ -1,8 +1,7 @@
 import type { EnvGroup } from './env_group'
 import type { NgxConfig } from '@/api/ngx'
 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 {
   modified_at: string
@@ -18,29 +17,14 @@ export interface Stream {
   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

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

@@ -1,6 +1,5 @@
 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 {
   type?: string
@@ -21,30 +20,14 @@ export interface Template extends NgxServer {
   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

+ 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 {
   name: string

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

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

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

@@ -42,7 +42,7 @@ watch(current, () => {
   if (mounted.value)
     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 => {
       credentials.value?.push({
         value: v.id,
@@ -60,7 +60,7 @@ onMounted(async () => {
   })
 
   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.provider = r.provider
       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() {
   if (directive.value.directive === Include) {
-    config.get(directive.value.params).then(r => {
+    config.getItem(directive.value.params).then(r => {
       content.value = r.content
     })
   }

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

@@ -46,7 +46,7 @@ connect({
 
 function init() {
   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
     unreadCount.value = r.pagination?.total || 0
   }).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 notifications from './notifications'
 
-export function detailRender(args: CustomRender) {
+export function detailRender(args: CustomRenderArgs) {
   try {
     return (
       <div>

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

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

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

@@ -212,7 +212,7 @@ async function _get_list() {
   loading.value = true
 
   // 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
     rowsKeyIndexMap.value = {}
     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]
     // not init value, we need to fetch them from api
     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]
         records.value = [r]
       })
@@ -80,7 +80,7 @@ async function _init() {
     // M_values.value = props.value || []
     // not init value, we need to fetch them from api
     if (!props.value && (selectedKey.value?.length || 0) > 0) {
-      api.get_list({
+      api.getList({
         ...props.getParams,
         id: selectedKey.value,
       }).then(r => {

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

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

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

@@ -146,6 +146,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:155
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:151
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:186
+#: src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/stream/StreamList.vue:180
@@ -273,6 +274,10 @@ msgstr ""
 msgid "Are you sure you want to clear the record of chat?"
 msgstr ""
 
+#: src/language/curd.ts:27
+msgid "Are you sure you want to delete permanently?"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:540
 msgid "Are you sure you want to delete this item permanently?"
 msgstr ""
@@ -281,6 +286,7 @@ msgstr ""
 msgid "Are you sure you want to delete this item?"
 msgstr ""
 
+#: src/language/curd.ts:25
 #: src/views/site/site_list/SiteList.vue:110
 #: src/views/stream/StreamList.vue:231
 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?"
 msgstr ""
 
+#: src/language/curd.ts:26
+msgid "Are you sure you want to restore?"
+msgstr ""
+
 #: src/components/ChatGPT/ChatGPT.vue:318
 msgid "Ask ChatGPT for Help"
 msgstr ""
@@ -395,6 +405,10 @@ msgstr ""
 msgid "Back to list"
 msgstr ""
 
+#: src/language/curd.ts:21
+msgid "Back to List"
+msgstr ""
+
 #: src/routes/modules/system.ts:26
 msgid "Backup"
 msgstr ""
@@ -749,6 +763,16 @@ msgstr ""
 msgid "Click or drag backup file to this area to upload"
 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
 msgid "Click to copy"
 msgstr ""
@@ -774,6 +798,7 @@ msgid "Client request header buffer size"
 msgstr ""
 
 #: src/components/ConfigHistory/ConfigHistory.vue:169
+#: src/language/curd.ts:14
 msgid "Close"
 msgstr ""
 
@@ -1030,6 +1055,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:21
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:519
+#: src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:87
 #: src/views/site/site_list/SiteList.vue:119
 #: src/views/stream/StreamList.vue:240
@@ -1042,6 +1068,7 @@ msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:35
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:547
+#: src/language/curd.ts:11
 msgid "Delete Permanently"
 msgstr ""
 
@@ -1088,6 +1115,7 @@ msgid "Delete stream: %{stream_name}"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:183
+#: src/language/curd.ts:29
 msgid "Deleted successfully"
 msgstr ""
 
@@ -1352,6 +1380,7 @@ msgid "Dynamic"
 msgstr ""
 
 #: src/components/StdDesign/StdDetail/StdDetail.vue:110
+#: src/language/curd.ts:8
 msgid "Edit"
 msgstr ""
 
@@ -1561,6 +1590,10 @@ msgstr ""
 msgid "Export"
 msgstr ""
 
+#: src/language/curd.ts:22
+msgid "Export Excel"
+msgstr ""
+
 #: src/views/preference/tabs/NginxSettings.vue:49
 msgid "External Docker Container"
 msgstr ""
@@ -1929,6 +1962,7 @@ msgid "General Certificate"
 msgstr ""
 
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:55
+#: src/language/curd.ts:17
 msgid "Generate"
 msgstr ""
 
@@ -2203,6 +2237,10 @@ msgstr ""
 msgid "Issuer: %{issuer}"
 msgstr ""
 
+#: src/language/curd.ts:6
+msgid "item(s)"
+msgstr ""
+
 #: src/views/preference/tabs/AppSettings.vue:11
 msgid "Jwt Secret"
 msgstr ""
@@ -2283,6 +2321,7 @@ msgid "Link Start"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:173
+#: src/language/curd.ts:23
 msgid "List"
 msgstr ""
 
@@ -2974,6 +3013,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:36
 #: src/components/Notification/Notification.vue:111
 #: src/components/StdDesign/StdDataDisplay/StdBulkActions.vue:95
+#: src/language/curd.ts:15
 #: src/views/notification/Notification.vue:38
 #: src/views/site/components/SiteStatusSegmented.vue:96
 #: 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:138
+#: src/language/curd.ts:52
 #: src/views/config/ConfigEditor.vue:316
 #: src/views/nginx_log/NginxLogList.vue:38
 msgid "Path"
@@ -3165,6 +3206,10 @@ msgstr ""
 msgid "Please enter the security token received during backup"
 msgstr ""
 
+#: src/language/curd.ts:24
+msgid "Please fill all fields correctly"
+msgstr ""
+
 #: src/views/certificate/DNSCredential.vue:53
 msgid "Please fill in the API authentication credentials provided by your DNS provider."
 msgstr ""
@@ -3285,6 +3330,10 @@ msgstr ""
 msgid "Process information"
 msgstr ""
 
+#: src/language/curd.ts:51
+msgid "Processing {count}/{total}"
+msgstr ""
+
 #: src/language/constants.ts:3
 msgid "Prohibit changing root password in demo"
 msgstr ""
@@ -3567,6 +3616,7 @@ msgid "Requests Per Connection"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:442
+#: src/language/curd.ts:13
 msgid "Reset"
 msgstr ""
 
@@ -3617,6 +3667,10 @@ msgstr ""
 msgid "Restarting"
 msgstr ""
 
+#: src/language/curd.ts:10
+msgid "Restore"
+msgstr ""
+
 #: src/components/SystemRestore/SystemRestoreContent.vue:135
 msgid "Restore completed successfully"
 msgstr ""
@@ -3640,6 +3694,10 @@ msgstr ""
 msgid "Restore this version"
 msgstr ""
 
+#: src/language/curd.ts:30
+msgid "Restored successfully"
+msgstr ""
+
 #: src/views/certificate/components/RemoveCert.vue:26
 #: src/views/certificate/components/RemoveCert.vue:95
 msgid "Revoke"
@@ -3681,6 +3739,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:129
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:64
 #: src/components/StdDesign/StdDetail/StdDetail.vue:93
+#: src/language/curd.ts:18
 #: src/views/certificate/CertificateEditor.vue:266
 #: src/views/config/components/ConfigName.vue:59
 #: src/views/config/ConfigEditor.vue:275
@@ -3744,6 +3803,7 @@ msgid "Save successfully"
 msgstr ""
 
 #: src/components/NgxConfigEditor/directive/DirectiveEditorItem.vue:43
+#: 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
@@ -3763,6 +3823,7 @@ msgid "SDK"
 msgstr ""
 
 #: src/language/constants.ts:62
+#: src/language/curd.ts:12
 msgid "Search"
 msgstr ""
 
@@ -3783,11 +3844,20 @@ msgstr ""
 msgid "Security Token Information"
 msgstr ""
 
+#: src/language/curd.ts:31
+msgid "Select all"
+msgstr ""
+
 #: src/views/environments/group/EnvGroup.vue:29
 msgid "Select an action after sync"
 msgstr ""
 
+#: src/language/curd.ts:50
+msgid "Selected {count} files"
+msgstr ""
+
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:189
+#: src/language/curd.ts:16
 msgid "Selector"
 msgstr ""
 
@@ -3919,6 +3989,10 @@ msgstr ""
 msgid "Sites-enabled directory not exist"
 msgstr ""
 
+#: src/language/curd.ts:53
+msgid "Size"
+msgstr ""
+
 #: src/views/preference/tabs/NodeSettings.vue:23
 msgid "Skip Installation"
 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"
 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:274
 msgid "Supported file type: .zip"
@@ -4280,15 +4364,18 @@ msgid "This field is required"
 msgstr ""
 
 #: src/constants/form_errors.ts:3
+#: src/language/curd.ts:34
 msgid "This field should be a valid email address"
 msgstr ""
 
 #: src/constants/form_errors.ts:5
+#: src/language/curd.ts:36
 msgid "This field should be a valid hostname"
 msgstr ""
 
 #: src/components/StdDesign/StdDataEntry/StdFormItem.vue:39
 #: src/constants/form_errors.ts:2
+#: src/language/curd.ts:33
 msgid "This field should not be empty"
 msgstr ""
 
@@ -4296,6 +4383,10 @@ msgstr ""
 msgid "This field should only contain letters, unicode characters, numbers, and -_."
 msgstr ""
 
+#: src/language/curd.ts:37
+msgid "This field should only contain letters, unicode characters, numbers, and -_./:"
+msgstr ""
+
 #: 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"
 msgstr ""
@@ -4309,6 +4400,7 @@ msgid "This token will only be shown once and cannot be retrieved later. Please
 msgstr ""
 
 #: src/constants/form_errors.ts:4
+#: src/language/curd.ts:35
 msgid "This value is already taken"
 msgstr ""
 
@@ -4373,6 +4465,10 @@ msgstr ""
 msgid "Token is not valid"
 msgstr ""
 
+#: src/language/curd.ts:5
+msgid "Total"
+msgstr ""
+
 #: src/components/StdDesign/StdDataDisplay/StdPagination.vue:40
 msgid "Total %{total} item"
 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 ""
 
 #: src/components/StdDesign/StdDataDisplay/StdCurd.vue:197
+#: src/language/curd.ts:20
 msgid "Trash"
 msgstr ""
 
@@ -4477,6 +4574,14 @@ msgstr ""
 msgid "Upgrading Nginx UI, please wait..."
 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
 msgid "Upstream Name"
 msgstr ""
@@ -4550,6 +4655,7 @@ msgid "Version"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:488
+#: src/language/curd.ts:7
 #: src/views/nginx_log/NginxLogList.vue:74
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:83
 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 { http } from './client'
 import { registerError, useMessageDedupe } from './error'
-import { setupInterceptors } from './interceptors'
-
-// Initialize interceptors
-setupInterceptors()
 
 // Export everything needed from this module
-export default http
 export type {
   CosyError,
   CosyErrorRecord,
   HttpConfig,
 }
+
 export {
   registerError,
   useMessageDedupe,

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

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

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

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

+ 50 - 9
app/src/main.ts

@@ -1,30 +1,71 @@
 import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
+import { createCurdConfig } from '@uozi-admin/curd'
 import { createPinia } from 'pinia'
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
 import { createApp } from 'vue'
 import VueDOMPurifyHTML from 'vue-dompurify-html'
+import { setupInterceptors } from '@/lib/http/interceptors'
 import { useSettingsStore } from '@/pinia'
+import i18n from '../i18n.json'
 import App from './App.vue'
 import gettext from './gettext'
 import router from './routes'
+import '@uozi-admin/curd/dist/index.css'
 import 'virtual:uno.css'
 
 const pinia = createPinia()
 
 const app = createApp(App)
 
+function setupTranslations() {
+  return Object.keys(i18n).reduce((acc, cur) => {
+    acc[cur] = gettext.translations[cur]
+    return acc
+  }, {})
+}
+
 pinia.use(piniaPluginPersistedstate)
+
 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
 const settings = useSettingsStore()

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

@@ -3,3 +3,5 @@ export type CheckedType = boolean | string | number
 interface Window {
   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">
+import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
 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 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'),
     dataIndex: 'name',
     sorter: true,
-    pithy: true,
+    pure: true,
     edit: {
-      type: input,
-      config: {
+      type: 'input',
+      formItem: {
         required: true,
       },
     },
+    search: true,
   },
   {
     title: () => $gettext('Email'),
     dataIndex: 'email',
     sorter: true,
-    pithy: true,
+    pure: true,
     edit: {
-      type: input,
-      config: {
+      type: 'input',
+      formItem: {
         required: true,
       },
     },
@@ -37,13 +36,11 @@ const columns: Column[] = [
     title: () => $gettext('CA Dir'),
     dataIndex: 'ca_dir',
     sorter: true,
-    pithy: true,
+    pure: true,
     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',
     hiddenInTable: true,
     edit: {
-      type: input,
+      type: 'input',
       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'),
     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="red">{$gettext('Invalid')}</Tag>
     },
     sorter: true,
-    pithy: true,
+    pure: true,
   },
   {
     title: () => $gettext('Register On Startup'),
@@ -79,7 +74,7 @@ const columns: Column[] = [
     hiddenInTable: true,
     hiddenInDetail: true,
     edit: {
-      type: switcher,
+      type: 'switch',
       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.'),
     },
@@ -87,13 +82,13 @@ const columns: Column[] = [
   {
     title: () => $gettext('Updated at'),
     dataIndex: 'updated_at',
-    customRender: datetime,
+    customRender: datetimeRender,
     sorter: true,
-    pithy: true,
+    pure: true,
   },
   {
-    title: () => $gettext('Action'),
-    dataIndex: 'action',
+    title: () => $gettext('Actions'),
+    dataIndex: 'actions',
     fixed: 'right',
   },
 ]
@@ -112,6 +107,7 @@ function register(id: number, data: AcmeUser) {
   <StdCurd
     :title="$gettext('ACME User')"
     :columns="columns"
+    disable-export
     :api="acme_user"
   >
     <template #edit="{ data }: {data: AcmeUser}">

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

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

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

@@ -1,7 +1,7 @@
 <script setup lang="tsx">
 import { CloudUploadOutlined, SafetyCertificateOutlined } from '@ant-design/icons-vue'
+import { StdTable } from '@uozi-admin/curd'
 import cert from '@/api/cert'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { useGlobalStore } from '@/pinia'
 import RemoveCert from '../components/RemoveCert.vue'
 import WildcardCertificate from '../components/WildcardCertificate.vue'
@@ -41,15 +41,16 @@ const { processingStatus } = storeToRefs(globalStore)
       ref="refTable"
       :api="cert"
       :columns="certColumns"
+      :get-list-api="cert.getList"
       disable-view
       :scroll-x="1000"
       disable-delete
-      @click-edit="id => $router.push(`/certificates/${id}`)"
+      @edit-item="record => $router.push(`/certificates/${record.id}`)"
     >
-      <template #actions="{ record }">
+      <template #afterActions="{ record }">
         <RemoveCert
           :id="record.id"
-          @removed="() => refTable.get_list()"
+          @removed="() => refTable.refresh()"
         />
       </template>
     </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 dayjs from 'dayjs'
-import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input } from '@/components/StdDesign/StdDataEntry'
 import { PrivateKeyTypeMask } from '@/constants'
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
   sorter: true,
-  pithy: true,
-  customRender: (args: CustomRender) => {
+  pure: true,
+  customRender: (args: CustomRenderArgs) => {
     const { text, record } = args
     if (!text)
       return h('div', record.domain)
@@ -19,14 +18,13 @@ const columns: Column[] = [{
     return h('div', text)
   },
   search: {
-    type: input,
+    type: 'input',
   },
 }, {
   title: () => $gettext('Type'),
   dataIndex: 'auto_cert',
-  customRender: (args: CustomRender) => {
+  customRender: ({ text }: CustomRenderArgs) => {
     const template: JSXElements = []
-    const { text } = args
     const sync = $gettext('Sync Certificate')
     const managed = $gettext('Managed Certificate')
     const general = $gettext('General Certificate')
@@ -54,18 +52,18 @@ const columns: Column[] = [{
     return h('div', template)
   },
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
   title: () => $gettext('Key Type'),
   dataIndex: 'key_type',
-  customRender: mask(PrivateKeyTypeMask),
+  customRender: maskRender(PrivateKeyTypeMask),
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
   title: () => $gettext('Status'),
   dataIndex: 'certificate_info',
-  pithy: true,
-  customRender: (args: CustomRender) => {
+  pure: true,
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
 
     const text = args.text?.not_before
@@ -75,11 +73,11 @@ const columns: Column[] = [{
 
     if (text) {
       template.push(<Badge status="success" />)
-      template.push($gettext('Valid'))
+      template.push(h('span', $gettext('Valid')))
     }
     else {
       template.push(<Badge status="error" />)
-      template.push($gettext('Expired'))
+      template.push(h('span', $gettext('Expired')))
     }
 
     return h('div', template)
@@ -87,12 +85,12 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Not After'),
   dataIndex: ['certificate_info', 'not_after'],
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
 }]
 

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

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

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

@@ -44,7 +44,7 @@ onMounted(async () => {
   let page = 1
   while (true) {
     try {
-      const r = await acme_user.get_list({ page })
+      const r = await acme_user.getList({ page })
 
       users.value.push(...r.data)
       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
       type="link"
       size="small"
+      danger
       @click="handleDelete"
     >
       {{ $gettext('Delete') }}

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

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

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

@@ -1,7 +1,7 @@
 <script setup lang="ts">
+import { StdTable } from '@uozi-admin/curd'
 import config from '@/api/config'
 import FooterToolBar from '@/components/FooterToolbar'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { useBreadcrumbs } from '@/composables/useBreadcrumbs'
 import Mkdir from './components/Mkdir.vue'
 import Rename from './components/Rename.vue'
@@ -133,17 +133,17 @@ const refRename = useTemplateRef('refRename')
     <StdTable
       :key="update"
       ref="table"
-      :api="config"
+      :get-list-api="config.getList"
       :columns="configColumns"
       disable-delete
       disable-view
       row-key="name"
-      :get-params="getParams"
-      disable-query-params
-      disable-modify
+      :custom-query-params="getParams"
+      disable-router-query
+      disable-edit
       :scroll-x="880"
     >
-      <template #actions="{ record }">
+      <template #beforeActions="{ record }">
         <AButton
           type="link"
           size="small"
@@ -184,11 +184,11 @@ const refRename = useTemplateRef('refRename')
     </StdTable>
     <Mkdir
       ref="refMkdir"
-      @created="() => table?.get_list()"
+      @created="() => table?.refresh()"
     />
     <Rename
       ref="refRename"
-      @renamed="() => table?.get_list()"
+      @renamed="() => table?.refresh()"
     />
     <FooterToolBar v-if="basePath">
       <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 { 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'),
   dataIndex: 'name',
   sorter: true,
-  pithy: true,
+  pure: true,
   search: {
-    type: input,
+    type: 'input',
   },
-  customRender: (args: CustomRender) => {
+  customRender: ({ text, record }: CustomRenderArgs) => {
     function renderIcon(isDir: boolean) {
       return (
         <div class="mr-2 text-truegray-5">
@@ -22,11 +21,11 @@ const configColumns = [{
       )
     }
 
-    const displayName = args.text || ''
+    const displayName = text || ''
 
     return (
       <div class="flex">
-        {renderIcon(args.record.is_dir)}
+        {renderIcon(record.is_dir)}
         {displayName}
       </div>
     )
@@ -35,14 +34,13 @@ const configColumns = [{
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
-  customRender: datetime,
-  datetime: true,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 200,
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   width: 180,
 }]

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

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

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

@@ -1,31 +1,44 @@
 <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 NodeSelector from '@/components/NodeSelector'
-import { StdCurd } from '@/components/StdDesign/StdDataDisplay'
 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>
 
 <template>
   <StdCurd
+    ref="table"
     :title="$gettext('Node Groups')"
     :api="env_group"
     :columns="columns"
     :scroll-x="600"
-    sortable
+    disable-export
+    row-draggable
+    :row-draggable-options="{
+      onEnd: handleDragEnd,
+    }"
   >
-    <template #edit="{ data }">
+    <template #afterForm="{ record }">
       <div class="mb-2">
         {{ $gettext('Sync Nodes') }}
       </div>
       <NodeSelector
-        v-model:target="data.sync_node_ids"
+        v-model:target="record.sync_node_ids"
         hidden-local
       />
 
       <AForm class="mt-4" layout="vertical">
         <AFormItem :label="$gettext('Post-sync Action')">
           <ASelect
-            v-model:value="data.post_sync_action"
+            v-model:value="record.post_sync_action"
             :placeholder="$gettext('Select an action after sync')"
             :default-value="PostSyncAction.ReloadNginx"
             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 { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
-import { input } from '@/components/StdDesign/StdDataEntry'
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   dataIndex: 'name',
   title: () => $gettext('Name'),
   search: true,
   edit: {
-    type: input,
+    type: 'input',
   },
-  handle: true,
-  pithy: true,
+  pure: true,
   width: 120,
 }, {
   title: () => $gettext('Post-sync Action'),
@@ -25,23 +23,23 @@ const columns: Column[] = [{
     }
     return text
   },
-  pithy: true,
+  pure: true,
   width: 150,
 }, {
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 150,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 150,
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   width: 150,
 }]

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

@@ -1,9 +1,9 @@
 <script setup lang="tsx">
+import { StdCurd } from '@uozi-admin/curd'
 import { message } from 'ant-design-vue'
 import environment from '@/api/environment'
 import node from '@/api/node'
 import FooterToolBar from '@/components/FooterToolbar'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import BatchUpgrader from './BatchUpgrader.vue'
 import envColumns from './envColumns'
 
@@ -16,7 +16,7 @@ const loadingRestart = ref(false)
 function loadFromSettings() {
   loadingFromSettings.value = true
   environment.load_from_settings().then(() => {
-    curd.value.get_list()
+    curd.value.getList()
     message.success($gettext('Load successfully'))
   }).finally(() => {
     loadingFromSettings.value = false
@@ -70,7 +70,7 @@ const inTrash = computed(() => {
       v-model:selected-row-keys="selectedNodeIds"
       v-model:selected-rows="selectedNodes"
       :scroll-x="1000"
-      selection-type="checkbox"
+      row-selection-type="checkbox"
       :title="$gettext('Environments')"
       :api="environment"
       :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 { 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'),
   dataIndex: 'name',
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
-    type: input,
+    type: 'input',
   },
   search: true,
   width: 200,
@@ -19,10 +18,10 @@ const columns: Column[] = [{
   title: () => $gettext('URL'),
   dataIndex: 'url',
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
-    type: input,
-    config: {
+    type: 'input',
+    input: {
       placeholder: () => 'https://10.0.0.1:9000',
     },
   },
@@ -30,7 +29,7 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Version'),
   dataIndex: 'version',
-  pithy: true,
+  pure: true,
   width: 120,
 }, {
   title: () => 'NodeSecret',
@@ -38,12 +37,12 @@ const columns: Column[] = [{
   sorter: true,
   hiddenInTable: true,
   edit: {
-    type: input,
+    type: 'input',
   },
 }, {
   title: () => $gettext('Status'),
   dataIndex: 'status',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const { text } = args
     if (args.record.enabled) {
@@ -64,12 +63,12 @@ const columns: Column[] = [{
     return h('div', template)
   },
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 120,
 }, {
   title: () => $gettext('Enabled'),
   dataIndex: 'enabled',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const { text } = args
     if (text === true || text > 0)
@@ -81,21 +80,21 @@ const columns: Column[] = [{
     return h('div', template)
   },
   edit: {
-    type: switcher,
+    type: 'switch',
   },
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 120,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 150,
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
   width: 200,
 }]

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

@@ -1,27 +1,33 @@
 <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 nginxLog from '@/api/nginx_log'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
-import { input, select } from '@/components/StdDesign/StdDataEntry'
 
 const router = useRouter()
 const stdCurdRef = ref()
 
-const columns: Column[] = [
+const columns: StdTableColumn[] = [
   {
     title: () => $gettext('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>
     },
     sorter: true,
     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,
@@ -31,7 +37,7 @@ const columns: Column[] = [
     dataIndex: 'name',
     sorter: true,
     search: {
-      type: input,
+      type: 'input',
     },
   },
   {
@@ -39,12 +45,14 @@ const columns: Column[] = [
     dataIndex: 'path',
     sorter: true,
     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"
     :api="nginxLog"
     disable-add
+    disable-export
     disable-delete
     disable-view
-    disable-modify
+    disable-edit
   >
-    <template #actions="{ record }">
+    <template #beforeActions="{ record }">
       <AButton type="link" size="small" @click="viewLog(record)">
         {{ $gettext('View') }}
       </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 { detailRender } from '@/components/Notification/detailRender'
-import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { NotificationTypeT } from '@/constants'
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Type'),
   dataIndex: 'type',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     if (args.text === NotificationTypeT.Error) {
       return (
         <Tag color="error">
@@ -37,35 +36,37 @@ const columns: Column[] = [{
         </Tag>
       )
     }
+    return args.text
   },
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 100,
 }, {
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
   sorter: true,
-  customRender: datetime,
-  pithy: true,
+  customRender: datetimeRender,
+  pure: true,
   width: 180,
 }, {
   title: () => $gettext('Title'),
   dataIndex: 'title',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     return h('span', $gettext(args.text))
   },
-  pithy: true,
+  pure: true,
   width: 250,
 }, {
   title: () => $gettext('Details'),
   dataIndex: 'details',
   customRender: detailRender,
-  pithy: true,
+  pure: true,
   width: 500,
 }, {
   title: () => $gettext('Action'),
   dataIndex: 'action',
   fixed: 'right',
+  width: 200,
 }]
 
 export default columns

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

@@ -20,7 +20,7 @@ const passkeyName = ref('')
 
 function getList() {
   getListLoading.value = true
-  passkey.get_list().then(r => {
+  passkey.getList().then(r => {
     data.value = r
   }).finally(() => {
     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) {
       try {
-        const r = await site.get(name.value)
+        const r = await site.getItem(name.value)
         handleResponse(r)
       }
       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 { Site } from '@/api/site'
 import type { Column } from '@/components/StdDesign/types'
+import { StdTable } from '@uozi-admin/curd'
 import { message } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import site from '@/api/site'
 import EnvGroupTabs from '@/components/EnvGroupTabs'
 import StdBatchEdit from '@/components/StdDesign/StdDataDisplay/StdBatchEdit.vue'
-import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
 import { ConfigStatus } from '@/constants'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import columns from '@/views/site/site_list/columns'
@@ -47,7 +47,7 @@ onMounted(async () => {
 
 function destroy(site_name: string) {
   site.destroy(site_name).then(() => {
-    table.value.get_list()
+    table.value.getList()
     message.success($gettext('Delete site: %{site_name}', { site_name }))
     inspectConfig.value?.test()
   })
@@ -82,21 +82,24 @@ function handleBatchUpdated() {
 
     <StdTable
       ref="table"
-      :api="site"
+      :get-list-api="site.getList"
       :columns="columns"
-      row-key="name"
+      :table-props="{
+        rowKey: 'name',
+      }"
       disable-delete
       disable-view
-      :get-params="{
+      row-selection-type="checkbox"
+      :custom-query-params="{
         env_group_id: envGroupId,
       }"
       :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"
     >
-      <template #actions="{ record }">
+      <template #afterActions="{ record }">
         <AButton
           type="link"
           size="small"

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

@@ -1,30 +1,27 @@
-import type { SiteStatus } from '@/api/site'
 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 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 envGroupColumns from '@/views/environments/group/columns'
 import SiteStatusSegmented from '@/views/site/components/SiteStatusSegmented.vue'
 
-const columns: Column[] = [{
+const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
-    type: input,
+    type: 'input',
   },
   search: true,
   width: 150,
-  customRender: ({ text, record }) => {
+  customRender: ({ text, record }: CustomRenderArgs) => {
     const template: JSXElements = []
 
     // Add site name
@@ -65,31 +62,31 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
-  customRender: actualValueRender('env_group.name'),
+  customRender: actualFieldRender('env_group.name'),
   edit: {
-    type: selector,
+    type: 'selector',
     selector: {
-      api: env_group,
+      getListApi: env_group.getList,
       columns: envGroupColumns,
-      recordValueIndex: 'name',
+      valueKey: 'id',
+      displayKey: 'name',
       selectionType: 'radio',
     },
   },
   sorter: true,
-  pithy: true,
-  batch: true,
+  pure: true,
   width: 100,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
   width: 150,
 }, {
   title: () => $gettext('Status'),
   dataIndex: 'status',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const { text, record } = args
     return h(SiteStatusSegmented, {
       'modelValue': text,
@@ -105,20 +102,31 @@ const columns: Column[] = [{
     })
   },
   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,
-  pithy: true,
+  pure: true,
   width: 110,
   fixed: 'right',
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   width: 80,
   fixed: 'right',
 }]

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

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

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

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

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

@@ -1,6 +1,6 @@
 <script setup lang="ts">
+import { StdCurd } from '@uozi-admin/curd'
 import user from '@/api/user'
-import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import userColumns from '@/views/user/userColumns'
 </script>
 
@@ -9,6 +9,7 @@ import userColumns from '@/views/user/userColumns'
     :scroll-x="1000"
     :title="$gettext('Manage Users')"
     :columns="userColumns"
+    disable-export
     :api="user"
   />
 </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 { 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'),
   dataIndex: 'name',
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
-    type: input,
+    type: 'input',
   },
   search: true,
 }, {
   title: () => $gettext('Password'),
   dataIndex: 'password',
   sorter: true,
-  pithy: true,
+  pure: true,
   edit: {
-    type: password,
-    config: {
-      placeholder: () => $gettext('Leave blank for no change'),
+    type: 'password',
+    password: {
+      placeholder: $gettext('Leave blank for no change'),
       generate: true,
     },
   },
@@ -31,7 +29,7 @@ const columns: Column[] = [{
 }, {
   title: () => $gettext('2FA'),
   dataIndex: 'enabled_2fa',
-  customRender: (args: CustomRender) => {
+  customRender: (args: CustomRenderArgs) => {
     const template: JSXElements = []
     const { text } = args
     if (text === true || text > 0)
@@ -43,23 +41,24 @@ const columns: Column[] = [{
     return h('div', template)
   },
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
   title: () => $gettext('Created at'),
   dataIndex: 'created_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
-  customRender: datetime,
+  customRender: datetimeRender,
   sorter: true,
-  pithy: true,
+  pure: true,
 }, {
-  title: () => $gettext('Action'),
-  dataIndex: 'action',
+  title: () => $gettext('Actions'),
+  dataIndex: 'actions',
   fixed: 'right',
+  width: 250,
 }]
 
 export default columns

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