Browse Source

🚧 feat(session): 增加会话管理.

pull/1087/head
colin 1 year ago
parent
commit
b9d844127d
  1. 3
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  2. 3
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  3. 9
      apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts
  4. 15
      apps/vben5/apps/app-antd/src/views/identity/sessions/index.vue
  5. 4
      apps/vben5/packages/@abp/identity/package.json
  6. 1
      apps/vben5/packages/@abp/identity/src/components/index.ts
  7. 243
      apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue

3
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -19,7 +19,8 @@
"claimTypes": "Claim Types",
"securityLogs": "Security Logs",
"organizationUnits": "Organization Units",
"auditLogs": "Audit Logs"
"auditLogs": "Audit Logs",
"sessions": "Sessions"
},
"permissions": {
"title": "Permissions",

3
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -19,7 +19,8 @@
"claimTypes": "身份标识",
"securityLogs": "安全日志",
"organizationUnits": "组织机构",
"auditLogs": "审计日志"
"auditLogs": "审计日志",
"sessions": "会话管理"
},
"permissions": {
"title": "权限管理",

9
apps/vben5/apps/app-antd/src/router/routes/modules/abp.ts

@ -79,6 +79,15 @@ const routes: RouteRecordRaw[] = [
icon: 'clarity:organization-line',
},
},
{
component: () => import('#/views/identity/sessions/index.vue'),
name: 'IdentitySessions',
path: '/manage/identity/sessions',
meta: {
title: $t('abp.manage.identity.sessions'),
icon: 'carbon:prompt-session',
},
},
],
},
{

15
apps/vben5/apps/app-antd/src/views/identity/sessions/index.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { SessionTable } from '@abp/identity';
defineOptions({
name: 'IdentitySessions',
});
</script>
<template>
<Page>
<SessionTable />
</Page>
</template>

4
apps/vben5/packages/@abp/identity/package.json

@ -33,6 +33,10 @@
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"ant-design-vue": "catalog:",
"lodash.debounce": "catalog:",
"vue": "catalog:*"
},
"devDependencies": {
"@types/lodash.debounce": "catalog:"
}
}

1
apps/vben5/packages/@abp/identity/src/components/index.ts

@ -2,5 +2,6 @@ export { default as ClaimTypeTable } from './claim-types/ClaimTypeTable.vue';
export { default as OrganizationUnitPage } from './organization-units/OrganizationUnitPage.vue';
export { default as RoleTable } from './roles/RoleTable.vue';
export { default as SecurityLogTable } from './security-logs/SecurityLogTable.vue';
export { default as SessionTable } from './sessions/SessionTable.vue';
export { default as UserSessionTable } from './sessions/UserSessionTable.vue';
export { default as UserTable } from './users/UserTable.vue';

243
apps/vben5/packages/@abp/identity/src/components/sessions/SessionTable.vue

@ -0,0 +1,243 @@
<script setup lang="ts">
import type { VbenFormProps, VxeGridListeners, VxeGridProps } from '@abp/ui';
import type { SelectValue } from 'ant-design-vue/es/select';
import type { IdentityUserDto } from '../../types';
import type { IdentitySessionDto } from '../../types/sessions';
import { computed, h, onMounted, ref } from 'vue';
import { useAccess } from '@vben/access';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui';
import { DeleteOutlined } from '@ant-design/icons-vue';
import { Button, message, Modal, Select, Tag } from 'ant-design-vue';
import debounce from 'lodash.debounce';
import { useUsersApi } from '../../api/useUsersApi';
import { useUserSessionsApi } from '../../api/useUserSessionsApi';
import { IdentitySessionPermissions } from '../../constants/permissions';
defineOptions({
name: 'SessionTable',
});
const { hasAccessByCodes } = useAccess();
const { getPagedListApi: getUserListApi } = useUsersApi();
const { cancel, getSessionsApi, revokeSessionApi } = useUserSessionsApi();
const abpStore = useAbpStore();
const users = ref<IdentityUserDto[]>([]);
/** 获取登录用户会话Id */
const getMySessionId = computed(() => {
return abpStore.application?.currentUser.sessionId;
});
/** 获取是否允许撤销会话 */
const getAllowRevokeSession = computed(() => {
return (session: IdentitySessionDto) => {
return getMySessionId.value !== session.sessionId;
};
});
const formOptions: VbenFormProps = {
//
collapsed: false,
schema: [
{
component: 'Select',
fieldName: 'userId',
label: $t('AbpIdentity.DisplayName:UserName'),
},
{
component: 'Input',
fieldName: 'clientId',
label: $t('AbpIdentity.DisplayName:ClientId'),
},
{
component: 'Input',
fieldName: 'device',
label: $t('AbpIdentity.DisplayName:Device'),
},
{
component: 'Input',
fieldName: 'filter',
formItemClass: 'col-span-2 items-baseline',
label: $t('AbpUi.Search'),
},
],
//
showCollapseButton: true,
//
submitOnEnter: true,
};
const gridOptions: VxeGridProps<IdentitySessionDto> = {
columns: [
{
align: 'left',
field: 'sessionId',
minWidth: 150,
title: $t('AbpIdentity.DisplayName:SessionId'),
},
{
align: 'left',
field: 'device',
minWidth: 120,
slots: { default: 'device' },
title: $t('AbpIdentity.DisplayName:Device'),
},
{
align: 'left',
field: 'deviceInfo',
title: $t('AbpIdentity.DisplayName:DeviceInfo'),
width: 'auto',
},
{
align: 'left',
field: 'clientId',
minWidth: 120,
title: $t('AbpIdentity.DisplayName:ClientId'),
},
{
align: 'left',
field: 'ipAddresses',
minWidth: 120,
title: $t('AbpIdentity.DisplayName:IpAddresses'),
},
{
align: 'left',
field: 'signedIn',
minWidth: 120,
title: $t('AbpIdentity.DisplayName:SignedIn'),
},
{
align: 'left',
field: 'lastAccessed',
minWidth: 120,
title: $t('AbpIdentity.DisplayName:LastAccessed'),
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: $t('AbpUi.Actions'),
visible: hasAccessByCodes([
IdentitySessionPermissions.Default,
IdentitySessionPermissions.Revoke,
]),
width: 150,
},
],
exportConfig: {},
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSessionsApi({
maxResultCount: page.pageSize,
skipCount: (page.currentPage - 1) * page.pageSize,
...formValues,
});
},
},
response: {
total: 'totalCount',
list: 'items',
},
},
toolbarConfig: {
custom: true,
export: true,
// import: true,
refresh: true,
zoom: true,
},
};
const gridEvents: VxeGridListeners<IdentitySessionDto> = {
cellClick: () => {},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridEvents,
gridOptions,
});
const onDelete = (row: IdentitySessionDto) => {
Modal.confirm({
centered: true,
content: $t('AbpIdentity.SessionWillBeRevokedMessage'),
onCancel: () => {
cancel();
},
onOk: async () => {
await revokeSessionApi(row.sessionId);
message.success($t('AbpIdentity.SuccessfullyRevoked'));
await gridApi.query();
},
title: $t('AbpUi.AreYouSure'),
});
};
const onGetUsers = debounce(async (filter?: string) => {
const { items } = await getUserListApi({ filter });
users.value = items;
}, 500);
function onFieldChange(field: string, value?: SelectValue) {
gridApi.formApi.setFieldValue(field, value);
}
onMounted(onGetUsers);
</script>
<template>
<Grid :table-title="$t('AbpIdentity.IdentitySessions')">
<template #form-userId="{ modelValue }">
<Select
:default-active-first-option="false"
:field-names="{ label: 'userName', value: 'id' }"
:filter-option="false"
:options="users"
:placeholder="$t('ui.placeholder.select')"
:value="modelValue"
allow-clear
class="w-full"
show-search
@change="(val) => onFieldChange('userId', val)"
@search="onGetUsers"
/>
</template>
<template #device="{ row }">
<div class="flex flex-row">
<span>{{ row.device }}</span>
<div class="pl-[5px]">
<Tag v-if="row.sessionId === getMySessionId" color="#87d068">
{{ $t('AbpIdentity.CurrentSession') }}
</Tag>
</div>
</div>
</template>
<template #action="{ row }">
<div class="flex flex-row">
<Button
v-if="getAllowRevokeSession(row)"
:icon="h(DeleteOutlined)"
block
danger
type="link"
v-access:code="[IdentitySessionPermissions.Revoke]"
@click="onDelete(row)"
>
{{ $t('AbpIdentity.RevokeSession') }}
</Button>
</div>
</template>
</Grid>
</template>
<style lang="scss" scoped></style>
Loading…
Cancel
Save