16 changed files with 442 additions and 8 deletions
@ -1,4 +1,5 @@ |
|||
export { useAccountApi } from './useAccountApi'; |
|||
export { useMySessionApi } from './useMySessionApi'; |
|||
export { useProfileApi } from './useProfileApi'; |
|||
export { useTokenApi } from './useTokenApi'; |
|||
export { useUserInfoApi } from './useUserInfoApi'; |
|||
|
|||
@ -0,0 +1,41 @@ |
|||
import type { PagedResultDto } from '@abp/core'; |
|||
import type { GetMySessionsInput, IdentitySessionDto } from '@abp/identity'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useMySessionApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 查询会话列表 |
|||
* @param { GetMySessionsInput } input 查询参数 |
|||
* @returns { Promise<PagedResultDto<IdentitySessionDto>> } 用户会话列表 |
|||
*/ |
|||
function getSessionsApi( |
|||
input?: GetMySessionsInput, |
|||
): Promise<PagedResultDto<IdentitySessionDto>> { |
|||
return request<PagedResultDto<IdentitySessionDto>>( |
|||
'/api/account/my-profile/sessions', |
|||
{ |
|||
method: 'GET', |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
/** |
|||
* 撤销会话 |
|||
* @param { string } sessionId 会话id |
|||
* @returns { Promise<void> } |
|||
*/ |
|||
function revokeSessionApi(sessionId: string): Promise<void> { |
|||
return request(`/api/account/my-profile/sessions/${sessionId}/revoke`, { |
|||
method: 'DELETE', |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getSessionsApi, |
|||
revokeSessionApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
<script setup lang="ts"> |
|||
import type { IdentitySessionDto } from '@abp/identity'; |
|||
|
|||
import { onMounted, ref } from 'vue'; |
|||
|
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { SessionTable } from '@abp/identity'; |
|||
import { Card, message, Modal } from 'ant-design-vue'; |
|||
|
|||
import { useMySessionApi } from '../../api/useMySessionApi'; |
|||
|
|||
const { cancel, getSessionsApi, revokeSessionApi } = useMySessionApi(); |
|||
|
|||
const sessions = ref<IdentitySessionDto[]>([]); |
|||
|
|||
async function getSessions() { |
|||
const { items } = await getSessionsApi(); |
|||
sessions.value = items; |
|||
} |
|||
|
|||
async function onRevoke(session: IdentitySessionDto) { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpIdentity.SessionWillBeRevokedMessage'), |
|||
iconType: 'warning', |
|||
onCancel: () => { |
|||
cancel(); |
|||
}, |
|||
onOk: async () => { |
|||
await revokeSessionApi(session.sessionId); |
|||
message.success($t('AbpIdentity.SuccessfullyRevoked')); |
|||
await getSessions(); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
|
|||
onMounted(getSessions); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card :bordered="false" :title="$t('abp.account.settings.sessionSettings')"> |
|||
<SessionTable :sessions="sessions" @revoke="onRevoke" /> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,42 @@ |
|||
import type { PagedResultDto } from '@abp/core'; |
|||
|
|||
import type { GetUserSessionsInput, IdentitySessionDto } from '../types'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useUserSessionsApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 查询会话列表 |
|||
* @param { GetUserSessionsInput } input 查询参数 |
|||
* @returns { Promise<PagedResultDto<IdentitySessionDto>> } 用户会话列表 |
|||
*/ |
|||
function getSessionsApi( |
|||
input?: GetUserSessionsInput, |
|||
): Promise<PagedResultDto<IdentitySessionDto>> { |
|||
return request<PagedResultDto<IdentitySessionDto>>( |
|||
'/api/identity/sessions', |
|||
{ |
|||
method: 'GET', |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
/** |
|||
* 撤销会话 |
|||
* @param { string } sessionId 会话id |
|||
* @returns { Promise<void> } |
|||
*/ |
|||
function revokeSessionApi(sessionId: string): Promise<void> { |
|||
return request(`/api/identity/sessions/${sessionId}/revoke`, { |
|||
method: 'DELETE', |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getSessionsApi, |
|||
revokeSessionApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,158 @@ |
|||
<script setup lang="ts"> |
|||
import type { VxeGridProps } from '@abp/ui'; |
|||
|
|||
import type { IdentitySessionDto } from '../../types/sessions'; |
|||
|
|||
import { computed, nextTick, reactive, watch } from 'vue'; |
|||
|
|||
import { useAccess } from '@vben/access'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { useAbpStore } from '@abp/core'; |
|||
import { useVbenVxeGrid } from '@abp/ui'; |
|||
import { Button, Descriptions, Tag } from 'ant-design-vue'; |
|||
|
|||
import { IdentitySessionPermissions } from '../../constants/permissions'; |
|||
|
|||
const props = defineProps<{ |
|||
sessions: IdentitySessionDto[]; |
|||
}>(); |
|||
const emits = defineEmits<{ |
|||
(event: 'revoke', session: IdentitySessionDto): void; |
|||
}>(); |
|||
const DescriptionItem = Descriptions.Item; |
|||
|
|||
const { hasAccessByCodes } = useAccess(); |
|||
const abpStore = useAbpStore(); |
|||
/** 获取登录用户会话Id */ |
|||
const getMySessionId = computed(() => { |
|||
return abpStore.application?.currentUser.sessionId; |
|||
}); |
|||
/** 获取是否允许撤销会话 */ |
|||
const getAllowRevokeSession = computed(() => { |
|||
return (session: IdentitySessionDto) => { |
|||
if (getMySessionId.value === session.sessionId) { |
|||
return false; |
|||
} |
|||
return hasAccessByCodes([IdentitySessionPermissions.Revoke]); |
|||
}; |
|||
}); |
|||
|
|||
const gridOptions = reactive<VxeGridProps<IdentitySessionDto>>({ |
|||
columns: [ |
|||
{ |
|||
align: 'left', |
|||
slots: { content: 'deviceInfo' }, |
|||
type: 'expand', |
|||
width: 50, |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'device', |
|||
slots: { default: 'device' }, |
|||
title: $t('AbpIdentity.DisplayName:Device'), |
|||
width: 150, |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'signedIn', |
|||
minWidth: 200, |
|||
title: $t('AbpIdentity.DisplayName:SignedIn'), |
|||
}, |
|||
{ |
|||
field: 'action', |
|||
fixed: 'right', |
|||
slots: { default: 'action' }, |
|||
title: $t('AbpUi.Actions'), |
|||
width: 120, |
|||
}, |
|||
], |
|||
expandConfig: { |
|||
padding: true, |
|||
trigger: 'default', |
|||
}, |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
pagerConfig: { |
|||
autoHidden: true, |
|||
}, |
|||
toolbarConfig: {}, |
|||
}); |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
|||
|
|||
watch( |
|||
() => props.sessions, |
|||
(sessions) => { |
|||
nextTick(() => { |
|||
gridApi.setGridOptions({ |
|||
data: sessions, |
|||
}); |
|||
}); |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
}, |
|||
); |
|||
|
|||
function onDelete(session: IdentitySessionDto) { |
|||
emits('revoke', session); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Grid> |
|||
<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 #deviceInfo="{ row }"> |
|||
<Descriptions :colon="false" :column="2" bordered size="small"> |
|||
<DescriptionItem |
|||
:label="$t('AbpIdentity.DisplayName:SessionId')" |
|||
:span="2" |
|||
> |
|||
{{ row.sessionId }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:Device')"> |
|||
{{ row.device }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:DeviceInfo')"> |
|||
{{ row.deviceInfo }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:ClientId')"> |
|||
{{ row.clientId }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:IpAddresses')"> |
|||
{{ row.ipAddresses }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:SignedIn')"> |
|||
{{ row.signedIn }} |
|||
</DescriptionItem> |
|||
<DescriptionItem :label="$t('AbpIdentity.DisplayName:LastAccessed')"> |
|||
{{ row.lastAccessed }} |
|||
</DescriptionItem> |
|||
</Descriptions> |
|||
</template> |
|||
<template #action="{ row }"> |
|||
<div class="flex flex-row"> |
|||
<Button |
|||
v-if="getAllowRevokeSession(row)" |
|||
danger |
|||
size="small" |
|||
@click="onDelete(row)" |
|||
> |
|||
{{ $t('AbpIdentity.RevokeSession') }} |
|||
</Button> |
|||
</div> |
|||
</template> |
|||
</Grid> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,67 @@ |
|||
<script setup lang="ts"> |
|||
import type { IdentitySessionDto, IdentityUserDto } from '../../types'; |
|||
|
|||
import { defineAsyncComponent, ref } from 'vue'; |
|||
|
|||
import { useVbenDrawer } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { message, Modal } from 'ant-design-vue'; |
|||
|
|||
import { useUserSessionsApi } from '../../api/useUserSessionsApi'; |
|||
|
|||
const SessionTable = defineAsyncComponent( |
|||
() => import('../sessions/SessionTable.vue'), |
|||
); |
|||
|
|||
const sessions = ref<IdentitySessionDto[]>([]); |
|||
|
|||
const { cancel, getSessionsApi, revokeSessionApi } = useUserSessionsApi(); |
|||
|
|||
const [Drawer, drawerApi] = useVbenDrawer({ |
|||
class: 'w-[800px]', |
|||
onBeforeClose: cancel, |
|||
onCancel() { |
|||
drawerApi.close(); |
|||
}, |
|||
onConfirm: async () => {}, |
|||
onOpenChange: async (isOpen: boolean) => { |
|||
isOpen && (await onRefresh()); |
|||
}, |
|||
title: $t('AbpIdentity.IdentitySessions'), |
|||
}); |
|||
async function onRefresh() { |
|||
try { |
|||
drawerApi.setState({ loading: true }); |
|||
const dto = drawerApi.getData<IdentityUserDto>(); |
|||
const { items } = await getSessionsApi({ userId: dto.id }); |
|||
sessions.value = items; |
|||
} finally { |
|||
drawerApi.setState({ loading: false }); |
|||
} |
|||
} |
|||
async function onRevoke(session: IdentitySessionDto) { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: $t('AbpIdentity.SessionWillBeRevokedMessage'), |
|||
iconType: 'warning', |
|||
onCancel: () => { |
|||
cancel(); |
|||
}, |
|||
onOk: async () => { |
|||
await revokeSessionApi(session.sessionId); |
|||
message.success($t('AbpIdentity.SuccessfullyRevoked')); |
|||
await onRefresh(); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Drawer> |
|||
<SessionTable :sessions="sessions" @revoke="onRevoke" /> |
|||
</Drawer> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,25 @@ |
|||
import type { EntityDto, PagedAndSortedResultRequestDto } from '@abp/core'; |
|||
|
|||
interface IdentitySessionDto extends EntityDto<string> { |
|||
clientId?: string; |
|||
device: string; |
|||
deviceInfo: string; |
|||
ipAddresses?: string; |
|||
lastAccessed?: Date; |
|||
sessionId: string; |
|||
signedIn: Date; |
|||
userId: string; |
|||
} |
|||
|
|||
interface GetUserSessionsInput extends PagedAndSortedResultRequestDto { |
|||
clientId?: string; |
|||
device?: string; |
|||
userId?: string; |
|||
} |
|||
|
|||
interface GetMySessionsInput extends PagedAndSortedResultRequestDto { |
|||
clientId?: string; |
|||
device?: string; |
|||
} |
|||
|
|||
export type { GetMySessionsInput, GetUserSessionsInput, IdentitySessionDto }; |
|||
Loading…
Reference in new issue