feat : 用户 用户组状态

This commit is contained in:
cxymds
2025-02-23 01:52:46 +08:00
parent bce2a60f9a
commit 6d711726e2
12 changed files with 630 additions and 434 deletions
+110 -72
View File
@@ -1,67 +1,3 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { makeRandomString } from '~/utils/functions';
interface Props {
visible: boolean;
}
const { visible } = defineProps<Props>();
const message = useMessage();
const { $api } = useNuxtApp();
const { createServiceAccountCreds } = useAccessKeys();
const emit = defineEmits<Emits>();
const defaultFormModal = {
accesskey: makeRandomString(20),
secretkey: makeRandomString(40),
name: '',
description: '',
comment: '',
expiry: null,
policy: '',
flag: false,
};
const formModel = ref({ ...defaultFormModal });
interface Emits {
(e: 'update:visible', visible: boolean): void;
(e: 'search'): void;
(e: 'notice', data: object): 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();
}
async function submitForm() {
try {
const res = await createServiceAccountCreds({
...formModel.value,
expiry: new Date(formModel.value.expiry || '').toISOString(),
});
message.success('添加成功');
emit('notice', res);
closeModal();
emit('search');
} catch (error) {
message.error('添加失败');
}
}
</script>
<template>
<n-modal
v-model:show="modalVisible"
@@ -71,17 +7,32 @@ async function submitForm() {
class="max-w-screen-md"
:segmented="{
content: true,
action: true,
action: true
}">
<n-card>
<n-form label-placement="left" :model="formModel" label-align="center" :label-width="130">
<n-form
label-placement="left"
:model="formModel"
label-align="center"
: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-input
v-model:value="formModel.secretkey"
show-password-on="mousedown"
type="password" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="策略" path="policy">
<n-select
v-model:value="formModel.policy"
filterable
multiple
:options="polices" />
</n-form-item-grid-item>
<!-- TODO: 时间格式有问题 -->
<n-form-item-grid-item :span="24" label="有效期" path="expiry">
<n-date-picker
@@ -95,18 +46,25 @@ async function submitForm() {
<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-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> -->
<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-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-form-item-grid-item
v-if="formModel.flag"
:span="24"
label="策略详情"
path="policy">
<json-editor v-model="formModel.policy" />
</n-form-item-grid-item>
</n-form-item-grid-item> -->
</n-grid>
</n-form>
</n-card>
@@ -118,5 +76,85 @@ async function submitForm() {
</template>
</n-modal>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { makeRandomString } from '~/utils/functions'
interface Props {
visible: boolean
}
const { visible } = defineProps<Props>()
const message = useMessage()
const { $api } = useNuxtApp()
const { createServiceAccountCreds } = useAccessKeys()
const { listPolicies } = usePolicies()
const { credentials } = useAuth()
const emit = defineEmits<Emits>()
const defaultFormModal = {
accesskey: makeRandomString(20),
secretkey: makeRandomString(40),
name: '',
description: '',
comment: '',
expiry: null,
policy: '',
flag: false
}
const formModel = ref({ ...defaultFormModal })
interface Emits {
(e: 'update:visible', visible: boolean): void
(e: 'search'): void
(e: 'notice', data: object): 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()
}
async function submitForm() {
try {
const res = await createServiceAccountCreds({
...formModel.value,
targetUser: credentials.value?.AccessKeyId,
status: 'enabled'
// expiry: new Date(formModel.value.expiry || '').toISOString()
})
message.success('添加成功')
emit('notice', res)
closeModal()
emit('search')
} catch (error) {
message.error('添加失败')
}
}
// 策略列表
const polices = ref<any[]>([])
const getPoliciesList = async () => {
const res = await listPolicies()
polices.value = Object.keys(res).map((key) => {
return {
label: key,
value: key
}
})
}
getPoliciesList()
</script>
<style scoped></style>
+2 -2
View File
@@ -40,7 +40,7 @@
<script setup lang="ts">
// import { groupMembers, groupPolicies } from './';
const visible = ref(false)
const { getGroup, updateGroup } = useGroups()
const { getGroup, updateGroupStatus } = useGroups()
interface GroupInfo {
name: string
@@ -56,7 +56,7 @@ const group = ref<GroupInfo>({
// 用户组的状态发生变化
const handerGroupStatusChange = async (val: string) => {
await updateGroup(group.value.name, { ...group.value, status: val })
await updateGroupStatus(group.value.name, { ...group.value, status: val })
await getGroupData(group.value.name)
}
async function openDialog(row: any) {
-1
View File
@@ -124,7 +124,6 @@ const members = ref(props.group.members)
const changeMebers = async () => {
try {
// 删除不存在的
await updateGroupMembers({
group: props.group.name,
members: props.group.members.filter((item: string) => {
+31 -21
View File
@@ -8,15 +8,19 @@
class="max-w-screen-md"
:segmented="{
content: true,
action: true,
action: true
}">
<n-card>
<n-tabs type="card">
<n-tab-pane name="groups" tab="分组">
<users-user-groups :user="user" @search="getUserData(user.accessKey)"></users-user-groups>
<users-user-groups
:user="user"
@search="getUserData(user.accessKey)"></users-user-groups>
</n-tab-pane>
<n-tab-pane name="policy" tab="策略">
<users-user-policies :user="user" @search="getUserData(user.accessKey)"></users-user-policies>
<users-user-policies
:user="user"
@search="getUserData(user.accessKey)"></users-user-policies>
</n-tab-pane>
<n-tab-pane name="accesskey" tab="账号">
<users-user-account
@@ -42,45 +46,51 @@
<script setup lang="ts">
// import { userPolicies, userAccount, userGroups } from './components';
const visible = ref(false);
const { getUser, updateUser } = useUsers();
const visible = ref(false)
const { getUser, updateUser, changeUserStatus } = useUsers()
interface UserInfo {
accessKey: string;
memberOf: string[];
policy: string[];
status: string;
accessKey: string
memberOf: string[]
policy: string[]
status: string
}
const user = ref<UserInfo>({
accessKey: '',
memberOf: [],
policy: [],
status: 'enabled',
});
status: 'enabled'
})
// 用户的状态发生变化
const handerUserStatusChange = async (val: string) => {
await updateUser(user.value.accessKey, { ...user.value, groups: user.value.memberOf, status: val });
await getUserData(user.value.accessKey);
};
await changeUserStatus(user.value.accessKey, {
accessKey: user.value.accessKey,
status: val
})
await getUserData(user.value.accessKey)
}
async function openDialog(row: any) {
await getUserData(row.accessKey);
visible.value = true;
await getUserData(row.accessKey)
visible.value = true
}
// 获取用户信息
async function getUserData(name: string) {
user.value = await getUser(name);
setTimeout(async () => {
user.value = await getUser(name)
user.value.accessKey = name
}, 200)
}
// 添加之后的反馈弹窗
const noticeRef = ref();
const noticeRef = ref()
function noticeDialog(data: any) {
noticeRef.value.openDialog(data);
noticeRef.value.openDialog(data)
}
defineExpose({
openDialog,
});
openDialog
})
</script>
<style lang="scss" scoped></style>
+165 -119
View File
@@ -1,7 +1,12 @@
<template>
<div>
<n-card>
<n-form ref="formRef" :model="searchForm" label-placement="left" :show-feedback="false" v-if="!editStatus">
<n-form
ref="formRef"
:model="searchForm"
label-placement="left"
:show-feedback="false"
v-if="!editStatus">
<n-flex justify="space-between">
<n-form-item class="!w-64" label="" path="name">
<n-input placeholder="搜索账号" @input="filterName" />
@@ -9,12 +14,12 @@
<n-space>
<NFlex>
<NButton secondary @click="deleteByList" :disabled="checkedKeys.length == 0">
<!-- <NButton secondary @click="deleteByList" :disabled="checkedKeys.length == 0">
<template #icon>
<Icon name="ri:delete-bin-5-line"></Icon>
</template>
删除所选
</NButton>
</NButton> -->
<NButton secondary @click="addItem">
<template #icon>
<Icon name="ri:add-line"></Icon>
@@ -25,13 +30,29 @@
</n-space>
</n-flex>
</n-form>
<n-form v-else label-placement="left" :model="formModel" label-align="right" :label-width="130">
<n-form
v-else
label-placement="left"
:model="formModel"
label-align="right"
:label-width="130">
<n-grid :cols="24" :x-gap="18">
<n-form-item-grid-item :span="24" label="Access Key" path="accesskey" v-if="editType == 'add'">
<n-form-item-grid-item
:span="24"
label="Access Key"
path="accesskey"
v-if="editType == 'add'">
<n-input v-model:value="formModel.accesskey" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="Secret Key" path="secretkey" v-if="editType == 'add'">
<n-input v-model:value="formModel.secretkey" show-password-on="mousedown" type="password" />
<n-form-item-grid-item
:span="24"
label="Secret Key"
path="secretkey"
v-if="editType == 'add'">
<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">
@@ -46,20 +67,39 @@
<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" v-if="editType == 'add'">
<n-form-item-grid-item
:span="24"
label="描述"
path="comment"
v-if="editType == 'add'">
<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" v-if="editType == 'add'">
<n-form-item-grid-item
:span="24"
label="限制超出用户策略"
path="flag"
v-if="editType == 'add'">
<n-switch v-model:value="formModel.flag" />
</n-form-item-grid-item>
<n-form-item-grid-item v-if="formModel.flag || editType == 'edit'" :span="24" label="策略详情" path="policy">
<n-form-item-grid-item
v-if="formModel.flag || editType == 'edit'"
:span="24"
label="策略详情"
path="policy">
<json-editor v-model="formModel.policy" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="24" label="状态" v-if="editType == 'edit'" path="status">
<n-switch v-model:value="formModel.status" checked-value="on" unchecked-value="off" />
<n-form-item-grid-item
:span="24"
label="状态"
v-if="editType == 'edit'"
path="status">
<n-switch
v-model:value="formModel.status"
checked-value="on"
unchecked-value="off" />
</n-form-item-grid-item>
</n-grid>
<n-space>
@@ -90,69 +130,74 @@ import {
type DataTableRowKey,
NButton,
NPopconfirm,
NSpace,
} from 'naive-ui';
import { Icon } from '#components';
NSpace
} from 'naive-ui'
import { Icon } from '#components'
// 随机字符串函数
import { makeRandomString } from '~/utils/functions';
const { listAllUserServiceAccounts, createServiceAccountCredentials } = useUsers();
const { getServiceAccount, updateServiceAccount, deleteServiceAccount, deleteMultipleServiceAccounts } =
useAccessKeys();
import { makeRandomString } from '~/utils/functions'
const { listUserServiceAccounts, createServiceAccount } = useAccessKeys()
const dialog = useDialog();
const message = useMessage();
const {
getServiceAccount,
updateServiceAccount,
deleteServiceAccount
// deleteMultipleServiceAccounts
} = useAccessKeys()
const dialog = useDialog()
const message = useMessage()
const props = defineProps({
user: {
type: Object,
required: true,
},
});
required: true
}
})
const searchForm = reactive({
name: '',
});
name: ''
})
interface RowData {
accessKey: string;
expiration: string;
name: string;
description: string;
accountStatus: string;
actions: string;
accessKey: string
expiration: string
name: string
description: string
accountStatus: string
actions: string
}
const columns: DataTableColumns<RowData> = [
{
type: 'selection',
type: 'selection'
},
{
title: 'Access Key',
align: 'center',
key: 'accessKey',
filter(value, row) {
return !!row.accessKey.includes(value.toString());
},
return !!row.accessKey.includes(value.toString())
}
},
{
title: '有效期',
align: 'center',
key: 'expiration',
key: 'expiration'
},
{
title: '状态',
align: 'center',
key: 'accountStatus',
render: (row: any) => {
return row.accountStatus === 'on' ? '可用' : '禁用';
},
return row.accountStatus === 'on' ? '可用' : '禁用'
}
},
{
title: '名称',
align: 'center',
key: 'name',
key: 'name'
},
{
title: '描述',
align: 'center',
key: 'description',
key: 'description'
},
{
title: '操作',
@@ -163,7 +208,7 @@ const columns: DataTableColumns<RowData> = [
return h(
NSpace,
{
justify: 'center',
justify: 'center'
},
{
default: () => [
@@ -172,11 +217,11 @@ const columns: DataTableColumns<RowData> = [
{
size: 'small',
secondary: true,
onClick: () => openEditItem(row),
onClick: () => openEditItem(row)
},
{
default: () => '',
icon: () => h(Icon, { name: 'ri:edit-2-line' }),
icon: () => h(Icon, { name: 'ri:edit-2-line' })
}
),
h(
@@ -190,36 +235,36 @@ const columns: DataTableColumns<RowData> = [
{ size: 'small', secondary: true },
{
default: () => '',
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' }),
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' })
}
),
)
}
),
],
)
]
}
);
},
},
];
)
}
}
]
// 搜索过滤
const tableRef = ref<DataTableInst>();
const tableRef = ref<DataTableInst>()
function filterName(value: string) {
tableRef.value &&
tableRef.value.filter({
name: [value],
});
name: [value]
})
}
const listData = ref([]);
const listData = ref([])
const getUserList = async () => {
const res = await listAllUserServiceAccounts(props.user.accessKey);
listData.value = res;
};
getUserList();
const res = await listUserServiceAccounts(props.user.accessKey)
listData.value = res
}
getUserList()
/** ***********************************编辑、新增 */
const editStatus = ref(false);
const editType = ref('add');
const editStatus = ref(false)
const editType = ref('add')
const formModel = ref({
accesskey: makeRandomString(20),
@@ -230,13 +275,13 @@ const formModel = ref({
expiry: null,
policy: '',
flag: false,
status: 'on',
});
status: 'on'
})
// 新增
function addItem() {
editType.value = 'add';
editStatus.value = true;
editType.value = 'add'
editStatus.value = true
formModel.value = {
accesskey: makeRandomString(20),
secretkey: makeRandomString(40),
@@ -246,25 +291,25 @@ function addItem() {
expiry: null,
policy: '',
flag: false,
status: 'on',
};
status: 'on'
}
}
// 编辑
async function openEditItem(row: any) {
editType.value = 'edit';
editStatus.value = true;
const res = await getServiceAccount(row.accessKey);
editType.value = 'edit'
editStatus.value = true
const res = await getServiceAccount(row.accessKey)
formModel.value = {
...res,
};
formModel.value.accesskey = row.accessKey;
formModel.value.expiry = res.expiration;
formModel.value.status = res.accountStatus;
...res
}
formModel.value.accesskey = row.accessKey
formModel.value.expiry = res.expiration
formModel.value.status = res.accountStatus
}
function cancelAdd() {
editStatus.value = false;
editType.value === 'add';
editStatus.value = false
editType.value === 'add'
formModel.value = {
accesskey: makeRandomString(20),
secretkey: makeRandomString(40),
@@ -274,71 +319,72 @@ function cancelAdd() {
expiry: null,
policy: '',
flag: false,
status: 'on',
};
status: 'on'
}
}
interface Emits {
(e: 'search'): void;
(e: 'notice', data: object): void;
(e: 'search'): void
(e: 'notice', data: object): void
}
const emit = defineEmits<Emits>();
const emit = defineEmits<Emits>()
async function submitForm() {
if (editType.value === 'add') {
try {
console.log(formModel.value);
const res = await createServiceAccountCredentials(props.user.accessKey, {
console.log(formModel.value)
const res = await createServiceAccount({
...formModel.value,
expiry: new Date(formModel.value.expiry || 0).toISOString() || '',
});
targetUser: props.user.accessKey,
expiry: new Date(formModel.value.expiry || 0).toISOString() || ''
})
message.success('添加成功');
cancelAdd();
emit('notice', res);
getUserList();
message.success('添加成功')
cancelAdd()
emit('notice', res)
getUserList()
} catch (error) {
console.log(error);
message.error('添加失败');
console.log(error)
message.error('添加失败')
}
} else {
try {
const res = await updateServiceAccount(formModel.value.accesskey, {
...formModel.value,
policy: formModel.value.policy || '{}',
expiry: new Date(formModel.value.expiry || 0).toISOString(),
});
message.success('修改成功');
cancelAdd();
getUserList();
expiry: new Date(formModel.value.expiry || 0).toISOString()
})
message.success('修改成功')
cancelAdd()
getUserList()
} catch (error) {
message.error('修改失败');
message.error('修改失败')
}
}
}
function dateDisabled(ts: number) {
const date = new Date(ts);
return date < new Date();
const date = new Date(ts)
return date < new Date()
}
/** ***********************************删除 */
async function deleteItem(row: any) {
try {
const res = await deleteServiceAccount(row.accessKey);
message.success('删除成功');
getUserList();
const res = await deleteServiceAccount(row.accessKey)
message.success('删除成功')
getUserList()
} catch (error) {
message.error('删除失败');
message.error('删除失败')
}
}
/** ************************************批量删除 */
function rowKey(row: any): string {
return row.accessKey;
return row.accessKey
}
const checkedKeys = ref<DataTableRowKey[]>([]);
const checkedKeys = ref<DataTableRowKey[]>([])
function handleCheck(keys: DataTableRowKey[]) {
checkedKeys.value = keys;
return checkedKeys;
checkedKeys.value = keys
return checkedKeys
}
function deleteByList() {
dialog.error({
@@ -348,21 +394,21 @@ function deleteByList() {
negativeText: '取消',
onPositiveClick: async () => {
if (!checkedKeys.value.length) {
message.error('请至少选择一项');
return;
message.error('请至少选择一项')
return
}
try {
const res = await deleteMultipleServiceAccounts({
body: checkedKeys.value,
});
checkedKeys.value = [];
getUserList();
message.success('删除成功');
// const res = await deleteMultipleServiceAccounts({
// body: checkedKeys.value
// })
checkedKeys.value = []
getUserList()
message.success('删除成功')
} catch (error) {
message.error('删除失败');
message.error('删除失败')
}
},
});
}
})
}
</script>
+79 -46
View File
@@ -1,7 +1,11 @@
<template>
<div>
<n-card>
<n-form ref="formRef" :model="searchForm" label-placement="left" :show-feedback="false">
<n-form
ref="formRef"
:model="searchForm"
label-placement="left"
:show-feedback="false">
<n-flex justify="space-between" v-if="!editStatus">
<n-form-item class="!w-64" label="" path="name">
<n-input placeholder="搜索分组" @input="filterName" />
@@ -20,7 +24,11 @@
</n-flex>
<n-flex justify="space-between" v-else>
<n-form-item class="!w-96" label="选择分组" path="group">
<n-select v-model:value="group" filterable multiple :options="groupList" />
<n-select
v-model:value="group"
filterable
multiple
:options="groupList" />
</n-form-item>
<n-space>
<NFlex>
@@ -41,23 +49,29 @@
</template>
<script setup lang="ts">
import { type DataTableColumns, type DataTableInst, NButton, NSpace } from 'naive-ui';
const { listGroup } = useGroups();
const { updateUserGroups } = useUsers();
import {
type DataTableColumns,
type DataTableInst,
NButton,
NSpace
} from 'naive-ui'
const { listGroup } = useGroups()
// const { updateUserGroups } = useUsers()
const { updateGroupMembers } = useGroups()
const messge = useMessage();
const messge = useMessage()
const props = defineProps({
user: {
type: Object,
required: true,
},
});
required: true
}
})
const searchForm = reactive({
name: '',
});
name: ''
})
interface RowData {
name: string;
name: string
}
const columns: DataTableColumns<RowData> = [
{
@@ -65,63 +79,82 @@ const columns: DataTableColumns<RowData> = [
align: 'left',
key: 'name',
filter(value, row) {
return !!row.name.includes(value.toString());
},
},
];
return !!row.name.includes(value.toString())
}
}
]
// 搜索过滤
const tableRef = ref<DataTableInst>();
const tableRef = ref<DataTableInst>()
function filterName(value: string) {
tableRef.value &&
tableRef.value.filter({
name: [value],
});
name: [value]
})
}
const listData = computed(() => {
return (
props.user.memberOf?.map((item: string) => {
return {
name: item,
};
name: item
}
}) || []
);
});
)
})
/********************编辑****************/
const editStatus = ref(false);
const editStatus = ref(false)
// 用户组
const groupList = ref([]);
const groupList = ref([])
const emit = defineEmits<{
(e: 'search'): void;
}>();
(e: 'search'): void
}>()
const getGroupList = async () => {
const res = await listGroup();
console.log(res);
groupList.value = res.groups.map((item: any) => {
const res = await listGroup()
groupList.value = res.map((item: any) => {
return {
label: item,
value: item,
};
});
};
getGroupList();
value: item
}
})
}
getGroupList()
const group = ref(props.user.memberOf)
// 监听props.user.memberOf变化 并修改group
const group = ref(props.user.memberOf);
const changeMebers = async () => {
try {
await updateUserGroups(props.user.accessKey, {
...props.user,
groups: group.value,
});
messge.success('修改成功');
editStatus.value = false;
emit('search');
} catch {
messge.error('修改失败');
props.user.memberOf.filter(async (item: string) => {
if (!group.value.includes(item)) {
// 删除不存在的
await updateGroupMembers({
group: item,
members: [props.user.accessKey],
isRemove: true,
groupStatus: 'enabled'
})
}
})
// 修改组的成员
group.value.map(async (element: string) => {
await updateGroupMembers({
group: element,
members: [props.user.accessKey],
isRemove: false,
groupStatus: 'enabled'
})
})
messge.success('修改成功')
editStatus.value = false
emit('search')
} catch (e) {
messge.error('修改失败')
}
};
}
</script>
<style lang="scss" scoped></style>
+61 -48
View File
@@ -1,7 +1,11 @@
<template>
<div>
<n-card>
<n-form ref="formRef" :model="searchForm" label-placement="left" :show-feedback="false">
<n-form
ref="formRef"
:model="searchForm"
label-placement="left"
:show-feedback="false">
<n-flex justify="space-between" v-if="!editStatus">
<n-form-item class="!w-64" label="" path="name">
<n-input placeholder="搜索策略" @input="filterName" />
@@ -20,7 +24,11 @@
</n-flex>
<n-flex justify="space-between" v-else>
<n-form-item class="!w-96" label="选择策略" path="policy">
<n-select v-model:value="policy" filterable multiple :options="policyList" />
<n-select
v-model:value="policy"
filterable
multiple
:options="policyList" />
</n-form-item>
<n-space>
<NFlex>
@@ -41,23 +49,27 @@
</template>
<script setup lang="ts">
import { type DataTableColumns, type DataTableInst, NButton, NSpace } from 'naive-ui';
const { listPolicies } = usePolicies();
const { setPolicy } = useUsers();
import {
type DataTableColumns,
type DataTableInst,
NButton,
NSpace
} from 'naive-ui'
const { listPolicies, setUserOrGroupPolicy } = usePolicies()
const messge = useMessage();
const messge = useMessage()
const props = defineProps({
user: {
type: Object,
required: true,
},
});
required: true
}
})
const searchForm = reactive({
name: '',
});
name: ''
})
interface RowData {
name: string;
name: string
}
const columns: DataTableColumns<RowData> = [
{
@@ -65,64 +77,65 @@ const columns: DataTableColumns<RowData> = [
align: 'left',
key: 'name',
filter(value, row) {
return !!row.name.includes(value.toString());
},
},
];
return !!row.name.includes(value.toString())
}
}
]
// 搜索过滤
const tableRef = ref<DataTableInst>();
const tableRef = ref<DataTableInst>()
function filterName(value: string) {
tableRef.value &&
tableRef.value.filter({
name: [value],
});
name: [value]
})
}
const listData = computed(() => {
if (!props.user?.policyName) return []
return (
props.user.policy.map((item: string) => {
props.user?.policyName?.split(',').map((item: string) => {
return {
name: item,
};
name: item
}
}) || []
);
});
)
})
/********************编辑****************/
const editStatus = ref(false);
const editStatus = ref(false)
// 策略列表
const policyList = ref([]);
const policyList = ref<any[]>([])
const emit = defineEmits<{
(e: 'search'): void;
}>();
(e: 'search'): void
}>()
const getPoliciesList = async () => {
const res = await listPolicies();
console.log(res);
policyList.value = res.policies.map((item: any) => {
const res = await listPolicies()
policyList.value = Object.keys(res).map((key) => {
return {
label: item.name,
value: item.name,
};
});
};
getPoliciesList();
label: key,
value: key
}
})
}
getPoliciesList()
const policy = ref(props.user.policy);
const policy = ref(props.user.policyName.split(','))
const changeMebers = async () => {
try {
await setPolicy({
name: policy.value,
entityType: 'user',
entityName: props.user.accessKey,
});
messge.success('修改成功');
editStatus.value = false;
emit('search');
await setUserOrGroupPolicy({
policyName: policy.value || '',
userOrGroup: encodeURIComponent(props.user.accessKey),
isGroup: false
})
messge.success('修改成功')
editStatus.value = false
emit('search')
} catch {
messge.error('修改失败');
messge.error('修改失败')
}
};
}
</script>
<style lang="scss" scoped></style>
+27 -21
View File
@@ -1,33 +1,39 @@
import { name } from './../node_modules/@jsep-plugin/regex/types/tsd.d'
export const useAccessKeys = () => {
const { $api } = useNuxtApp();
const { $api } = useNuxtApp()
const listUserServiceAccounts = async () => {
return await $api.get('/service-accounts');
};
const listUserServiceAccounts = async (name: string) => {
return await $api.get('/list-service-accounts?user=' + name)
}
const createServiceAccount = async (data: any) => {
return await $api.post('/service-accounts', data);
};
return await $api.put('/add-service-accounts', data)
}
const deleteMultipleServiceAccounts = async (data: any) => {
return await $api.delete('/service-accounts/delete-multi', data);
};
// const deleteMultipleServiceAccounts = async (data: any) => {
// return await $api.delete('/delete-service-accounts', data)
// }
const getServiceAccount = async (name: string) => {
return await $api.get(`/service-accounts/${encodeURIComponent(name)}`);
};
return await $api.get(
`/info-service-account?accessKey=${encodeURIComponent(name)}`
)
}
const updateServiceAccount = async (name: string, data: any) => {
return await $api.put(`/service-accounts/${encodeURIComponent(name)}`, data);
};
return await $api.post(`/update-service-account`, data)
}
const deleteServiceAccount = async (name: string) => {
return await $api.delete(`/service-accounts/${encodeURIComponent(name)}`, {});
};
return await $api.delete(
`/delete-service-accounts?accessKey==${encodeURIComponent(name)}`,
{}
)
}
const createServiceAccountCreds = async (data: any) => {
return await $api.post(`/service-account-credentials`, data);
};
return await $api.post(`/service-account-credentials`, data)
}
return {
listUserServiceAccounts,
@@ -35,7 +41,7 @@ export const useAccessKeys = () => {
deleteServiceAccount,
createServiceAccountCreds,
updateServiceAccount,
getServiceAccount,
deleteMultipleServiceAccounts,
};
};
getServiceAccount
// deleteMultipleServiceAccounts
}
}
+6 -1
View File
@@ -54,7 +54,12 @@ export const useGroups = () => {
* @returns
*/
const updateGroupStatus = async (name: string, data: any) => {
return await $api.put(`/group/set-group-status`, data)
return await $api.put(
`/set-group-status?group=${encodeURIComponent(name)}&status=${
data.status
}`,
data
)
}
/**
+35 -7
View File
@@ -1,4 +1,4 @@
import { AccessKeys } from "./../.nuxt/components.d"
import { AccessKeys } from './../.nuxt/components.d'
export const useUsers = () => {
const { $api } = useNuxtApp()
@@ -8,13 +8,16 @@ export const useUsers = () => {
* @returns
*/
const listUsers = async () => {
return await $api.get("/list-users")
return await $api.get('/list-users')
}
const createUser = async (data: any) => {
const { accessKey } = data
delete data.accessKey
return await $api.put("/add-user" + `?accessKey=${encodeURIComponent(accessKey)}`, data)
return await $api.put(
'/add-user' + `?accessKey=${encodeURIComponent(accessKey)}`,
data
)
}
const getUser = async (name: string) => {
@@ -25,8 +28,26 @@ export const useUsers = () => {
return await $api.put(`/user/${encodeURIComponent(name)}`, data)
}
/**
* 修改用户状态
* @param name
* @param data
* @returns
*/
const changeUserStatus = async (name: string, data: any) => {
return await $api.put(
`/set-user-status?accessKey=${encodeURIComponent(name)}&status=${
data.status
}`,
data
)
}
const deleteUser = async (name: string) => {
return await $api.delete(`/remove-user?accessKey=${encodeURIComponent(name)}`, {})
return await $api.delete(
`/remove-user?accessKey=${encodeURIComponent(name)}`,
{}
)
}
const updateUserGroups = async (name: string, data: any) => {
@@ -57,11 +78,17 @@ export const useUsers = () => {
}
const createAUserServiceAccount = async (name: string, data: any) => {
return await $api.post(`/user/${encodeURIComponent(name)}/service-accounts`, data)
return await $api.post(
`/user/${encodeURIComponent(name)}/service-accounts`,
data
)
}
const createServiceAccountCredentials = async (name: string, data: any) => {
return await $api.post(`/user/${encodeURIComponent(name)}/service-account-credentials`, data)
return await $api.post(
`/user/${encodeURIComponent(name)}/service-account-credentials`,
data
)
}
return {
@@ -69,6 +96,7 @@ export const useUsers = () => {
createUser,
getUser,
deleteUser,
changeUserStatus,
updateUser,
updateUserGroups,
getUserPolicy,
@@ -76,6 +104,6 @@ export const useUsers = () => {
getSaUserPolicy,
listAllUserServiceAccounts,
createAUserServiceAccount,
createServiceAccountCredentials,
createServiceAccountCredentials
}
}
+102 -82
View File
@@ -6,18 +6,21 @@
</template>
<template #actions>
<NFlex>
<NButton :disabled="!checkedKeys.length" secondary @click="deleteByList">
<NButton
:disabled="!checkedKeys.length"
secondary
@click="deleteByList">
<template #icon>
<Icon name="ri:delete-bin-5-line"></Icon>
</template>
删除选中项
</NButton>
<NButton secondary @click="changePassword">
<!-- <NButton secondary @click="changePassword">
<template #icon>
<Icon name="ri:key-2-line"></Icon>
</template>
修改秘钥
</NButton>
</NButton> -->
<NButton secondary @click="addItem">
<template #icon>
<Icon name="ri:add-line"></Icon>
@@ -29,7 +32,12 @@
</page-header>
<page-content>
<n-form class="mb-4" ref="formRef" :model="searchForm" label-placement="left" :show-feedback="false">
<n-form
class="mb-4"
ref="formRef"
:model="searchForm"
label-placement="left"
:show-feedback="false">
<n-flex justify="space-between">
<n-form-item label="" path="name">
<n-input placeholder="搜索访问秘钥" @input="filterName" />
@@ -50,9 +58,16 @@
:row-key="rowKey"
@update:checked-row-keys="handleCheck" />
</page-content>
<NewItem ref="newItemRef" v-model:visible="newItemVisible" @search="getDataList" @notice="noticeDialog" />
<NewItem
ref="newItemRef"
v-model:visible="newItemVisible"
@search="getDataList"
@notice="noticeDialog" />
<EditItem ref="editItemRef" @search="getDataList" />
<ChangePassword ref="changePasswordModalRef" v-model:visible="changePasswordVisible" @search="getDataList" />
<ChangePassword
ref="changePasswordModalRef"
v-model:visible="changePasswordVisible"
@search="getDataList" />
<users-user-notice ref="noticeRef"></users-user-notice>
</div>
</template>
@@ -64,61 +79,62 @@ import {
type DataTableRowKey,
NButton,
NPopconfirm,
NSpace,
} from 'naive-ui';
import { Icon } from '#components';
import { ChangePassword, EditItem, NewItem } from '~/components/access-keys';
NSpace
} from 'naive-ui'
import { Icon } from '#components'
import { ChangePassword, EditItem, NewItem } from '~/components/access-keys'
const { $api } = useNuxtApp();
const dialog = useDialog();
const message = useMessage();
const { $api } = useNuxtApp()
const dialog = useDialog()
const message = useMessage()
const { listUserServiceAccounts, deleteServiceAccount } = useAccessKeys()
const searchForm = reactive({
name: '',
});
name: ''
})
interface RowData {
accessKey: string;
expiration: string;
name: string;
description: string;
accountStatus: string;
actions: string;
accessKey: string
expiration: string
name: string
description: string
accountStatus: string
actions: string
}
const columns: DataTableColumns<RowData> = [
{
type: 'selection',
type: 'selection'
},
{
title: 'Access Key',
align: 'center',
key: 'accessKey',
filter(value, row) {
return !!row.accessKey.includes(value.toString());
},
return !!row.accessKey.includes(value.toString())
}
},
{
title: '有效期',
align: 'center',
key: 'expiration',
key: 'expiration'
},
{
title: '状态',
align: 'center',
key: 'accountStatus',
render: (row: any) => {
return row.accountStatus === 'on' ? '可用' : '禁用';
},
return row.accountStatus === 'on' ? '可用' : '禁用'
}
},
{
title: '名称',
align: 'center',
key: 'name',
key: 'name'
},
{
title: '描述',
align: 'center',
key: 'description',
key: 'description'
},
{
title: '操作',
@@ -129,7 +145,7 @@ const columns: DataTableColumns<RowData> = [
return h(
NSpace,
{
justify: 'center',
justify: 'center'
},
{
default: () => [
@@ -138,11 +154,11 @@ const columns: DataTableColumns<RowData> = [
{
size: 'small',
secondary: true,
onClick: () => openEditItem(row),
onClick: () => openEditItem(row)
},
{
default: () => '',
icon: () => h(Icon, { name: 'ri:edit-2-line' }),
icon: () => h(Icon, { name: 'ri:edit-2-line' })
}
),
h(
@@ -156,96 +172,100 @@ const columns: DataTableColumns<RowData> = [
{ size: 'small', secondary: true },
{
default: () => '',
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' }),
icon: () => h(Icon, { name: 'ri:delete-bin-5-line' })
}
),
)
}
),
],
)
]
}
);
},
},
];
)
}
}
]
// 搜索过滤
const tableRef = ref<DataTableInst>();
const tableRef = ref<DataTableInst>()
function filterName(value: string) {
tableRef.value &&
tableRef.value.filter({
accessKey: [value],
});
accessKey: [value]
})
}
const listData = ref<any[]>([]);
const listData = ref<any[]>([])
onMounted(() => {
getDataList();
});
getDataList()
})
// 获取数据
const getDataList = async () => {
try {
const res = await $api.get('service-accounts');
listData.value = res || [];
const res = await listUserServiceAccounts('')
listData.value =
res.accounts.map((item: string) => {
return {
name: item
}
}) || []
} catch (error) {
message.error('获取数据失败');
message.error('获取数据失败')
}
};
}
// 刷新
const refresh = () => {
getDataList();
};
getDataList()
}
/** **********************************添加 */
const newItemRef = ref();
const newItemVisible = ref(false);
const newItemRef = ref()
const newItemVisible = ref(false)
function addItem() {
newItemVisible.value = true;
newItemVisible.value = true
}
// 添加之后的反馈弹窗
const noticeRef = ref();
const noticeRef = ref()
function noticeDialog(data: any) {
console.log(data);
noticeRef.value.openDialog(data);
console.log(data)
noticeRef.value.openDialog(data)
}
/** **********************************修改 */
const editItemRef = ref();
const editItemRef = ref()
function openEditItem(row: any) {
editItemRef.value.openDialog(row);
editItemRef.value.openDialog(row)
}
/** **********************************修改密码 */
const changePasswordModalRef = ref();
const changePasswordVisible = ref(false);
const changePasswordModalRef = ref()
const changePasswordVisible = ref(false)
function changePassword() {
changePasswordVisible.value = true;
changePasswordVisible.value = true
}
/** ***********************************删除 */
async function deleteItem(row: any) {
try {
const res = await $api.delete('/service-accounts/delete-multi', {
body: [row.accessKey],
});
message.success('删除成功');
getDataList();
const res = deleteServiceAccount(row.accessKey)
message.success('删除成功')
getDataList()
} catch (error) {
message.error('删除失败');
message.error('删除失败')
}
}
/** ************************************批量删除 */
function rowKey(row: any): string {
return row.accessKey;
return row.accessKey
}
const checkedKeys = ref<DataTableRowKey[]>([]);
const checkedKeys = ref<DataTableRowKey[]>([])
function handleCheck(keys: DataTableRowKey[]) {
checkedKeys.value = keys;
return checkedKeys;
checkedKeys.value = keys
return checkedKeys
}
function deleteByList() {
dialog.error({
@@ -255,19 +275,19 @@ function deleteByList() {
negativeText: '取消',
onPositiveClick: async () => {
if (!checkedKeys.value.length) {
message.error('请至少选择一项');
return;
message.error('请至少选择一项')
return
}
try {
const res = await $api.delete('/service-accounts/delete-multi', {
body: checkedKeys.value,
});
message.success('删除成功');
getDataList();
body: checkedKeys.value
})
message.success('删除成功')
getDataList()
} catch (error) {
message.error('删除失败');
message.error('删除失败')
}
},
});
}
})
}
</script>
+12 -14
View File
@@ -1,28 +1,26 @@
import { AwsClient } from "aws4fetch"
import ApiClient from "~/lib/api-client"
import { AwsClient } from 'aws4fetch'
import ApiClient from '~/lib/api-client'
export default defineNuxtPlugin((nuxtApp) => {
const runtimeConfig = useRuntimeConfig().public
const { isAuthenticated, credentials } = useAuth()
console.log("credentials", credentials.value)
if (!isAuthenticated.value) {
return
}
const accessKeyId = credentials.value?.AccessKeyId || ""
const secretAccessKey = credentials.value?.SecretAccessKey || ""
const sessionToken = credentials.value?.SessionToken || ""
const region = runtimeConfig.s3.region || "us-east-1"
const service = "s3"
const accessKeyId = credentials.value?.AccessKeyId || ''
const secretAccessKey = credentials.value?.SecretAccessKey || ''
const sessionToken = credentials.value?.SessionToken || ''
const region = runtimeConfig.s3.region || 'us-east-1'
const service = 's3'
const adminApiClient = new AwsClient({
accessKeyId,
secretAccessKey,
sessionToken,
region,
service,
service
})
return {
@@ -30,9 +28,9 @@ export default defineNuxtPlugin((nuxtApp) => {
api: new ApiClient(adminApiClient, {
baseUrl: runtimeConfig.api.baseURL,
headers: {
"Content-Type": "application/json",
},
}),
},
'Content-Type': 'application/json'
}
})
}
}
})