diff --git a/package.json b/package.json index 05c379c..805e4a1 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "dependencies": { "sweetalert2": "^11.26.3", - "vue": "^3.4.15" + "vue": "^3.4.15", + "vue-router": "^4.6.3" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a95fb7..48a6a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: vue: specifier: ^3.4.15 version: 3.5.22(typescript@5.9.3) + vue-router: + specifier: ^4.6.3 + version: 4.6.3(vue@3.5.22(typescript@5.9.3)) devDependencies: '@typescript-eslint/eslint-plugin': specifier: ^6.14.0 @@ -494,6 +497,9 @@ packages: '@vue/compiler-ssr@3.5.22': resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/eslint-config-typescript@12.0.0': resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==} engines: {node: ^14.17.0 || >=16.0.0} @@ -1345,6 +1351,11 @@ packages: peerDependencies: eslint: '>=6.0.0' + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + vue-template-compiler@2.7.16: resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} @@ -1757,6 +1768,8 @@ snapshots: '@vue/compiler-dom': 3.5.22 '@vue/shared': 3.5.22 + '@vue/devtools-api@6.6.4': {} + '@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@9.33.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) @@ -2621,6 +2634,11 @@ snapshots: transitivePeerDependencies: - supports-color + vue-router@4.6.3(vue@3.5.22(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.22(typescript@5.9.3) + vue-template-compiler@2.7.16: dependencies: de-indent: 1.0.2 diff --git a/src/App.vue b/src/App.vue index d90f05e..f2e850a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,8 +3,10 @@ import { ref, onMounted, watch } from 'vue' import type { VaultServer, VaultCredentials, VaultConnection } from './types' import ServerSelector from './components/ServerSelector.vue' import LoginForm from './components/LoginForm.vue' -import Dashboard from './components/Dashboard.vue' +import RouterWrapper from './components/RouterWrapper.vue' import PolicyModal from './components/PolicyModal.vue' +import GlobalSearch from './components/GlobalSearch.vue' +import SecretModal from './components/SecretModal.vue' import { useSweetAlert } from './composables/useSweetAlert' import { usePolicyModal } from './composables/usePolicyModal' @@ -15,6 +17,10 @@ const activeConnection = ref(null) const { error } = useSweetAlert() const { modalState, closePolicyModal } = usePolicyModal() +// Global search state +const showSecretModal = ref(false) +const selectedSecretPath = ref('') + // Load servers from localStorage on mount onMounted(() => { const savedServers = localStorage.getItem('vaultServers') @@ -93,6 +99,30 @@ const handleLogin = async (credentials: VaultCredentials, shouldSaveCredentials: const handleLogout = () => { activeConnection.value = null } + +const handleOpenSecret = (path: string) => { + selectedSecretPath.value = path + showSecretModal.value = true +} + +const handleClearCredentials = (serverId: string) => { + const serverIndex = servers.value.findIndex(s => s.id === serverId) + if (serverIndex !== -1 && servers.value[serverIndex].savedCredentials) { + delete servers.value[serverIndex].savedCredentials + console.log(`✓ Cleared saved credentials for server: ${servers.value[serverIndex].name}`) + } +} + +const handleClearAllCredentials = () => { + let clearedCount = 0 + servers.value.forEach(server => { + if (server.savedCredentials) { + delete server.savedCredentials + clearedCount++ + } + }) + console.log(`✓ Cleared saved credentials for ${clearedCount} server(s)`) +} diff --git a/src/components/Dashboard.vue b/src/components/Dashboard.vue index a5db7dc..576b300 100644 --- a/src/components/Dashboard.vue +++ b/src/components/Dashboard.vue @@ -1,9 +1,7 @@ diff --git a/src/components/GlobalSearch.vue b/src/components/GlobalSearch.vue new file mode 100644 index 0000000..f1d6bbd --- /dev/null +++ b/src/components/GlobalSearch.vue @@ -0,0 +1,232 @@ + + + diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue index 6003a9e..8006745 100644 --- a/src/components/LoginForm.vue +++ b/src/components/LoginForm.vue @@ -10,9 +10,10 @@ interface Props { const props = defineProps() const emit = defineEmits<{ login: [credentials: VaultCredentials, saveCredentials: boolean] + clearCredentials: [serverId: string] }>() -const { error } = useSweetAlert() +const { confirm, error } = useSweetAlert() const authMethod = ref<'token' | 'userpass' | 'ldap'>('token') const token = ref('') @@ -90,6 +91,17 @@ const confirmSaveCredentials = () => { performLogin() } +const handleClearCredentials = async (serverId: string, serverName: string) => { + const result = await confirm(`Clear saved credentials for "${serverName}"?`, 'Clear Credentials') + if (result.isConfirmed) { + emit('clearCredentials', serverId) + token.value = '' + username.value = '' + password.value = '' + saveCredentials.value = false + } +} + const cancelSaveCredentials = () => { showSecurityWarning.value = false saveCredentials.value = false @@ -190,6 +202,11 @@ const cancelSaveCredentials = () => { + + diff --git a/src/components/RouterWrapper.vue b/src/components/RouterWrapper.vue new file mode 100644 index 0000000..2ab23d3 --- /dev/null +++ b/src/components/RouterWrapper.vue @@ -0,0 +1,16 @@ + + + diff --git a/src/components/SecretBrowser.vue b/src/components/SecretBrowser.vue new file mode 100644 index 0000000..520dcff --- /dev/null +++ b/src/components/SecretBrowser.vue @@ -0,0 +1,312 @@ + + + diff --git a/src/components/ServerSelector.vue b/src/components/ServerSelector.vue index 577289c..33ea2d3 100644 --- a/src/components/ServerSelector.vue +++ b/src/components/ServerSelector.vue @@ -13,6 +13,7 @@ const emit = defineEmits<{ addServer: [server: VaultServer] removeServer: [serverId: string] selectServer: [server: VaultServer] + clearAllCredentials: [] }>() const { confirm } = useSweetAlert() @@ -45,6 +46,18 @@ const handleRemove = async (serverId: string, serverName: string) => { emit('removeServer', serverId) } } + +const handleClearAllCredentials = async () => { + const serversWithCredentials = props.servers.filter(s => s.savedCredentials) + if (serversWithCredentials.length === 0) { + return + } + + const result = await confirm(`Clear saved credentials for all ${serversWithCredentials.length} server(s)?`, 'Clear All Credentials') + if (result.isConfirmed) { + emit('clearAllCredentials') + } +} diff --git a/src/main.ts b/src/main.ts index 2425c0f..7b19912 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,6 @@ import { createApp } from 'vue' import './style.css' import App from './App.vue' +import router from './router' -createApp(App).mount('#app') +createApp(App).use(router).mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..98b045c --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,34 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Dashboard from '../components/Dashboard.vue' +import SecretBrowser from '../components/SecretBrowser.vue' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'dashboard', + component: Dashboard, + }, + { + path: '/browse/:mount', + name: 'secret-browser-root', + component: SecretBrowser, + props: route => ({ + mount: route.params.mount, + path: undefined, + }), + }, + { + path: '/browse/:mount/:path+', + name: 'secret-browser', + component: SecretBrowser, + props: route => ({ + mount: route.params.mount, + path: Array.isArray(route.params.path) ? route.params.path.join('/') : route.params.path, + }), + }, + ], +}) + +export default router