2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 13:47:15 +08:00

feat: 支持创建access_token

This commit is contained in:
2025-05-15 02:18:35 +08:00
parent 103529a9b4
commit a7dd9d411b
25 changed files with 708 additions and 22 deletions

View File

@@ -4,7 +4,8 @@ export default {
// 获取注册表配置
registryConfig: (): any => http.Get('/apps/podman/registry_config'),
// 保存注册表配置
saveRegistryConfig: (config: string): any => http.Post('/apps/podman/registry_config', { config }),
saveRegistryConfig: (config: string): any =>
http.Post('/apps/podman/registry_config', { config }),
// 获取存储配置
storageConfig: (): any => http.Get('/apps/podman/storage_config'),
// 保存存储配置

View File

@@ -27,7 +27,8 @@ export default {
serverRemark: (id: number, remark: string) =>
http.Put(`/database_server/${id}/remark`, { remark }),
// 获取数据库用户列表
userList: (page: number, limit: number) => http.Get('/database_user', { params: { page, limit } }),
userList: (page: number, limit: number) =>
http.Get('/database_user', { params: { page, limit } }),
// 创建数据库用户
userCreate: (data: any) => http.Post('/database_user', data),
// 获取数据库用户

View File

@@ -35,5 +35,17 @@ export default {
generateTwoFA: (id: number): any => http.Get(`/users/${id}/2fa`),
// 保存2FA密钥
updateTwoFA: (id: number, code: string, secret: string): any =>
http.Post(`/users/${id}/2fa`, { code, secret })
http.Post(`/users/${id}/2fa`, { code, secret }),
// 获取用户Token列表
tokenList: (user_id: number, page: number, limit: number): any =>
http.Get(`/user_tokens`, { params: { user_id, page, limit } }),
// 创建用户Token
tokenCreate: (user_id: number, ips: string[], expired_at: number): any =>
http.Post('/user_tokens', { user_id, ips, expired_at }),
// 删除用户Token
tokenDelete: (id: number): any => http.Delete(`/user_tokens/${id}`),
// 更新用户Token
tokenUpdate: (id: number, ips: string[], expired_at: number): any =>
http.Put(`/user_tokens/${id}`, { ips, expired_at })
}

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import user from '@/api/panel/user'
import { NButton, NInput } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const model = ref({
username: '',
password: '',
email: ''
})
const handleCreate = () => {
useRequest(() =>
user.create(model.value.username, model.value.password, model.value.email)
).onSuccess(() => {
show.value = false
window.$message.success($gettext('Created successfully'))
window.$bus.emit('user:refresh')
model.value.username = ''
model.value.password = ''
model.value.email = ''
})
}
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
:title="$gettext('Create User')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-form :model="model">
<n-form-item path="username" :label="$gettext('Username')">
<n-input
v-model:value="model.username"
@keydown.enter.prevent
:placeholder="$gettext('Enter user name')"
/>
</n-form-item>
<n-form-item path="password" :label="$gettext('Password')">
<n-input
v-model:value="model.password"
type="password"
show-password-on="click"
@keydown.enter.prevent
:placeholder="$gettext('Enter user password')"
/>
</n-form-item>
<n-form-item path="email" :label="$gettext('Email')">
<n-input
v-model:value="model.email"
@keydown.enter.prevent
:placeholder="$gettext('Enter user email')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">{{ $gettext('Submit') }}</n-button>
</n-modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -8,6 +8,7 @@ import { useGettext } from 'vue3-gettext'
import setting from '@/api/panel/setting'
import TheIcon from '@/components/custom/TheIcon.vue'
import { useThemeStore } from '@/store'
import CreateModal from '@/views/setting/CreateModal.vue'
import SettingBase from '@/views/setting/SettingBase.vue'
import SettingSafe from '@/views/setting/SettingSafe.vue'
import SettingUser from '@/views/setting/SettingUser.vue'
@@ -15,6 +16,7 @@ import SettingUser from '@/views/setting/SettingUser.vue'
const { $gettext } = useGettext()
const themeStore = useThemeStore()
const currentTab = ref('base')
const createModal = ref(false)
const { data: model } = useRequest(setting.list, {
initialData: {
@@ -50,7 +52,9 @@ const handleSave = () => {
})
}
const handleCreate = () => {}
const handleCreate = () => {
createModal.value = true
}
</script>
<template>
@@ -77,6 +81,7 @@ const handleCreate = () => {}
</n-tab-pane>
</n-tabs>
</common-page>
<create-modal v-model:show="createModal" />
</template>
<style scoped lang="scss"></style>

View File

@@ -2,6 +2,7 @@
import user from '@/api/panel/user'
import { formatDateTime, renderIcon } from '@/utils'
import PasswordModal from '@/views/setting/PasswordModal.vue'
import TokenModal from '@/views/setting/TokenModal.vue'
import TwoFaModal from '@/views/setting/TwoFaModal.vue'
import { NButton, NDataTable, NInput, NPopconfirm, NSwitch } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
@@ -11,6 +12,7 @@ const { $gettext } = useGettext()
const currentID = ref(0)
const passwordModal = ref(false)
const twoFaModal = ref(false)
const tokenModal = ref(false)
const columns: any = [
{
@@ -73,7 +75,7 @@ const columns: any = [
{
title: $gettext('Actions'),
key: 'actions',
width: 260,
width: 380,
hideInExcel: true,
render(row: any) {
return [
@@ -82,6 +84,22 @@ const columns: any = [
{
size: 'small',
type: 'primary',
onClick: () => {
currentID.value = row.id
tokenModal.value = true
}
},
{
default: () => $gettext('Access Token'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
}
),
h(
NButton,
{
size: 'small',
type: 'primary',
style: 'margin-left: 15px;',
onClick: () => {
currentID.value = row.id
passwordModal.value = true
@@ -176,6 +194,7 @@ onMounted(() => {
</n-flex>
<password-modal v-model:id="currentID" v-model:show="passwordModal" />
<two-fa-modal v-model:id="currentID" v-model:show="twoFaModal" />
<token-modal v-model:id="currentID" v-model:show="tokenModal" />
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,307 @@
<script setup lang="ts">
import user from '@/api/panel/user'
import { formatDateTime, renderIcon } from '@/utils'
import copy2clipboard from '@vavt/copy2clipboard'
import { NAlert, NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
import { useGettext } from 'vue3-gettext'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const id = defineModel<number>('id', { type: Number, required: true })
const createModal = ref(false)
const updateModal = ref(false)
const currentID = ref(0)
const createModel = ref({
ips: [] as Array<string>,
expired_at: new Date().getTime() + 31536000 * 1000 // 1 year
})
const updateModel = ref({
ips: [] as Array<string>,
expired_at: new Date().getTime() + 31536000 * 1000 // 1 year
})
const columns: any = [
{
title: $gettext('ID'),
key: 'id',
width: 100,
resizable: true,
ellipsis: { tooltip: true }
},
{
title: $gettext('Creation Time'),
key: 'created_at',
minWidth: 200,
ellipsis: { tooltip: true },
render(row: any) {
return formatDateTime(row.created_at)
}
},
{
title: $gettext('Expiration Time'),
key: 'expired_at',
minWidth: 200,
ellipsis: { tooltip: true },
render(row: any) {
return formatDateTime(row.expired_at)
}
},
{
title: $gettext('Actions'),
key: 'actions',
width: 260,
hideInExcel: true,
render(row: any) {
return [
h(
NButton,
{
size: 'small',
type: 'primary',
onClick: () => {
currentID.value = row.id
updateModal.value = true
}
},
{
default: () => $gettext('Change'),
icon: renderIcon('material-symbols:edit-outline', { size: 14 })
}
),
h(
NPopconfirm,
{
style: 'margin-left: 15px;',
onPositiveClick: () => handleDelete(row.id)
},
{
default: () => {
return $gettext('Are you sure you want to delete this access token?')
},
trigger: () => {
return h(
NButton,
{
size: 'small',
type: 'error',
style: 'margin-left: 15px;'
},
{
default: () => $gettext('Delete'),
icon: renderIcon('material-symbols:delete-outline', { size: 14 })
}
)
}
}
)
]
}
}
]
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
(page, pageSize) => user.tokenList(id.value, page, pageSize),
{
initialData: { total: 0, list: [] },
initialPageSize: 20,
total: (res: any) => res.total,
data: (res: any) => res.items
}
)
const handleDelete = (id: number) => {
useRequest(() => user.tokenDelete(id)).onSuccess(() => {
window.$message.success($gettext('Deleted successfully'))
refresh()
})
}
const handleCreate = () => {
useRequest(() =>
user.tokenCreate(id.value, createModel.value.ips, createModel.value.expired_at)
).onSuccess(({ data }) => {
createModal.value = false
window.$dialog.success({
title: $gettext('Created successfully'),
content: () => {
return [
h(
NFlex,
{
vertical: true
},
{
default: () => [
h(
NAlert,
{
type: 'warning'
},
{
default: () =>
$gettext(
'Token is only displayed once, please save it before closing the dialog'
)
}
),
h(NInput, {
value: data.token,
type: 'password',
showPasswordOn: 'click',
readonly: true
})
]
}
)
]
},
maskClosable: false,
positiveText: $gettext('Copy and close'),
onPositiveClick: () => {
copy2clipboard(data.token)
.then(() => {
window.$message.success($gettext('Copied successfully'))
})
.catch(() => {
window.$message.error($gettext('Copy failed'))
})
.finally(() => {
createModal.value = false
})
}
})
refresh()
})
}
const handleUpdate = () => {
useRequest(() =>
user.tokenUpdate(currentID.value, updateModel.value.ips, updateModel.value.expired_at)
).onSuccess(() => {
window.$message.success($gettext('Updated successfully'))
updateModal.value = false
refresh()
})
}
watch(
() => show.value,
(val) => {
if (val) {
refresh()
}
},
{ immediate: true }
)
</script>
<template>
<n-modal
v-model:show="show"
preset="card"
:title="$gettext('Access Token')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-flex vertical>
<n-flex>
<n-button type="primary" @click="createModal = true">
{{ $gettext('Create Access Token') }}
</n-button>
</n-flex>
<n-data-table
striped
remote
:scroll-x="600"
:loading="loading"
:columns="columns"
:data="data"
:row-key="(row: any) => row.name"
v-model:page="page"
v-model:pageSize="pageSize"
:pagination="{
page: page,
pageCount: pageCount,
pageSize: pageSize,
itemCount: total,
showQuickJumper: true,
showSizePicker: true,
pageSizes: [20, 50, 100, 200]
}"
/>
</n-flex>
</n-modal>
<n-modal
v-model:show="createModal"
preset="card"
:title="$gettext('Create Access Token')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="createModal = false"
>
<n-flex vertical>
<n-form>
<n-form-item :label="$gettext('IP White List')">
<n-dynamic-input
v-model:value="createModel.ips"
:placeholder="$gettext('127.0.0.1')"
show-sort-button
/>
</n-form-item>
<n-form-item :label="$gettext('Expiration Time')">
<n-date-picker
v-model:value="createModel.expired_at"
type="datetime"
placeholder="$gettext('Please select the expiration time')"
w-full
/>
</n-form-item>
</n-form>
<n-button type="primary" @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-flex>
</n-modal>
<n-modal
v-model:show="updateModal"
preset="card"
:title="$gettext('Update Access Token')"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="updateModal = false"
>
<n-flex vertical>
<n-form>
<n-form-item :label="$gettext('IP White List')">
<n-dynamic-input
v-model:value="updateModel.ips"
:placeholder="$gettext('127.0.0.1')"
show-sort-button
/>
</n-form-item>
<n-form-item :label="$gettext('Expiration Time')">
<n-date-picker
v-model:value="updateModel.expired_at"
type="datetime"
placeholder="$gettext('Please select the expiration time')"
w-full
/>
</n-form-item>
</n-form>
<n-button type="primary" @click="handleUpdate">
{{ $gettext('Update') }}
</n-button>
</n-flex>
</n-modal>
</template>
<style scoped lang="scss"></style>

View File

@@ -63,7 +63,7 @@ watch(
}}
</n-text>
<n-text style="max-width: 400px; word-break: break-all">
{{ model.url }}
<a :href="model.url" target="_blank">{{ model.url }}</a>
</n-text>
</n-flex>
</n-flex>