Continue frontend building

This commit is contained in:
2025-07-13 12:03:43 +02:00
parent 6faed0dbd4
commit 746f809d28
44 changed files with 2057 additions and 632 deletions

View File

@ -0,0 +1,194 @@
<template>
<div class="flex flex-col h-full min-h-0">
<div class="tree-header mb-2 grid grid-cols-2 place-content-between">
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">Secrets</h1>
<div class="flex">
<div class="flex w-full justify-end">
<sl-dropdown>
<sl-icon-button slot="trigger" name="plus-square" label="Add Secret"></sl-icon-button>
<sl-menu>
<sl-menu-item @click="createSecretDrawer = true">Create secret</sl-menu-item>
<sl-menu-item @click="createGroupDrawer = true">
<span v-if="currentPath">Create subgroup</span>
<span v-else>Create group</span>
</sl-menu-item>
</sl-menu>
</sl-dropdown>
</div>
</div>
<div class="col-span-full">
<!-- The search would have gone here... -->
&nbsp;
</div>
<div id="secret-tree-items" class="flex flex-col h-full min-h-0 col-span-full">
<div class="flex-1 overflow-y-auto">
<sl-tree class="w-full" @sl-selection-change="itemSelected" v-if="treeState.secretGroups">
<template v-if="ungrouped">
<SecretGroupTreeItem path="ungrouped" name="Ungrouped" groupPath="ungrouped">
<template v-for="entry in ungrouped">
<SecretGroupTreeEntry :name="entry.name" groupPath="ungrouped" />
</template>
</SecretGroupTreeItem>
</template>
<template v-if="secretGroups">
<SecretGroup v-for="group in secretGroups" :group="group" />
</template>
</sl-tree>
</div>
<!-- pagination would go here -->
</div>
</div>
</div>
<Drawer label="Create Group" :open="createGroupDrawer" @hide="createGroupDrawer = false">
<AddGroup
:parent="currentPath"
@submit="createGroup"
@cancel="cancelCreateGroup"
:key="drawerKey"
/>
</Drawer>
<Drawer label="Create Secret" :open="createSecretDrawer" @hide="createSecretDrawer = false">
<SecretForm
:key="createDrawerKey"
:group="createSecretParent"
@submit="createSecret"
@cancel="createSecretDrawer = false"
/>
</Drawer>
</template>
<script setup lang="ts">
import type { SshecretObject } from '@/api/types'
import type { SecretCreate } from '@/client'
import { computed, ref, reactive, onMounted, watch } from 'vue'
import { useTreeState } from '@/store/useTreeState'
import { useAlertsStore } from '@/store/useAlertsStore'
import { SshecretAdmin } from '@/client'
import { SshecretObjectType } from '@/api/types'
import SecretGroup from '@/components/secrets/SecretGroup.vue'
import SecretGroupTreeItem from '@/components/secrets/SecretGroupTreeItem.vue'
import SecretGroupTreeEntry from '@/components/secrets/SecretGroupTreeEntry.vue'
import AddGroup from '@/components/secrets/AddGroup.vue'
import SecretForm from '@/components/secrets/SecretForm.vue'
import Drawer from '@/components/common/Drawer.vue'
const treeState = useTreeState()
const alerts = useAlertsStore()
const ungrouped = computed(() => treeState.secretGroups.ungrouped)
const secretGroups = computed(() => treeState.secretGroups.groups)
const groupSelected = computed(() => {
if (!treeState.selected) {
return false
}
if (treesState.selected.objectType === SshecretObject.SecretGroup) {
return true
}
return false
})
const drawerKey = ref(0)
const createDrawerKey = ref(0)
const createGroupDrawer = ref<boolean>(false)
const createSecretDrawer = ref<boolean>(false)
function cancelCreateGroup() {
createGroupDrawer.value = false
drawerKey.value += 1
}
const currentPath = computed(() => {
if (treeState.selected && treeState.selected.objectType === SshecretObjectType.SecretGroup) {
return treeState.selected.id
}
return ''
})
const createSecretParent = computed(() => {
if (treeState.selected && treeState.selected.objectType === SshecretObjectType.SecretGroup) {
return treeState.selected.id
}
return null
})
async function loadGroups() {
await treeState.getSecretGroups()
}
const drawerKeyName = computed(() => `${currentPath.value}_${drawerKey.value}`)
async function itemSelected(event: Event) {
if (event.detail.selection.length == 0) {
treeState.unselect()
} else {
const el = event.detail.selection[0] as HTMLElement
const childType = el.dataset.type
if (childType === 'secret') {
const secretName = el.dataset.name
treeState.selectSecret(secretName, null)
} else if (childType === 'group') {
const groupPath = el.dataset.groupPath
if (groupPath === 'ungrouped') {
treeState.unselect()
} else {
treeState.selectGroup(groupPath)
}
}
}
}
async function createGroup(path: string) {
// Create a group
console.log('Submit called')
const response = await SshecretAdmin.addSecretGroupApiV1SecretsGroupsPost({
body: {
name: path,
},
})
if (response.status === 200) {
console.log('Success. Group created.')
alerts.showAlert('Group created', 'success')
createGroupDrawer.value = false
drawerKey.value += 1
await loadGroups()
} else {
console.error(response)
alerts.showAlert('Group creation failed', 'error')
}
}
async function createSecret(secretCreate: SecretCreate) {
console.log('Creating secret')
const response = await SshecretAdmin.addSecretApiV1SecretsPost({
body: secretCreate,
})
if (response.status == 200) {
alerts.showAlert('Secret created', 'success')
// We can close the drawer now.
createSecretDrawer.value = false
createDrawerKey.value += 1
await loadGroups()
// Also update all the clients affected
for (const clientId in secretCreate.clients) {
console.log('Refreshing client: ', clientId)
await treeState.refreshClient(clientId)
}
treeState.selectSecret(secretCreate.name)
} else {
console.log(response)
alerts.showAlert('Secret creation failed', 'error')
}
}
watch(
() => treeState.secretGroupRevision,
() => {
treeState.getSecretGroups()
},
)
onMounted(loadGroups)
</script>