2022-05-24 07:10:16 +00:00
|
|
|
<script lang="ts" setup>
|
|
|
|
import {
|
|
|
|
IconAddCircle,
|
2022-05-29 13:22:37 +00:00
|
|
|
IconUserFollow,
|
2022-05-24 07:10:16 +00:00
|
|
|
IconUserSettings,
|
2023-03-21 03:54:28 +00:00
|
|
|
IconLockPasswordLine,
|
2022-06-14 07:56:55 +00:00
|
|
|
VButton,
|
|
|
|
VCard,
|
|
|
|
VPageHeader,
|
2022-07-11 10:16:45 +00:00
|
|
|
VPagination,
|
2022-06-14 07:56:55 +00:00
|
|
|
VSpace,
|
|
|
|
VTag,
|
2022-09-10 17:26:26 +00:00
|
|
|
VAvatar,
|
2022-09-14 03:17:15 +00:00
|
|
|
VEntity,
|
|
|
|
VEntityField,
|
2022-10-18 01:58:09 +00:00
|
|
|
Dialog,
|
2022-09-20 09:56:15 +00:00
|
|
|
VStatusDot,
|
2022-11-24 03:46:10 +00:00
|
|
|
VLoading,
|
2022-12-20 11:04:29 +00:00
|
|
|
Toast,
|
2023-02-20 04:18:18 +00:00
|
|
|
IconRefreshLine,
|
|
|
|
VEmpty,
|
2023-03-27 08:06:13 +00:00
|
|
|
VDropdownItem,
|
2022-06-14 07:56:55 +00:00
|
|
|
} from "@halo-dev/components";
|
2022-07-18 03:52:13 +00:00
|
|
|
import UserEditingModal from "./components/UserEditingModal.vue";
|
|
|
|
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
|
2022-09-27 09:13:20 +00:00
|
|
|
import GrantPermissionModal from "./components/GrantPermissionModal.vue";
|
2023-03-23 13:44:34 +00:00
|
|
|
import { computed, onMounted, ref, watch } from "vue";
|
2022-09-22 08:46:32 +00:00
|
|
|
import { apiClient } from "@/utils/api-client";
|
2023-07-07 08:23:06 +00:00
|
|
|
import type { User, ListedUser } from "@halo-dev/api-client";
|
2022-07-20 14:47:10 +00:00
|
|
|
import { rbacAnnotations } from "@/constants/annotations";
|
2022-08-23 06:33:53 +00:00
|
|
|
import { formatDatetime } from "@/utils/date";
|
2022-09-04 17:06:11 +00:00
|
|
|
import { useRouteQuery } from "@vueuse/router";
|
2022-09-30 09:48:19 +00:00
|
|
|
import { usePermission } from "@/utils/permission";
|
2022-11-23 08:39:29 +00:00
|
|
|
import { useUserStore } from "@/stores/user";
|
2023-11-13 08:56:08 +00:00
|
|
|
import { useFetchRole } from "@/composables/use-role";
|
2023-03-23 13:44:34 +00:00
|
|
|
import { useQuery } from "@tanstack/vue-query";
|
2023-03-23 08:54:33 +00:00
|
|
|
import { useI18n } from "vue-i18n";
|
2023-04-24 08:19:42 +00:00
|
|
|
import UserCreationModal from "./components/UserCreationModal.vue";
|
2022-09-30 09:48:19 +00:00
|
|
|
|
|
|
|
const { currentUserHasPermission } = usePermission();
|
2023-03-23 08:54:33 +00:00
|
|
|
const { t } = useI18n();
|
2022-05-24 07:10:16 +00:00
|
|
|
|
2022-09-20 09:56:15 +00:00
|
|
|
const checkedAll = ref(false);
|
2022-07-18 03:52:13 +00:00
|
|
|
const editingModal = ref<boolean>(false);
|
2023-04-24 08:19:42 +00:00
|
|
|
const creationModal = ref<boolean>(false);
|
2022-07-18 03:52:13 +00:00
|
|
|
const passwordChangeModal = ref<boolean>(false);
|
2022-09-27 09:13:20 +00:00
|
|
|
const grantPermissionModal = ref<boolean>(false);
|
|
|
|
|
2022-09-20 09:56:15 +00:00
|
|
|
const selectedUserNames = ref<string[]>([]);
|
2022-09-27 09:13:20 +00:00
|
|
|
const selectedUser = ref<User>();
|
2023-12-21 06:52:12 +00:00
|
|
|
const keyword = useRouteQuery<string>("keyword", "");
|
2022-11-23 08:39:29 +00:00
|
|
|
|
|
|
|
const userStore = useUserStore();
|
2022-05-24 07:10:16 +00:00
|
|
|
|
2023-02-10 04:32:15 +00:00
|
|
|
const ANONYMOUSUSER_NAME = "anonymousUser";
|
2023-02-20 13:14:20 +00:00
|
|
|
const DELETEDUSER_NAME = "ghost";
|
2023-02-10 04:32:15 +00:00
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
// Filters
|
|
|
|
const { roles } = useFetchRole();
|
2023-12-21 06:52:12 +00:00
|
|
|
const page = useRouteQuery<number>("page", 1, {
|
|
|
|
transform: Number,
|
|
|
|
});
|
|
|
|
const size = useRouteQuery<number>("size", 20, {
|
|
|
|
transform: Number,
|
|
|
|
});
|
|
|
|
const selectedRoleValue = useRouteQuery<string | undefined>("role");
|
|
|
|
const selectedSortValue = useRouteQuery<string | undefined>("sort");
|
2023-03-23 13:44:34 +00:00
|
|
|
|
|
|
|
function handleClearFilters() {
|
2023-07-07 08:23:06 +00:00
|
|
|
selectedRoleValue.value = undefined;
|
|
|
|
selectedSortValue.value = undefined;
|
2023-03-23 13:44:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const hasFilters = computed(() => {
|
2023-07-07 08:23:06 +00:00
|
|
|
return selectedRoleValue.value || selectedSortValue.value;
|
2023-03-23 13:44:34 +00:00
|
|
|
});
|
|
|
|
|
2023-07-07 08:23:06 +00:00
|
|
|
watch(
|
|
|
|
() => [selectedRoleValue.value, selectedSortValue.value, keyword.value],
|
|
|
|
() => {
|
|
|
|
page.value = 1;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
const total = ref(0);
|
|
|
|
|
|
|
|
const {
|
|
|
|
data: users,
|
|
|
|
isLoading,
|
|
|
|
isFetching,
|
|
|
|
refetch,
|
|
|
|
} = useQuery<ListedUser[]>({
|
2023-07-07 08:23:06 +00:00
|
|
|
queryKey: [
|
|
|
|
"users",
|
|
|
|
page,
|
|
|
|
size,
|
|
|
|
keyword,
|
|
|
|
selectedSortValue,
|
|
|
|
selectedRoleValue,
|
|
|
|
],
|
2023-03-23 13:44:34 +00:00
|
|
|
queryFn: async () => {
|
2023-02-20 04:18:18 +00:00
|
|
|
const { data } = await apiClient.user.listUsers({
|
2023-03-23 13:44:34 +00:00
|
|
|
page: page.value,
|
|
|
|
size: size.value,
|
2023-02-20 04:18:18 +00:00
|
|
|
keyword: keyword.value,
|
2023-02-20 13:14:20 +00:00
|
|
|
fieldSelector: [
|
|
|
|
`name!=${ANONYMOUSUSER_NAME}`,
|
|
|
|
`name!=${DELETEDUSER_NAME}`,
|
|
|
|
],
|
2023-07-07 08:23:06 +00:00
|
|
|
sort: [selectedSortValue.value].filter(Boolean) as string[],
|
|
|
|
role: selectedRoleValue.value,
|
refactor: method parameters of api client (halo-dev/console#605)
<!-- Thanks for sending a pull request! Here are some tips for you:
1. 如果这是你的第一次,请阅读我们的贡献指南:<https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>。
1. If this is your first time, please read our contributor guidelines: <https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md>.
2. 请根据你解决问题的类型为 Pull Request 添加合适的标签。
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request.
3. 请确保你已经添加并运行了适当的测试。
3. Ensure you have added or ran the appropriate tests for your PR.
-->
#### What type of PR is this?
/kind improvement
/milestone 2.0
<!--
添加其中一个类别:
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind optimization
适当添加其中一个或多个类别(可选):
Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
#### What this PR does / why we need it:
修改 api-client 的请求参数结构,改为所有参数由一个对象包裹,而不是将各个参数作为方法的参数,防止因为后端参数结构发生改变,或者生成 api-client 时参数顺序发生改变导致请求异常。如:
```diff
await apiClient.extension.storage.group.updatestorageHaloRunV1alpha1Group(
- formState.value.metadata.name,
- formState.value
+ {
+ name: formState.value.metadata.name,
+ group: formState.value,
+ }
);
```
#### Which issue(s) this PR fixes:
<!--
PR 合并时自动关闭 issue。
Automatically closes linked issue when PR is merged.
用法:`Fixes #<issue 号>`,或者 `Fixes (粘贴 issue 完整链接)`
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
None
#### Screenshots:
<!--
如果此 PR 有 UI 的改动,最好截图说明这个 PR 的改动。
If there are UI changes to this PR, it is best to take a screenshot to illustrate the changes to this PR.
eg.
Before:

After:

-->
None
#### Special notes for your reviewer:
/cc @halo-dev/sig-halo-admin
#### Does this PR introduce a user-facing change?
<!--
如果当前 Pull Request 的修改不会造成用户侧的任何变更,在 `release-note` 代码块儿中填写 `NONE`。
否则请填写用户侧能够理解的 Release Note。如果当前 Pull Request 包含破坏性更新(Break Change),
Release Note 需要以 `action required` 开头。
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
None
```
2022-09-06 02:26:11 +00:00
|
|
|
});
|
2022-09-28 10:27:21 +00:00
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
total.value = data.total;
|
2022-12-25 05:04:31 +00:00
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
return data.items;
|
|
|
|
},
|
|
|
|
refetchInterval(data) {
|
2024-02-27 08:45:12 +00:00
|
|
|
const hasDeletingData = data?.some(
|
2023-02-24 06:06:14 +00:00
|
|
|
(user) => !!user.user.metadata.deletionTimestamp
|
2022-12-25 05:04:31 +00:00
|
|
|
);
|
|
|
|
|
2024-02-27 08:45:12 +00:00
|
|
|
return hasDeletingData ? 1000 : false;
|
2023-03-23 13:44:34 +00:00
|
|
|
},
|
|
|
|
onSuccess() {
|
2022-09-27 09:13:20 +00:00
|
|
|
selectedUser.value = undefined;
|
2023-03-23 13:44:34 +00:00
|
|
|
},
|
|
|
|
});
|
2022-07-14 08:48:54 +00:00
|
|
|
|
2022-09-20 09:56:15 +00:00
|
|
|
const handleDelete = async (user: User) => {
|
2022-10-18 01:58:09 +00:00
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.user.operations.delete.title"),
|
|
|
|
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
2022-09-20 09:56:15 +00:00
|
|
|
confirmType: "danger",
|
2023-03-23 08:54:33 +00:00
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-09-20 09:56:15 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
try {
|
|
|
|
await apiClient.extension.user.deletev1alpha1User({
|
|
|
|
name: user.metadata.name,
|
|
|
|
});
|
2022-12-20 11:04:29 +00:00
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
2022-09-20 09:56:15 +00:00
|
|
|
} catch (e) {
|
|
|
|
console.error("Failed to delete user", e);
|
|
|
|
} finally {
|
2023-03-23 13:44:34 +00:00
|
|
|
await refetch();
|
2022-09-20 09:56:15 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleDeleteInBatch = async () => {
|
2022-10-18 01:58:09 +00:00
|
|
|
Dialog.warning({
|
2023-03-23 08:54:33 +00:00
|
|
|
title: t("core.user.operations.delete_in_batch.title"),
|
|
|
|
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
2022-09-20 09:56:15 +00:00
|
|
|
confirmType: "danger",
|
2023-03-23 08:54:33 +00:00
|
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
|
|
cancelText: t("core.common.buttons.cancel"),
|
2022-09-20 09:56:15 +00:00
|
|
|
onConfirm: async () => {
|
|
|
|
const userNamesToDelete = selectedUserNames.value.filter(
|
2022-11-23 08:39:29 +00:00
|
|
|
(name) => name != userStore.currentUser?.metadata.name
|
2022-09-20 09:56:15 +00:00
|
|
|
);
|
|
|
|
await Promise.all(
|
|
|
|
userNamesToDelete.map((name) => {
|
|
|
|
return apiClient.extension.user.deletev1alpha1User({
|
|
|
|
name,
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2023-03-23 13:44:34 +00:00
|
|
|
await refetch();
|
2022-09-20 09:56:15 +00:00
|
|
|
selectedUserNames.value.length = 0;
|
2023-03-23 08:54:33 +00:00
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
2022-09-20 09:56:15 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
watch(selectedUserNames, (newValue) => {
|
2023-08-03 07:23:32 +00:00
|
|
|
checkedAll.value =
|
|
|
|
newValue.length ===
|
|
|
|
users.value?.filter(
|
|
|
|
(user) => user.user.metadata.name !== userStore.currentUser?.metadata.name
|
|
|
|
).length;
|
2022-09-20 09:56:15 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const checkSelection = (user: User) => {
|
|
|
|
return (
|
|
|
|
user.metadata.name === selectedUser.value?.metadata.name ||
|
|
|
|
selectedUserNames.value.includes(user.metadata.name)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCheckAllChange = (e: Event) => {
|
|
|
|
const { checked } = e.target as HTMLInputElement;
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
selectedUserNames.value =
|
2023-08-03 07:23:32 +00:00
|
|
|
users.value
|
|
|
|
?.filter((user) => {
|
|
|
|
return (
|
|
|
|
user.user.metadata.name !== userStore.currentUser?.metadata.name
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.map((user) => {
|
|
|
|
return user.user.metadata.name;
|
|
|
|
}) || [];
|
2022-09-20 09:56:15 +00:00
|
|
|
} else {
|
|
|
|
selectedUserNames.value.length = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-27 09:03:08 +00:00
|
|
|
const handleOpenCreateModal = (user: User) => {
|
|
|
|
selectedUser.value = user;
|
2022-07-18 03:52:13 +00:00
|
|
|
editingModal.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleOpenPasswordChangeModal = (user: User) => {
|
|
|
|
selectedUser.value = user;
|
|
|
|
passwordChangeModal.value = true;
|
2022-06-27 09:03:08 +00:00
|
|
|
};
|
|
|
|
|
2022-09-27 09:13:20 +00:00
|
|
|
const handleOpenGrantPermissionModal = (user: User) => {
|
|
|
|
selectedUser.value = user;
|
|
|
|
grantPermissionModal.value = true;
|
|
|
|
};
|
|
|
|
|
2022-09-04 17:06:11 +00:00
|
|
|
// Route query action
|
|
|
|
const routeQueryAction = useRouteQuery<string | undefined>("action");
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
if (routeQueryAction.value === "create") {
|
2023-04-24 08:19:42 +00:00
|
|
|
creationModal.value = true;
|
2022-09-04 17:06:11 +00:00
|
|
|
}
|
|
|
|
});
|
2022-05-24 07:10:16 +00:00
|
|
|
</script>
|
|
|
|
<template>
|
2023-04-24 08:19:42 +00:00
|
|
|
<UserEditingModal v-model:visible="editingModal" :user="selectedUser" />
|
|
|
|
|
|
|
|
<UserCreationModal
|
|
|
|
v-model:visible="creationModal"
|
|
|
|
@close="routeQueryAction = undefined"
|
2022-07-18 03:52:13 +00:00
|
|
|
/>
|
|
|
|
|
|
|
|
<UserPasswordChangeModal
|
|
|
|
v-model:visible="passwordChangeModal"
|
2022-06-27 09:03:08 +00:00
|
|
|
:user="selectedUser"
|
2023-03-23 13:44:34 +00:00
|
|
|
@close="refetch"
|
2022-06-27 08:16:49 +00:00
|
|
|
/>
|
|
|
|
|
2022-09-27 09:13:20 +00:00
|
|
|
<GrantPermissionModal
|
|
|
|
v-model:visible="grantPermissionModal"
|
|
|
|
:user="selectedUser"
|
2023-03-23 13:44:34 +00:00
|
|
|
@close="refetch"
|
2022-09-27 09:13:20 +00:00
|
|
|
/>
|
|
|
|
|
2023-03-23 08:54:33 +00:00
|
|
|
<VPageHeader :title="$t('core.user.title')">
|
2022-05-24 07:10:16 +00:00
|
|
|
<template #icon>
|
2022-05-29 15:55:06 +00:00
|
|
|
<IconUserSettings class="mr-2 self-center" />
|
2022-05-24 07:10:16 +00:00
|
|
|
</template>
|
|
|
|
<template #actions>
|
2022-05-29 13:22:37 +00:00
|
|
|
<VSpace>
|
2022-07-19 06:07:28 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:roles:view']"
|
|
|
|
:route="{ name: 'Roles' }"
|
|
|
|
size="sm"
|
|
|
|
type="default"
|
|
|
|
>
|
2022-05-29 13:22:37 +00:00
|
|
|
<template #icon>
|
2022-05-29 15:55:06 +00:00
|
|
|
<IconUserFollow class="h-full w-full" />
|
2022-05-29 13:22:37 +00:00
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.user.actions.roles") }}
|
2022-05-29 13:22:37 +00:00
|
|
|
</VButton>
|
2023-03-21 03:54:28 +00:00
|
|
|
<VButton :route="{ name: 'AuthProviders' }" size="sm" type="default">
|
|
|
|
<template #icon>
|
|
|
|
<IconLockPasswordLine class="h-full w-full" />
|
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.user.actions.identity_authentication") }}
|
2023-03-21 03:54:28 +00:00
|
|
|
</VButton>
|
2022-07-19 06:07:28 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:users:manage']"
|
|
|
|
type="secondary"
|
2023-04-24 08:19:42 +00:00
|
|
|
@click="creationModal = true"
|
2022-07-19 06:07:28 +00:00
|
|
|
>
|
2022-05-29 13:22:37 +00:00
|
|
|
<template #icon>
|
2022-05-29 15:55:06 +00:00
|
|
|
<IconAddCircle class="h-full w-full" />
|
2022-05-29 13:22:37 +00:00
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.new") }}
|
2022-05-29 13:22:37 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
2022-05-24 07:10:16 +00:00
|
|
|
</template>
|
|
|
|
</VPageHeader>
|
|
|
|
|
|
|
|
<div class="m-0 md:m-4">
|
|
|
|
<VCard :body-class="['!p-0']">
|
|
|
|
<template #header>
|
2022-05-29 15:55:06 +00:00
|
|
|
<div class="block w-full bg-gray-50 px-4 py-3">
|
2022-05-24 07:10:16 +00:00
|
|
|
<div
|
2023-09-14 16:14:14 +00:00
|
|
|
class="relative flex flex-col flex-wrap items-start gap-4 sm:flex-row sm:items-center"
|
2022-05-24 07:10:16 +00:00
|
|
|
>
|
2022-09-30 09:48:19 +00:00
|
|
|
<div
|
|
|
|
v-permission="['system:users:manage']"
|
2023-09-14 16:14:14 +00:00
|
|
|
class="hidden items-center sm:flex"
|
2022-09-30 09:48:19 +00:00
|
|
|
>
|
2022-05-24 07:10:16 +00:00
|
|
|
<input
|
2022-09-20 09:56:15 +00:00
|
|
|
v-model="checkedAll"
|
2022-05-24 07:10:16 +00:00
|
|
|
type="checkbox"
|
2022-09-20 09:56:15 +00:00
|
|
|
@change="handleCheckAllChange"
|
2022-05-24 07:10:16 +00:00
|
|
|
/>
|
|
|
|
</div>
|
2023-02-20 04:18:18 +00:00
|
|
|
<div class="flex w-full flex-1 items-center sm:w-auto">
|
2023-07-07 08:23:06 +00:00
|
|
|
<SearchInput v-if="!selectedUserNames.length" v-model="keyword" />
|
2022-05-24 07:10:16 +00:00
|
|
|
<VSpace v-else>
|
2022-09-20 09:56:15 +00:00
|
|
|
<VButton type="danger" @click="handleDeleteInBatch">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.delete") }}
|
2022-09-20 09:56:15 +00:00
|
|
|
</VButton>
|
2022-05-24 07:10:16 +00:00
|
|
|
</VSpace>
|
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
<VSpace spacing="lg" class="flex-wrap">
|
|
|
|
<FilterCleanButton
|
|
|
|
v-if="hasFilters"
|
|
|
|
@click="handleClearFilters"
|
|
|
|
/>
|
|
|
|
<FilterDropdown
|
|
|
|
v-model="selectedRoleValue"
|
|
|
|
:label="$t('core.user.filters.role.label')"
|
|
|
|
:items="[
|
|
|
|
{
|
|
|
|
label: t('core.common.filters.item_labels.all'),
|
|
|
|
},
|
|
|
|
...roles.map((role) => {
|
|
|
|
return {
|
|
|
|
label:
|
|
|
|
role.metadata.annotations?.[
|
|
|
|
rbacAnnotations.DISPLAY_NAME
|
|
|
|
] || role.metadata.name,
|
|
|
|
value: role.metadata.name,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
]"
|
|
|
|
/>
|
|
|
|
<FilterDropdown
|
|
|
|
v-model="selectedSortValue"
|
|
|
|
:label="$t('core.common.filters.labels.sort')"
|
|
|
|
:items="[
|
|
|
|
{
|
|
|
|
label: t('core.common.filters.item_labels.default'),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.user.filters.sort.items.create_time_desc'),
|
2024-02-27 08:45:12 +00:00
|
|
|
value: 'metadata.creationTimestamp,desc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t('core.user.filters.sort.items.create_time_asc'),
|
2024-02-27 08:45:12 +00:00
|
|
|
value: 'metadata.creationTimestamp,asc',
|
2023-09-14 16:14:14 +00:00
|
|
|
},
|
|
|
|
]"
|
|
|
|
/>
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
<div
|
|
|
|
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
|
|
|
@click="refetch()"
|
|
|
|
>
|
|
|
|
<IconRefreshLine
|
|
|
|
v-tooltip="$t('core.common.buttons.refresh')"
|
|
|
|
:class="{
|
|
|
|
'animate-spin text-gray-900': isFetching,
|
|
|
|
}"
|
|
|
|
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
|
|
|
/>
|
2023-02-20 04:18:18 +00:00
|
|
|
</div>
|
2023-09-14 16:14:14 +00:00
|
|
|
</div>
|
|
|
|
</VSpace>
|
2022-05-24 07:10:16 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
2023-02-20 04:18:18 +00:00
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
<VLoading v-if="isLoading" />
|
2023-02-20 04:18:18 +00:00
|
|
|
|
2023-03-23 13:44:34 +00:00
|
|
|
<Transition v-else-if="!users?.length" appear name="fade">
|
2023-02-20 04:18:18 +00:00
|
|
|
<VEmpty
|
2023-03-23 08:54:33 +00:00
|
|
|
:message="$t('core.user.empty.message')"
|
|
|
|
:title="$t('core.user.empty.title')"
|
2023-02-20 04:18:18 +00:00
|
|
|
>
|
|
|
|
<template #actions>
|
|
|
|
<VSpace>
|
2023-03-23 13:44:34 +00:00
|
|
|
<VButton @click="refetch()">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.refresh") }}
|
|
|
|
</VButton>
|
2023-02-20 04:18:18 +00:00
|
|
|
<VButton
|
|
|
|
v-permission="['system:users:manage']"
|
|
|
|
type="secondary"
|
|
|
|
@click="editingModal = true"
|
|
|
|
>
|
|
|
|
<template #icon>
|
|
|
|
<IconAddCircle class="h-full w-full" />
|
|
|
|
</template>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.new") }}
|
2023-02-20 04:18:18 +00:00
|
|
|
</VButton>
|
|
|
|
</VSpace>
|
|
|
|
</template>
|
|
|
|
</VEmpty>
|
|
|
|
</Transition>
|
|
|
|
|
2022-11-24 03:46:10 +00:00
|
|
|
<Transition v-else appear name="fade">
|
|
|
|
<ul
|
|
|
|
class="box-border h-full w-full divide-y divide-gray-100"
|
|
|
|
role="list"
|
|
|
|
>
|
2023-03-23 13:44:34 +00:00
|
|
|
<li v-for="(user, index) in users" :key="index">
|
2023-02-24 06:06:14 +00:00
|
|
|
<VEntity :is-selected="checkSelection(user.user)">
|
2022-11-24 03:46:10 +00:00
|
|
|
<template
|
|
|
|
v-if="currentUserHasPermission(['system:users:manage'])"
|
|
|
|
#checkbox
|
2022-09-27 09:13:20 +00:00
|
|
|
>
|
2022-11-24 03:46:10 +00:00
|
|
|
<input
|
|
|
|
v-model="selectedUserNames"
|
2023-02-24 06:06:14 +00:00
|
|
|
:value="user.user.metadata.name"
|
2022-11-24 03:46:10 +00:00
|
|
|
name="post-checkbox"
|
|
|
|
type="checkbox"
|
2023-08-03 07:23:32 +00:00
|
|
|
:disabled="
|
|
|
|
user.user.metadata.name ===
|
|
|
|
userStore.currentUser?.metadata.name
|
|
|
|
"
|
2022-11-24 03:46:10 +00:00
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
<template #start>
|
|
|
|
<VEntityField>
|
|
|
|
<template #description>
|
|
|
|
<VAvatar
|
2023-02-24 06:06:14 +00:00
|
|
|
:alt="user.user.spec.displayName"
|
|
|
|
:src="user.user.spec.avatar"
|
2022-11-24 03:46:10 +00:00
|
|
|
size="md"
|
|
|
|
></VAvatar>
|
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
<VEntityField
|
2023-02-24 06:06:14 +00:00
|
|
|
:title="user.user.spec.displayName"
|
|
|
|
:description="user.user.metadata.name"
|
2022-11-24 03:46:10 +00:00
|
|
|
:route="{
|
|
|
|
name: 'UserDetail',
|
2023-02-24 06:06:14 +00:00
|
|
|
params: { name: user.user.metadata.name },
|
2022-11-24 03:46:10 +00:00
|
|
|
}"
|
|
|
|
/>
|
|
|
|
</template>
|
|
|
|
<template #end>
|
|
|
|
<VEntityField>
|
|
|
|
<template #description>
|
|
|
|
<div
|
2023-02-24 06:06:14 +00:00
|
|
|
v-for="(role, roleIndex) in user.roles"
|
2022-11-24 03:46:10 +00:00
|
|
|
:key="roleIndex"
|
|
|
|
class="flex items-center"
|
|
|
|
>
|
|
|
|
<VTag>
|
2023-02-24 06:06:14 +00:00
|
|
|
{{
|
|
|
|
role.metadata.annotations?.[
|
|
|
|
rbacAnnotations.DISPLAY_NAME
|
|
|
|
] || role.metadata.name
|
|
|
|
}}
|
2022-11-24 03:46:10 +00:00
|
|
|
</VTag>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</VEntityField>
|
2023-02-24 06:06:14 +00:00
|
|
|
<VEntityField v-if="user.user.metadata.deletionTimestamp">
|
2022-11-24 03:46:10 +00:00
|
|
|
<template #description>
|
2023-03-23 08:54:33 +00:00
|
|
|
<VStatusDot
|
|
|
|
v-tooltip="$t('core.common.status.deleting')"
|
|
|
|
state="warning"
|
|
|
|
animate
|
|
|
|
/>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
<VEntityField>
|
|
|
|
<template #description>
|
|
|
|
<span class="truncate text-xs tabular-nums text-gray-500">
|
2023-02-24 06:06:14 +00:00
|
|
|
{{ formatDatetime(user.user.metadata.creationTimestamp) }}
|
2022-11-24 03:46:10 +00:00
|
|
|
</span>
|
|
|
|
</template>
|
|
|
|
</VEntityField>
|
|
|
|
</template>
|
|
|
|
<template
|
|
|
|
v-if="currentUserHasPermission(['system:users:manage'])"
|
|
|
|
#dropdownItems
|
2022-09-20 09:56:15 +00:00
|
|
|
>
|
2023-03-27 08:06:13 +00:00
|
|
|
<VDropdownItem @click="handleOpenCreateModal(user.user)">
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.user.operations.update_profile.title") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
|
|
|
<VDropdownItem
|
2023-02-24 06:06:14 +00:00
|
|
|
@click="handleOpenPasswordChangeModal(user.user)"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.user.operations.change_password.title") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
|
|
|
<VDropdownItem
|
2022-11-24 03:46:10 +00:00
|
|
|
v-if="
|
2023-02-24 06:06:14 +00:00
|
|
|
userStore.currentUser?.metadata.name !==
|
|
|
|
user.user.metadata.name
|
2022-11-24 03:46:10 +00:00
|
|
|
"
|
2023-02-24 06:06:14 +00:00
|
|
|
@click="handleOpenGrantPermissionModal(user.user)"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.user.operations.grant_permission.title") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
|
|
|
<VDropdownItem
|
2022-11-24 03:46:10 +00:00
|
|
|
v-if="
|
2023-02-24 06:06:14 +00:00
|
|
|
userStore.currentUser?.metadata.name !==
|
|
|
|
user.user.metadata.name
|
2022-11-24 03:46:10 +00:00
|
|
|
"
|
|
|
|
type="danger"
|
2023-02-24 06:06:14 +00:00
|
|
|
@click="handleDelete(user.user)"
|
2022-11-24 03:46:10 +00:00
|
|
|
>
|
2023-03-23 08:54:33 +00:00
|
|
|
{{ $t("core.common.buttons.delete") }}
|
2023-03-27 08:06:13 +00:00
|
|
|
</VDropdownItem>
|
2022-11-24 03:46:10 +00:00
|
|
|
</template>
|
|
|
|
</VEntity>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</Transition>
|
2022-05-24 07:10:16 +00:00
|
|
|
|
|
|
|
<template #footer>
|
2023-07-28 03:15:08 +00:00
|
|
|
<VPagination
|
|
|
|
v-model:page="page"
|
|
|
|
v-model:size="size"
|
|
|
|
:total="total"
|
|
|
|
:page-label="$t('core.components.pagination.page_label')"
|
|
|
|
:size-label="$t('core.components.pagination.size_label')"
|
|
|
|
:total-label="
|
|
|
|
$t('core.components.pagination.total_label', { total: total })
|
|
|
|
"
|
|
|
|
:size-options="[20, 30, 50, 100]"
|
|
|
|
/>
|
2022-05-24 07:10:16 +00:00
|
|
|
</template>
|
|
|
|
</VCard>
|
|
|
|
</div>
|
|
|
|
</template>
|