diff --git a/frontend/src/composables/useSearchPersistence.ts b/frontend/src/composables/useSearchPersistence.ts new file mode 100644 index 000000000..fcef3d923 --- /dev/null +++ b/frontend/src/composables/useSearchPersistence.ts @@ -0,0 +1,63 @@ +import { storeToRefs } from 'pinia'; +import { watch, type Ref } from 'vue'; +import GlobalStore from '@/store/modules/global'; + +type SearchValue = string | number | undefined; +type SearchFields = Record>; + +export const useSearchPersistence = (page: string, fields: SearchFields, scope?: () => SearchValue) => { + const { currentNode } = storeToRefs(GlobalStore()); + const defaults = Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value])); + let initialized = false; + let restoring = false; + let activeKey: string | undefined; + + watch( + () => { + const context = scope?.(); + if (scope && context === undefined) return undefined; + return `1panel:search:v1:${JSON.stringify([currentNode.value, page, context ?? ''])}`; + }, + (key) => { + activeKey = key; + let saved: Record = {}; + try { + const parsed = activeKey ? JSON.parse(localStorage.getItem(activeKey) || '{}') : {}; + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) saved = parsed; + } catch {} + restoring = true; + for (const [name, field] of Object.entries(fields)) { + const value = saved[name]; + const initial = defaults[name]; + let fallback = initial; + if (initialized) { + fallback = typeof initial === 'string' ? '' : undefined; + } + const valid = + (typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value))) && + (fallback === undefined || typeof value === typeof fallback); + if (value === null) { + field.value = typeof initial === 'string' ? '' : undefined; + } else if (valid) { + field.value = value as SearchValue; + } else { + field.value = fallback; + } + } + restoring = false; + initialized = true; + }, + { immediate: true, flush: 'sync' }, + ); + + watch( + () => Object.fromEntries(Object.entries(fields).map(([name, field]) => [name, field.value ?? null])), + (value) => { + if (restoring || !activeKey) return; + try { + localStorage.setItem(activeKey, JSON.stringify(value)); + } catch {} + }, + { flush: 'sync' }, + ); +}; diff --git a/frontend/src/store/modules/process.ts b/frontend/src/store/modules/process.ts index f2795fd5f..13a5a4ece 100644 --- a/frontend/src/store/modules/process.ts +++ b/frontend/src/store/modules/process.ts @@ -1,5 +1,6 @@ +import { useSearchPersistence } from '@/composables/useSearchPersistence'; import { defineStore } from 'pinia'; -import { ref, reactive } from 'vue'; +import { ref, reactive, toRef } from 'vue'; import { MsgError } from '@/utils/message'; import { checkStreamAuth } from '@/utils/stream-auth'; @@ -46,6 +47,17 @@ export const ProcessStore = defineStore('ProcessStore', () => { port: undefined, }); + useSearchPersistence('host/process/process', { + pid: toRef(psSearch, 'pid'), + name: toRef(psSearch, 'name'), + username: toRef(psSearch, 'username'), + }); + useSearchPersistence('host/process/network', { + processID: toRef(netSearch, 'processID'), + processName: toRef(netSearch, 'processName'), + port: toRef(netSearch, 'port'), + }); + let pendingRequestType: 'ps' | 'net' | null = null; let queuedRequestType: 'ps' | 'net' | null = null; diff --git a/frontend/src/views/ai/agents/agent/config/tabs/plugins.vue b/frontend/src/views/ai/agents/agent/config/tabs/plugins.vue index 5187d3580..1a7994868 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/plugins.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/plugins.vue @@ -172,6 +172,7 @@