diff --git a/dashboard/src/components/ApiTokens.vue b/dashboard/src/components/ApiTokens.vue index 0221d2eae..cf42925eb 100644 --- a/dashboard/src/components/ApiTokens.vue +++ b/dashboard/src/components/ApiTokens.vue @@ -6,7 +6,7 @@ const t = i18n.t; import moment from 'moment-timezone'; import { ref, onMounted, computed, useTemplateRef } from 'vue'; -import { Button, Dialog, InputDialog, FormGroup, Radiobutton, TableView, TextInput } from '@cloudron/pankow'; +import { Button, Menu, Dialog, InputDialog, FormGroup, Radiobutton, TableView, TextInput } from '@cloudron/pankow'; import { copyToClipboard, prettyLongDate } from '@cloudron/pankow/utils'; import { TOKEN_TYPES } from '../constants.js'; import Section from './Section.vue'; @@ -48,6 +48,18 @@ const columns = { actions: {} }; +const actionMenuModel = ref([]); +const actionMenuElement = useTemplateRef('actionMenuElement'); +function onActionMenu(apiToken, event) { + actionMenuModel.value = [{ + icon: 'fa-solid fa-trash-alt', + label: t('main.action.remove'), + action: onRevokeToken.bind(null, apiToken), + }]; + + actionMenuElement.value.open(event, event.currentTarget); +} + const isValid = computed(() => { if (!tokenName.value) return false; if (!(tokenScope.value === 'r' || tokenScope.value === 'rw')) return false; @@ -92,9 +104,9 @@ function onReset() { }, 500); } -async function onRevokeToken(id, name) { +async function onRevokeToken(apiToken) { const yes = await inputDialog.value.confirm({ - message: t('profile.removeApiToken.title', { name }), + message: t('profile.removeApiToken.title', { name: apiToken.name }), confirmStyle: 'danger', confirmLabel: t('main.dialog.yes'), rejectLabel: t('main.dialog.no') @@ -102,7 +114,7 @@ async function onRevokeToken(id, name) { if (!yes) return; - const [error] = await tokensModel.remove(id); + const [error] = await tokensModel.remove(apiToken.id); if (error) return console.error(error); await refreshApiTokens(); @@ -116,6 +128,7 @@ onMounted(async () => { diff --git a/dashboard/src/components/AppPasswords.vue b/dashboard/src/components/AppPasswords.vue index 50f578b5d..060a159db 100644 --- a/dashboard/src/components/AppPasswords.vue +++ b/dashboard/src/components/AppPasswords.vue @@ -6,7 +6,7 @@ const t = i18n.t; import moment from 'moment-timezone'; import { ref, onMounted, useTemplateRef, computed } from 'vue'; -import { Button, Dialog, SingleSelect, FormGroup, TextInput, TableView, InputDialog } from '@cloudron/pankow'; +import { Button, Menu, Dialog, SingleSelect, FormGroup, TextInput, TableView, InputDialog } from '@cloudron/pankow'; import { prettyLongDate, copyToClipboard } from '@cloudron/pankow/utils'; import Section from './Section.vue'; import AppPasswordsModel from '../models/AppPasswordsModel.js'; @@ -40,6 +40,18 @@ const columns = { actions: {} }; +const actionMenuModel = ref([]); +const actionMenuElement = useTemplateRef('actionMenuElement'); +function onActionMenu(appPassword, event) { + actionMenuModel.value = [{ + icon: 'fa-solid fa-trash-alt', + label: t('main.action.remove'), + action: onRemove.bind(null, appPassword), + }]; + + actionMenuElement.value.open(event, event.currentTarget); +} + // new dialog props const addedPassword = ref(''); const passwordName = ref(''); @@ -99,9 +111,9 @@ function onCopyToClipboard(password) { window.pankow.notify({ type: 'success', text: 'Password copied!' }); } -async function onRemove(id, name) { +async function onRemove(appPassword) { const yes = await inputDialog.value.confirm({ - message: t('profile.removeAppPassword.title', { name }), + message: t('profile.removeAppPassword.title', { name: appPassword.name }), confirmStyle: 'danger', confirmLabel: t('main.dialog.yes'), rejectLabel: t('main.dialog.no') @@ -109,7 +121,7 @@ async function onRemove(id, name) { if (!yes) return; - const [error] = await appPasswordsModel.remove(id); + const [error] = await appPasswordsModel.remove(appPassword.id); if (error) return console.error(error); await refresh(); @@ -149,6 +161,7 @@ onMounted(async () => {