diff --git a/components.d.ts b/components.d.ts index 2bfbe44..9125675 100644 --- a/components.d.ts +++ b/components.d.ts @@ -14,6 +14,7 @@ declare module 'vue' { NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDropdown: typeof import('naive-ui')['NDropdown'] + NFloatButton: typeof import('naive-ui')['NFloatButton'] NForm: typeof import('naive-ui')['NForm'] NFormItem: typeof import('naive-ui')['NFormItem'] NGi: typeof import('naive-ui')['NGi'] diff --git a/package-lock.json b/package-lock.json index 7015ff1..b80baf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "eslint-plugin-vue": "^9.20.1", "husky": "^9.0.6", "jest": "^29.7.0", - "naive-ui": "^2.37.3", + "naive-ui": "^2.38.1", "prettier": "^3.2.4", "sass": "^1.70.0", "ts-jest": "^29.1.2", @@ -5328,9 +5328,9 @@ "dev": true }, "node_modules/naive-ui": { - "version": "2.37.3", - "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.3.tgz", - "integrity": "sha512-aUkHFXVIluSi8Me+npbcsdv1NYhVMj5t9YaruoCESlqmfqspj+R2QHEVXkTtUI1kQwVrABMCtAGq/wountqjZA==", + "version": "2.38.1", + "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.38.1.tgz", + "integrity": "sha512-AnU1FQ7K/CbhguAX++V4kCFjk7h7RvWt4nvZPRjORMpq+fUIlzD+EcQ5Cv1VqDloNF8+eMv4Akc2Ogacc9S+5A==", "dev": true, "dependencies": { "@css-render/plugin-bem": "^0.15.12", diff --git a/package.json b/package.json index 4eb0a7f..8cdf7ce 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "eslint-plugin-vue": "^9.20.1", "husky": "^9.0.6", "jest": "^29.7.0", - "naive-ui": "^2.37.3", + "naive-ui": "^2.38.1", "prettier": "^3.2.4", "sass": "^1.70.0", "ts-jest": "^29.1.2", diff --git a/src/dataSource/clients/storeClient.ts b/src/dataSource/clients/storeClient.ts new file mode 100644 index 0000000..286d571 --- /dev/null +++ b/src/dataSource/clients/storeClient.ts @@ -0,0 +1,4 @@ +import { Store } from 'tauri-plugin-store-api'; + +export const secretClient = new Store('.secrets.st'); +export const connectionClient = new Store('.connections.st'); diff --git a/src/dataSource/connectionDataSource.ts b/src/dataSource/connectionDataSource.ts new file mode 100644 index 0000000..a1de0a8 --- /dev/null +++ b/src/dataSource/connectionDataSource.ts @@ -0,0 +1,30 @@ +import { isTauri } from '../common'; +import { connectionClient } from './clients/storeClient.ts'; +import { Connection } from '../store'; + +const getConnections = async () => { + if (isTauri()) { + return connectionClient.values(); + } +}; +const saveConnection = async (connection: Connection) => { + if (isTauri()) { + await connectionClient.set(`${connection.id}`, connection); + + await connectionClient.save(); + } +}; + +const removeConnection = async ({ id }: Connection) => { + if (isTauri()) { + await connectionClient.delete(`${id}`); + + await connectionClient.save(); + } +}; + +export const loadConnectionDataSource = () => ({ + getConnections, + saveConnection, + removeConnection, +}); diff --git a/src/dataSource/index.ts b/src/dataSource/index.ts new file mode 100644 index 0000000..a09aa0d --- /dev/null +++ b/src/dataSource/index.ts @@ -0,0 +1,2 @@ +export { loadSecretsDataSource } from './secretDataSource.ts'; +export { loadConnectionDataSource } from './connectionDataSource.ts'; diff --git a/src/dataSource/secretDataSource.ts b/src/dataSource/secretDataSource.ts new file mode 100644 index 0000000..b5d89b8 --- /dev/null +++ b/src/dataSource/secretDataSource.ts @@ -0,0 +1,29 @@ +import { Secret } from '../store'; +import { isTauri } from '../common'; +import { secretClient } from './clients/storeClient.ts'; +const saveSecret = async (secret: Secret) => { + if (isTauri()) { + await secretClient.set(`${secret.id}`, secret); + + await secretClient.save(); + } +}; +const removeSecret = async (secret: Secret) => { + if (isTauri()) { + await secretClient.delete(`${secret.id}`); + + await secretClient.save(); + } +}; + +const getSecrets = async () => { + if (isTauri()) { + return secretClient.values(); + } +}; + +export const loadSecretsDataSource = () => ({ + saveSecret, + removeSecret, + getSecrets, +}); diff --git a/src/lang/enUS.ts b/src/lang/enUS.ts index 0fdbcb8..53bb268 100644 --- a/src/lang/enUS.ts +++ b/src/lang/enUS.ts @@ -25,6 +25,7 @@ export const enUS = { port: 'Port', username: 'Username', password: 'Password', + secret: 'Secret', queryParameters: 'query parameters', add: 'Add SSH', edit: 'Edit SSH', @@ -33,6 +34,7 @@ export const enUS = { nameRequired: 'Name is required', hostRequired: 'Host is required', portRequired: 'Port is required', + secretRequired: 'Secret is required', }, operations: { connect: 'Connect', diff --git a/src/lang/zhCN.ts b/src/lang/zhCN.ts index ad28cf2..feeba12 100644 --- a/src/lang/zhCN.ts +++ b/src/lang/zhCN.ts @@ -33,6 +33,7 @@ export const zhCN = { nameRequired: '请输入连接名称', hostRequired: '请输入主机地址', portRequired: '请输入端口号', + secretRequired: '请选择密钥', }, operations: { connect: '连接', @@ -63,6 +64,7 @@ export const zhCN = { pubKey: '公钥', username: '用户名', password: '密码', + secret: '密钥', verify: '验证', edit: '修改密钥', formValidation: { diff --git a/src/main.ts b/src/main.ts index 12c0e23..0e44c60 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { GiHouseKeys, FaTerminal, MdMorehoriz, + HiSolidPlus, } from 'oh-vue-icons/icons'; const app = createApp(App); @@ -41,6 +42,7 @@ addIcons( GiHouseKeys, FaTerminal, MdMorehoriz, + HiSolidPlus, ); app.component('VIcon', OhVueIcon); diff --git a/src/store/clients/secretClient.ts b/src/store/clients/secretClient.ts deleted file mode 100644 index 88f0481..0000000 --- a/src/store/clients/secretClient.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Store } from 'tauri-plugin-store-api'; -import { Secret } from '../secretStore.ts'; -import { isTauri } from '../../common'; - -const store = new Store('.secrets.st'); -const saveSecret = async (secret: Secret) => { - if (isTauri()) { - await store.set(`${secret.id}`, secret); - - await store.save(); - } -}; -const removeSecret = async (secret: Secret) => { - if (isTauri()) { - await store.delete(`${secret.id}`); - - await store.save(); - } -}; - -const getSecrets = async () => { - if (isTauri()) { - return store.values(); - } -}; - -export const secretClient = { saveSecret, removeSecret, getSecrets }; diff --git a/src/store/connectionStore.ts b/src/store/connectionStore.ts index 61d5555..3f0cea5 100644 --- a/src/store/connectionStore.ts +++ b/src/store/connectionStore.ts @@ -1,14 +1,15 @@ import { defineStore } from 'pinia'; import { loadHttpClient } from '../common/httpClient'; +import { loadConnectionDataSource } from '../dataSource'; +import { ulid } from 'ulidx'; export type Connection = { - id?: number; + id?: string; name: string; host: string; port: number; - username?: string; - password?: string; - queryParameters?: string; + username: string; + secretId: number; }; export type ConnectionIndex = { health: string; @@ -33,25 +34,19 @@ type Established = | (Connection & { indices: Array; activeIndex?: ConnectionIndex }) | null; -const buildPath = (index: string | undefined, path: string | undefined) => { - return index && - !['_nodes', '_cluster', '_cat', '_bulk', '_aliases', '_analyze'].includes( - path?.split('/')[0] ?? '', - ) - ? `/${index}/${path}` - : `/${path}`; -}; +const connectionDataSource = loadConnectionDataSource(); export const useConnectionStore = defineStore('connectionStore', { - state(): { + state: (): { connections: Connection[]; established: Established; - } { + } => { return { connections: [], established: null, }; }, + persist: true, getters: { establishedIndexNames(state) { return state.established?.indices.map(({ index }) => index) ?? []; @@ -59,20 +54,23 @@ export const useConnectionStore = defineStore('connectionStore', { }, actions: { async fetchConnections() { - this.connections = []; + const connections = (await connectionDataSource.getConnections()) || this.connections; + this.connections = connections as Connection[]; }, async testConnection(con: Connection) { const client = loadHttpClient(con); return await client.get(undefined, 'format=json'); }, - saveConnection(connection: Connection) { - const index = this.connections.findIndex(item => item.id === connection.id); - if (index >= 0) { - this.connections[index] = connection; + async saveConnection(connection: Connection) { + if (!connection.id) { + connection.id = ulid(); + this.connections.push(connection); } else { - this.connections.push({ ...connection, id: this.connections.length + 1 }); + const index = this.connections.findIndex(({ id }) => id === connection.id); + this.connections[index] = connection; } + await connectionDataSource.saveConnection(connection); }, removeConnection(connection: Connection) { this.connections = this.connections.filter(item => item.id !== connection.id); @@ -98,31 +96,5 @@ export const useConnectionStore = defineStore('connectionStore', { activeIndex: this.established?.indices.find(({ index }) => index === indexName), } as Established; }, - async searchQDSL({ - method, - path, - index, - qdsl, - }: { - method: string; - path: string; - index?: string; - qdsl?: string; - }) { - if (!this.established) throw new Error('no connection established'); - const client = loadHttpClient(this.established); - - const reqPath = buildPath(index, path); - const body = qdsl ? JSON.parse(qdsl) : undefined; - - const dispatch: { [method: string]: () => Promise } = { - POST: async () => client.post(reqPath, undefined, body), - PUT: async () => client.put(reqPath, undefined, body), - DELETE: async () => client.delete(reqPath, undefined, body), - GET: async () => - body ? client.post(reqPath, undefined, body) : client.get(reqPath, 'format=json'), - }; - return dispatch[method](); - }, }, }); diff --git a/src/store/secretStore.ts b/src/store/secretStore.ts index bde69d8..62110d7 100644 --- a/src/store/secretStore.ts +++ b/src/store/secretStore.ts @@ -1,6 +1,8 @@ import { defineStore } from 'pinia'; import { ulid } from 'ulidx'; -import { secretClient } from './clients/secretClient.ts'; +import { loadSecretsDataSource } from '../dataSource'; + +const secretDataSource = loadSecretsDataSource(); export enum SecretType { SSH_KEY = 'SSH_KEY', PASSWORD = 'PASSWORD', @@ -26,7 +28,7 @@ export const useSecretStore = defineStore('secretStore', { getters: {}, actions: { async loadSecrets() { - const secrets = (await secretClient.getSecrets()) || this.secrets; + const secrets = (await secretDataSource.getSecrets()) || this.secrets; this.secrets = secrets as Secret[]; }, async saveSecret(secret: Secret) { @@ -37,12 +39,12 @@ export const useSecretStore = defineStore('secretStore', { const index = this.secrets.findIndex(s => s.id === secret.id); this.secrets[index] = secret; } - await secretClient.saveSecret(secret); + await secretDataSource.saveSecret(secret); }, async removeSecret(secret: Secret) { const index = this.secrets.findIndex(s => s.id === secret.id); this.secrets.splice(index, 1); - await secretClient.removeSecret(secret); + await secretDataSource.removeSecret(secret); }, }, }); diff --git a/src/views/ssh/components/ssh-dialog.vue b/src/views/ssh/components/ssh-dialog.vue index 76de9a1..8f1ca77 100644 --- a/src/views/ssh/components/ssh-dialog.vue +++ b/src/views/ssh/components/ssh-dialog.vue @@ -44,8 +44,8 @@ - - + + @@ -87,7 +87,7 @@ import { useLang } from '../../../lang'; import { FormValidationError } from 'naive-ui'; import { storeToRefs } from 'pinia'; -const { testConnection, saveConnection } = useConnectionStore(); +const { testConnection, saveConnection, fetchConnections } = useConnectionStore(); const lang = useLang(); // DOM const connectFormRef = ref(); @@ -96,20 +96,20 @@ const { secrets } = storeToRefs(useSecretStore()); const options = computed(() => secrets.value.map(({ name, id }) => ({ label: name, value: id }))); -const selectedSecret = ref(''); const showModal = ref(false); const modalTitle = ref(lang.t('ssh.add')); const testLoading = ref(false); const saveLoading = ref(false); + const defaultFormData = { name: '', host: '', - port: 9200, + port: 22, username: '', - password: '', - queryParameters: '', + secretId: -1, }; -const formData = ref(defaultFormData); +const formData = ref({ ...defaultFormData }); + const formRules = reactive({ name: [ { @@ -132,6 +132,13 @@ const formRules = reactive({ trigger: ['input', 'blur'], }, ], + secret: [ + { + required: true, + renderMessage: () => lang.t('ssh.formValidation.secretRequired'), + trigger: ['input', 'blur'], + }, + ], }); const message = useMessage(); @@ -188,6 +195,8 @@ const saveConnect = async (event: MouseEvent) => { }; defineExpose({ showMedal }); + +fetchConnections(); diff --git a/src/views/ssh/components/ssh-list.vue b/src/views/ssh/components/ssh-list.vue index 33d22a2..945421a 100644 --- a/src/views/ssh/components/ssh-list.vue +++ b/src/views/ssh/components/ssh-list.vue @@ -1,33 +1,26 @@ @@ -36,6 +29,7 @@ import { storeToRefs } from 'pinia'; import { useLang } from '../../../lang'; import { Connection, useConnectionStore } from '../../../store'; import { CustomError } from '../../../common'; +import NewKeyDialog from '../../secret/components/new-key-dialog.vue'; const emits = defineEmits(['edit-connect']); @@ -50,7 +44,7 @@ const options = reactive([ ]); const connectionStore = useConnectionStore(); const { fetchConnections, removeConnection, establishConnection } = connectionStore; -const { connections, established } = storeToRefs(connectionStore); +const { connections } = storeToRefs(connectionStore); fetchConnections(); const handleSelect = (key: number, connection: Connection) => { @@ -100,81 +94,17 @@ const removeConnect = (connection: Connection) => { diff --git a/src/views/ssh/components/ssh-selector.vue b/src/views/ssh/components/ssh-selector.vue deleted file mode 100644 index 1181bba..0000000 --- a/src/views/ssh/components/ssh-selector.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - - - diff --git a/src/views/ssh/components/ssh-terminal.vue b/src/views/ssh/components/ssh-terminal.vue index 9fda411..08bbe16 100644 --- a/src/views/ssh/components/ssh-terminal.vue +++ b/src/views/ssh/components/ssh-terminal.vue @@ -1,7 +1,5 @@ - + diff --git a/src/views/ssh/index.vue b/src/views/ssh/index.vue index f9e88ab..770268d 100644 --- a/src/views/ssh/index.vue +++ b/src/views/ssh/index.vue @@ -1,49 +1,68 @@