feat : accesskey

This commit is contained in:
cxymds
2024-12-10 17:23:18 +08:00
parent 1d6f4ea715
commit df58e55bbb
10 changed files with 679 additions and 12 deletions
+9 -5
View File
@@ -1,11 +1,15 @@
<template>
<div :class="theme.name === darkTheme.name ? 'dark' : ''">
<n-config-provider :theme="theme">
<n-message-provider>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<n-message-provider>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</n-message-provider>
</n-notification-provider>
</n-dialog-provider>
</n-config-provider>
</div>
</template>
+1
View File
@@ -13,6 +13,7 @@ declare module 'vue' {
NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDropdown: typeof import('naive-ui')['NDropdown']
NFlex: typeof import('naive-ui')['NFlex']
NInput: typeof import('naive-ui')['NInput']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutFooter: typeof import('naive-ui')['NLayoutFooter']
+1 -1
View File
@@ -31,7 +31,7 @@ export default defineNuxtConfig({
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || '',
sessionToken: process.env.S3_SESSION_TOKEN || ''
}
},
}
},
i18n: {
vueI18n: './i18n.config.ts'
@@ -0,0 +1,161 @@
<script setup lang="ts">
import type { FormItemRule } from 'naive-ui'
// import { accountChangePassword } from '@/service'
interface Props {
visible: boolean
}
const { visible } = defineProps<Props>()
const emit = defineEmits<Emits>()
const defaultFormModal = {
current_secret_key: '',
new_secret_key: '',
re_new_secret_key: ''
}
const formModel = ref({ ...defaultFormModal })
// 验证规则
const rules = {
current_secret_key: [
{
required: true,
message: '请输入当前密码',
trigger: 'blur'
}
],
new_secret_key: [
{
required: true,
message: '请输入新密码',
trigger: 'blur'
}
],
re_new_secret_key: [
{
required: true,
message: '请再次输入新密码',
trigger: 'blur'
},
{
validator: validatePasswordSame,
message: '两次密码输入不一致',
trigger: ['blur', 'password-input']
}
]
}
// 再次输入密码的时候验证两次输入的密码是否一致
function validatePasswordSame(rule: FormItemRule, value: string): boolean {
return value === formModel.value.new_secret_key
}
// 输入密码时候验证与下发已经输入的重复密码是否一致
const rPasswordFormItemRef = ref()
function handlePasswordInput() {
if (formModel.value.re_new_secret_key) {
rPasswordFormItemRef.value?.validate({ trigger: 'password-input' })
}
}
// 提交确认
const formRef = ref()
function submitForm(e: MouseEvent) {
e.preventDefault()
formRef.value?.validate((errors: any) => {
if (errors) {
return
}
// accountChangePassword({
// current_secret_key: formModel.value.current_secret_key,
// new_secret_key: formModel.value.new_secret_key
// }).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// window.$message.success('操作成功')
// } else {
// window.$message.error('修改失败')
// }
// })
})
}
interface Emits {
(e: 'update:visible', visible: boolean): void
}
const modalVisible = computed({
get() {
return visible
},
set(visible) {
closeModal(visible)
}
})
function closeModal(visible = false) {
emit('update:visible', visible)
}
</script>
<template>
<n-modal
v-model:show="modalVisible"
:mask-closable="false"
preset="card"
title="修改当前账号的密码"
class="w-700px"
:segmented="{
content: true,
action: true
}">
<n-form
ref="formRef"
label-placement="left"
:model="formModel"
:rules="rules"
label-align="left"
:label-width="130">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item
:span="24"
label="当前密码"
path="current_secret_key">
<n-input
v-model:value="formModel.current_secret_key"
show-password-on="mousedown"
type="password" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="新密码" path="new_secret_key">
<n-input
ref="nPasswordFormItemRef"
v-model:value="formModel.new_secret_key"
show-password-on="mousedown"
type="password"
@input="handlePasswordInput" />
</n-form-item-grid-item>
<n-form-item-grid-item
ref="rPasswordFormItemRef"
:span="24"
label="确认新密码"
path="re_new_secret_key">
<n-input
v-model:value="formModel.re_new_secret_key"
:disabled="!formModel.new_secret_key"
show-password-on="mousedown"
type="password"
@keydown.enter.prevent />
</n-form-item-grid-item>
</n-grid>
</n-form>
<template #action>
<n-space justify="center">
<n-button @click="closeModal()">取消</n-button>
<n-button type="primary" @click="submitForm">提交</n-button>
</n-space>
</template>
</n-modal>
</template>
<style scoped>
.n-date-picker {
width: 100%;
}
</style>
+117
View File
@@ -0,0 +1,117 @@
<script setup lang="ts">
// import { getServiceAccount, updateServiceAccount } from '@/service'
import { ref } from 'vue'
const emit = defineEmits<Emits>()
const visible = ref(false)
const defaultFormModal = {
accesskey: '',
secretkey: '',
name: '',
description: '',
comment: '',
expiry: null,
policy: '',
status: false
}
const formModel = ref({ ...defaultFormModal })
const accessKey = ref<string>('')
function openDialog(accKey: string) {
accessKey.value = accKey
// getServiceAccount(btoa(unescape(encodeURIComponent(accKey)))).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// formModel.value = res.data
// visible.value = true
// }
// })
}
defineExpose({ openDialog })
interface Emits {
(e: 'search'): void
}
function closeModal() {
visible.value = false
}
function dateDisabled(ts: number) {
const date = new Date(ts)
return date < new Date()
}
function submitForm() {
// updateServiceAccount(accessKey.value, {
// policy: formModel.value.policy
// }).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// window.$message.success('添加成功')
// closeModal()
// emit('search')
// }
// })
}
</script>
<template>
<n-modal
v-model:show="visible"
:mask-closable="false"
preset="card"
title="修改Access Key"
class="w-700px"
:segmented="{
content: true,
action: true
}">
<n-form
label-placement="left"
:model="formModel"
label-align="left"
:label-width="130">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item :span="24" label="Access Key策略" path="policy">
<n-input
v-model:value="formModel.policy"
type="textarea"
placeholder="" />
</n-form-item-grid-item>
<!-- TODO: 时间格式有问题 -->
<n-form-item-grid-item :span="24" label="有效期" path="expiry">
<n-date-picker
v-model:value="formModel.expiry"
:is-date-disabled="dateDisabled"
type="datetime"
clearable />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="名称" path="name">
<n-input v-model:value="formModel.name" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="描述" path="comment">
<n-input v-model:value="formModel.comment" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="状态" path="status">
<n-switch v-model:value="formModel.status" />
</n-form-item-grid-item>
</n-grid>
</n-form>
<template #action>
<n-space justify="center">
<n-button @click="closeModal()">取消</n-button>
<n-button type="primary" @click="submitForm">提交</n-button>
</n-space>
</template>
</n-modal>
</template>
<style scoped>
.n-date-picker {
width: 100%;
}
</style>
+3
View File
@@ -0,0 +1,3 @@
export { default as ChangePassword } from './changePassword.vue'
export { default as EditItem } from './editItem.vue'
export { default as NewItem } from './newItem.vue'
+128
View File
@@ -0,0 +1,128 @@
<script setup lang="ts">
// import { createServiceAccountCreds } from '@/service'
import { computed, ref } from 'vue'
interface Props {
visible: boolean
}
const { visible } = defineProps<Props>()
const emit = defineEmits<Emits>()
const defaultFormModal = {
accesskey: '',
secretkey: '',
name: '',
description: '',
comment: '',
expiry: null,
policy: '',
flag: false
}
const formModel = ref({ ...defaultFormModal })
interface Emits {
(e: 'update:visible', visible: boolean): void
(e: 'search'): void
}
const modalVisible = computed({
get() {
return visible
},
set(visible) {
closeModal(visible)
}
})
function closeModal(visible = false) {
emit('update:visible', visible)
}
function dateDisabled(ts: number) {
const date = new Date(ts)
return date < new Date()
}
function submitForm() {
// createServiceAccountCreds(formModel.value).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// window.$message.success('添加成功')
// closeModal()
// emit('search')
// }
// })
}
</script>
<template>
<n-modal
v-model:show="modalVisible"
:mask-closable="false"
preset="card"
title="添加Access Key"
class="w-700px"
:segmented="{
content: true,
action: true
}">
<n-form
label-placement="left"
:model="formModel"
label-align="left"
:label-width="130">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item :span="24" label="Access Key" path="accesskey">
<n-input v-model:value="formModel.accesskey" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="Secret Key" path="secretkey">
<n-input
v-model:value="formModel.secretkey"
show-password-on="mousedown"
type="password" />
</n-form-item-grid-item>
<!-- TODO: 时间格式有问题 -->
<n-form-item-grid-item :span="24" label="有效期" path="expiry">
<n-date-picker
v-model:value="formModel.expiry"
:is-date-disabled="dateDisabled"
type="datetime"
clearable />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="名称" path="name">
<n-input v-model:value="formModel.name" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="描述" path="comment">
<n-input v-model:value="formModel.comment" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="注释" path="description">
<n-input v-model:value="formModel.description" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="限制超出用户策略" path="flag">
<n-switch v-model:value="formModel.flag" />
</n-form-item-grid-item>
<n-form-item-grid-item
v-if="formModel.flag"
:span="24"
label="限制超出用户策略"
path="policy">
<n-input
v-model:value="formModel.policy"
type="textarea"
placeholder="" />
</n-form-item-grid-item>
</n-grid>
</n-form>
<template #action>
<n-space justify="center">
<n-button @click="closeModal()">取消</n-button>
<n-button type="primary" @click="submitForm">提交</n-button>
</n-space>
</template>
</n-modal>
</template>
<style scoped>
.n-date-picker {
width: 100%;
}
</style>
+253
View File
@@ -0,0 +1,253 @@
<template>
<div>
<page-header>
<template #title>
<h1 class="text-2xl font-bold">AccessKey</h1>
</template>
<template #actions>
<NFlex>
<NButton type="primary" secondary @click="deleteByList">
<template #icon>
<Icon name="ri:delete-bin-5-line"></Icon>
</template>
删除选中项
</NButton>
<NButton type="info" secondary @click="changePassword">
<template #icon>
<Icon name="ri:key-2-line"></Icon>
</template>
修改秘钥
</NButton>
<NButton type="info" secondary @click="addItem">
<template #icon>
<Icon name="ri:add-line"></Icon>
</template>
新增访问秘钥
</NButton>
</NFlex>
</template>
</page-header>
<page-content>
<n-data-table
ref="tableRef"
:columns="columns"
:data="listData"
:pagination="false"
:bordered="false"
max-height="calc(100vh - 320px)"
:row-key="rowKey"
@update:checked-row-keys="handleCheck" />
</page-content>
<NewItem
ref="newItemRef"
v-model:visible="newItemVisible"
@search="getDataList" />
<EditItem ref="editItemRef" @search="getDataList" />
<ChangePassword
ref="changePasswordModalRef"
v-model:visible="changePasswordVisible"
@search="getDataList" />
</div>
</template>
<script lang="ts" setup>
import {
type DataTableColumns,
type DataTableInst,
type DataTableRowKey,
NButton,
NPopconfirm,
NSpace
} from 'naive-ui'
const { $api } = useNuxtApp()
const dialog = useDialog()
const message = useMessage()
const searchForm = reactive({
name: ''
})
interface RowData {
accessKey: string
expiration: string
name: string
description: string
accountStatus: string
actions: string
}
const columns: DataTableColumns<RowData> = [
{
type: 'selection'
},
{
title: 'Access Key',
align: 'center',
key: 'accessKey',
filter(value, row) {
return !!row.accessKey.includes(value.toString())
}
},
{
title: '有效期',
align: 'center',
key: 'expiration'
},
{
title: '状态',
align: 'center',
key: 'accountStatus',
render: (row: any) => {
return row.accountStatus === 'on' ? '可用' : '禁用'
}
},
{
title: '名称',
align: 'center',
key: 'name'
},
{
title: '描述',
align: 'center',
key: 'description'
},
{
title: '操作',
key: 'actions',
width: 150,
render: (row: any) => {
return h(
NButton,
{
strong: true,
tertiary: true,
size: 'small',
onClick: () => editItem(row)
},
{ default: () => 'Play' }
)
// return (
// <NSpace justify="center">
// <NButton
// type="info"
// size="small"
// secondary
// onClick={() => editItem(row)}>
// <n-icon>{/* <Icon name="ri:edit-2-line"></Icon> */}</n-icon>
// </NButton>
// <NPopconfirm onPositiveClick={() => deleteItem(row)}>
// {{
// default: () => '确认删除',
// trigger: () => (
// <NButton type="error" size="small" secondary>
// <n-icon>
// {/* <Icon name="ri:delete-bin-5-line"></Icon> */}
// </n-icon>
// </NButton>
// )
// }}
// </NPopconfirm>
// </NSpace>
// )
}
}
]
// 搜索过滤
const tableRef = ref<DataTableInst>()
function filterName(value: string) {
tableRef.value &&
tableRef.value.filter({
name: [value]
})
}
const listData = ref<any[]>([])
onMounted(() => {
getDataList()
})
// 获取数据
const getDataList = async () => {
try {
const res = await $api.get('/service-accounts')
console.log(res)
} catch (error) {}
// listUserServiceAccounts({})
// .then((res: any) => {
// listData.value = res.data
// })
// .finally(() => {
// endLoading()
// })
}
/** **********************************添加 */
const newItemRef = ref()
const newItemVisible = ref(false)
function addItem() {
newItemVisible.value = true
}
/** **********************************修改 */
const editItemRef = ref()
function editItem(row: any) {
editItemRef.value.openDialog(row.accessKey)
}
/** **********************************修改密码 */
const changePasswordModalRef = ref()
const changePasswordVisible = ref(false)
function changePassword() {
changePasswordVisible.value = true
}
/** ***********************************删除 */
function deleteItem(row: any) {
// deleteMultipleServiceAccounts([row.accessKey]).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// message.success('删除成功')
// getDataList()
// } else {
// message.error('删除失败')
// }
// })
}
/** ************************************批量删除 */
function rowKey(row: any): string {
return row.accessKey
}
const checkedKeys = ref<DataTableRowKey[]>([])
function handleCheck(keys: DataTableRowKey[]) {
checkedKeys.value = keys
return checkedKeys
}
function deleteByList() {
dialog.error({
title: '警告',
content: '你确定要删除所有选中的秘钥吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
// if (!checkedKeys.value.length) {
// message.error('请至少选择一项')
// return
// }
// deleteMultipleServiceAccounts(checkedKeys.value).then((res: any) => {
// const { isSuccess } = res
// if (isSuccess) {
// checkedKeys.value = []
// message.success('确定')
// // getDataList()
// } else {
// message.error('删除失败')
// }
// })
}
})
}
</script>
+1 -1
View File
@@ -1,4 +1,4 @@
import ApiClient from "~/lib/api-client"
import ApiClient from '~/lib/api-client'
export default defineNuxtPlugin((nuxtApp) => {
const token = useCookie('token')
+5 -5
View File
@@ -1,11 +1,11 @@
import { joinURL } from "ufo";
import { joinURL } from 'ufo'
export default defineEventHandler(async (event) => {
const proxyUrl = useRuntimeConfig().public.api.baseURL;
const proxyUrl = useRuntimeConfig().public.api.baseURL
const target = joinURL(proxyUrl, event.path.replace('/api', ''));
const target = joinURL(proxyUrl, event.path.replace('/api', '/api/v1'))
console.log('Proxying request to:', target);
console.log('Proxying request to:', target)
return proxyRequest(event, target);
return proxyRequest(event, target)
})