@drax/identity-vue 0.11.4 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/combobox/RoleCombobox.vue +4 -2
- package/src/combobox/TenantCombobox.vue +4 -2
- package/src/components/IdentityRegistration/IdentityRegistration.vue +3 -3
- package/src/composables/useRole.ts +2 -1
- package/src/composables/useTenant.ts +2 -1
- package/src/composables/useUser.ts +2 -1
- package/src/composables/useUserApiKey.ts +2 -1
- package/src/cruds/role-crud/RoleCrud.ts +4 -2
- package/src/cruds/role-crud/RoleForm.vue +1 -1
- package/src/cruds/tenant-crud/TenantCrud.ts +5 -1
- package/src/cruds/user-api-key-crud/UserApiKeyCreated.vue +63 -0
- package/src/cruds/user-api-key-crud/UserApiKeyCrud.ts +94 -0
- package/src/cruds/user-api-key-crud/UserApiKeyForm.vue +127 -0
- package/src/cruds/user-crud/UserCrud.ts +1 -1
- package/src/cruds/user-crud/UserForm.vue +2 -0
- package/src/cruds/user-crud/UserPasswordDialog.vue +5 -7
- package/src/index.ts +3 -5
- package/src/pages/crud/UserApiKeyCrudPage.vue +70 -2
- package/src/cruds/user-api-key-crud/UserApiKeyCrud.vue +0 -204
- package/src/cruds/user-api-key-crud/UserApiKeyList.vue +0 -118
- package/src/forms/UserApiKeyForm.vue +0 -79
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.12.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"module": "./src/index.ts",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"format": "prettier --write src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@drax/common-front": "^0.
|
|
28
|
-
"@drax/common-vue": "^0.
|
|
29
|
-
"@drax/crud-front": "^0.
|
|
30
|
-
"@drax/crud-share": "^0.
|
|
31
|
-
"@drax/crud-vue": "^0.
|
|
32
|
-
"@drax/identity-share": "^0.
|
|
27
|
+
"@drax/common-front": "^0.12.1",
|
|
28
|
+
"@drax/common-vue": "^0.12.1",
|
|
29
|
+
"@drax/crud-front": "^0.12.1",
|
|
30
|
+
"@drax/crud-share": "^0.12.1",
|
|
31
|
+
"@drax/crud-vue": "^0.12.1",
|
|
32
|
+
"@drax/identity-share": "^0.12.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"pinia": "^2.2.2",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"vue-tsc": "^2.1.6",
|
|
67
67
|
"vuetify": "^3.7.1"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "76fd366e12449f5f605662197f985a28d98058a2"
|
|
70
70
|
}
|
|
@@ -14,6 +14,8 @@ defineProps({
|
|
|
14
14
|
rules: {type: Array as PropType<any[]>, default: () => []},
|
|
15
15
|
multiple: {type: Boolean, default: false},
|
|
16
16
|
clearable: {type: Boolean, default: false},
|
|
17
|
+
itemTitle: {type: String, default: "name"},
|
|
18
|
+
itemValue: {type: String, default: "_id"},
|
|
17
19
|
readonly: {type: Boolean, default: false},
|
|
18
20
|
label: {type: String, default: 'role.entity'},
|
|
19
21
|
density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
|
|
@@ -35,8 +37,8 @@ onMounted(async () => {
|
|
|
35
37
|
v-model="model"
|
|
36
38
|
:label="te(label) ? t(label) : label"
|
|
37
39
|
:items="items"
|
|
38
|
-
item-title="
|
|
39
|
-
item-value="
|
|
40
|
+
:item-title="itemTitle"
|
|
41
|
+
:item-value="itemValue"
|
|
40
42
|
:variant="variant"
|
|
41
43
|
:error-messages="errorMessages"
|
|
42
44
|
:multiple="multiple"
|
|
@@ -8,6 +8,8 @@ defineProps({
|
|
|
8
8
|
errorMessages: {type: String as PropType<string | string[] | undefined>,},
|
|
9
9
|
clearable: {type: Boolean, default: false},
|
|
10
10
|
readonly: {type: Boolean, default: false},
|
|
11
|
+
itemTitle: {type: String, default: "name"},
|
|
12
|
+
itemValue: {type: String, default: "_id"},
|
|
11
13
|
rules: {type: Array as PropType<any[]>, default: () => []},
|
|
12
14
|
label: {type: String, default: 'tenant.entity'},
|
|
13
15
|
density: {type: String as PropType<'comfortable' | 'compact' | 'default'>, default: 'default'},
|
|
@@ -31,8 +33,8 @@ onMounted(async () => {
|
|
|
31
33
|
v-model="model"
|
|
32
34
|
:label="te(label) ? t(label) : label"
|
|
33
35
|
:items="items"
|
|
34
|
-
item-title="
|
|
35
|
-
item-value="
|
|
36
|
+
:item-title="itemTitle"
|
|
37
|
+
:item-value="itemValue"
|
|
36
38
|
:variant="variant"
|
|
37
39
|
:error-messages="errorMessages"
|
|
38
40
|
:clearable="clearable"
|
|
@@ -44,8 +44,8 @@ function confirmPasswordRule(value: string) {
|
|
|
44
44
|
async function submitRegistration() {
|
|
45
45
|
try {
|
|
46
46
|
loading.value = true
|
|
47
|
-
await register(form.value)
|
|
48
|
-
success.value =
|
|
47
|
+
const result = await register(form.value)
|
|
48
|
+
success.value = result.success
|
|
49
49
|
} catch (err) {
|
|
50
50
|
if (err instanceof ClientError) {
|
|
51
51
|
inputErrors.value = err.inputErrors
|
|
@@ -69,7 +69,7 @@ async function submitRegistration() {
|
|
|
69
69
|
<v-card>
|
|
70
70
|
<v-card-text>
|
|
71
71
|
<v-alert type="success">
|
|
72
|
-
{{ t('user.
|
|
72
|
+
{{ t('user.events.registrationComplete') }}
|
|
73
73
|
</v-alert>
|
|
74
74
|
</v-card-text>
|
|
75
75
|
<v-card-text class="text-center">
|
|
@@ -4,6 +4,7 @@ import {RoleSystemFactory} from "@drax/identity-front";
|
|
|
4
4
|
import type {IRole, IRoleBase} from "@drax/identity-share";
|
|
5
5
|
import {ClientError} from "@drax/common-front";
|
|
6
6
|
import type {IClientInputError} from "@drax/common-front";
|
|
7
|
+
import type {IDraxPaginateOptions} from "@drax/crud-share";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export function useRole() {
|
|
@@ -28,7 +29,7 @@ export function useRole() {
|
|
|
28
29
|
return roles
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
async function paginateRole({page = 1, limit = 5, orderBy = "", order =
|
|
32
|
+
async function paginateRole({page = 1, limit = 5, orderBy = "", order = "asc", search = ""}: IDraxPaginateOptions) {
|
|
32
33
|
loading.value = true
|
|
33
34
|
let paginatedrole = roleSystem.paginate({page, limit, orderBy, order, search})
|
|
34
35
|
loading.value = false
|
|
@@ -4,6 +4,7 @@ import type { TenantSystem} from "@drax/identity-front";
|
|
|
4
4
|
import { TenantSystemFactory} from "@drax/identity-front";
|
|
5
5
|
import {ClientError} from "@drax/common-front";
|
|
6
6
|
import type { IClientInputError} from "@drax/common-front";
|
|
7
|
+
import type {IDraxPaginateOptions} from "@drax/crud-share";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export function useTenant() {
|
|
@@ -21,7 +22,7 @@ export function useTenant() {
|
|
|
21
22
|
return tenants
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async function paginateTenant({page= 1, limit= 5, orderBy="", order=
|
|
25
|
+
async function paginateTenant({page= 1, limit= 5, orderBy="", order="asc", search = ""}: IDraxPaginateOptions) {
|
|
25
26
|
loading.value = true
|
|
26
27
|
let paginatedtenant = tenantSystem.paginate({page, limit, orderBy, order, search})
|
|
27
28
|
loading.value = false
|
|
@@ -4,6 +4,7 @@ import {UserSystemFactory} from "@drax/identity-front";
|
|
|
4
4
|
import type {IClientInputError} from "@drax/common-front";
|
|
5
5
|
import {ClientError} from "@drax/common-front";
|
|
6
6
|
import type {IUser, IUserCreate, IUserUpdate} from "@drax/identity-share";
|
|
7
|
+
import type {IDraxPaginateOptions} from "@drax/crud-share";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
export function useUser() {
|
|
@@ -14,7 +15,7 @@ export function useUser() {
|
|
|
14
15
|
let inputErrors = ref<IClientInputError>()
|
|
15
16
|
let loading = ref(false);
|
|
16
17
|
|
|
17
|
-
async function paginateUser({page= 1, limit= 5, orderBy="", order=
|
|
18
|
+
async function paginateUser({page= 1, limit= 5, orderBy="", order = "asc", search = ""}:IDraxPaginateOptions) {
|
|
18
19
|
loading.value = true
|
|
19
20
|
let paginatedUser = userSystem.paginate({page, limit, orderBy, order, search})
|
|
20
21
|
loading.value = false
|
|
@@ -3,6 +3,7 @@ import type {IUserApiKey, IUserApiKeyBase} from "@drax/identity-share";
|
|
|
3
3
|
import { UserApiKeySystemFactory} from "@drax/identity-front";
|
|
4
4
|
import {ClientError} from "@drax/common-front";
|
|
5
5
|
import type { IClientInputError} from "@drax/common-front";
|
|
6
|
+
import type {IDraxPaginateOptions} from "@drax/crud-share";
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export function useUserApiKey() {
|
|
@@ -14,7 +15,7 @@ export function useUserApiKey() {
|
|
|
14
15
|
let loading = ref(false);
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
async function paginateUserApiKey({page= 1, limit= 5, orderBy="", order=
|
|
18
|
+
async function paginateUserApiKey({page= 1, limit= 5, orderBy="", order="asc", search = ""}: IDraxPaginateOptions) {
|
|
18
19
|
loading.value = true
|
|
19
20
|
let paginateduserApiKey = userApiKeySystem.paginate({page, limit, orderBy, order, search})
|
|
20
21
|
loading.value = false
|
|
@@ -58,7 +58,9 @@ class RoleCrud extends EntityCrud implements IEntityCrud {
|
|
|
58
58
|
|
|
59
59
|
get fields(): IEntityCrudField[]{
|
|
60
60
|
return [
|
|
61
|
-
{name: 'name', type: 'string', label: 'name', default:'', prependInnerIcon:'mdi-text-short' }
|
|
61
|
+
{name: 'name', type: 'string', label: 'name', default:'', prependInnerIcon:'mdi-text-short' },
|
|
62
|
+
{name: 'childRoles', type: 'array.ref', ref:'Role', refDisplay: 'name', label: 'childRoles', default:[], prependInnerIcon:'mdi-text-short' },
|
|
63
|
+
{name: 'permissions', type: 'array.string', label: 'childRoles', default:[], prependInnerIcon:'mdi-text-short' }
|
|
62
64
|
]
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -73,7 +75,7 @@ class RoleCrud extends EntityCrud implements IEntityCrud {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
get exportHeaders(){
|
|
76
|
-
return ['
|
|
78
|
+
return ['id', 'name','permissions','childRoles','readonly']
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
get isExportable(){
|
|
@@ -51,7 +51,7 @@ const {
|
|
|
51
51
|
<template v-if="!valueModel.readonly || store.operation == 'view'">
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
<v-card-subtitle v-if="valueModel.
|
|
54
|
+
<v-card-subtitle v-if="valueModel.id">ID: {{ valueModel.id }}</v-card-subtitle>
|
|
55
55
|
|
|
56
56
|
<v-card-text v-if="store.error">
|
|
57
57
|
<v-alert color="error">{{ te(store.error) ? t(store.error) : store.error }}</v-alert>
|
|
@@ -70,7 +70,7 @@ class TenantCrud extends EntityCrud implements IEntityCrud {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
get exportHeaders(){
|
|
73
|
-
return ['
|
|
73
|
+
return ['id', 'name']
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
get isExportable(){
|
|
@@ -81,6 +81,10 @@ class TenantCrud extends EntityCrud implements IEntityCrud {
|
|
|
81
81
|
return false
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
get dialogMaxWidth(){
|
|
85
|
+
return '800'
|
|
86
|
+
}
|
|
87
|
+
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
export default TenantCrud
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {useCopy} from "@drax/common-vue";
|
|
3
|
+
import {useI18n} from "vue-i18n";
|
|
4
|
+
import {defineModel, type PropType} from "vue";
|
|
5
|
+
import type {IUserApiKey} from "@drax/identity-share";
|
|
6
|
+
|
|
7
|
+
const {t} = useI18n()
|
|
8
|
+
|
|
9
|
+
const {copy} = useCopy()
|
|
10
|
+
|
|
11
|
+
defineProps({
|
|
12
|
+
userApiKey: {type: Object as PropType<IUserApiKey>, required: true},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const dialog = defineModel<boolean>({
|
|
16
|
+
type: Boolean
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
|
|
23
|
+
<v-dialog v-model="dialog" max-width="800" persistent>
|
|
24
|
+
<v-sheet border>
|
|
25
|
+
<v-toolbar>
|
|
26
|
+
<v-toolbar-title>{{t('userapikey.created')}}</v-toolbar-title>
|
|
27
|
+
</v-toolbar>
|
|
28
|
+
<v-card>
|
|
29
|
+
|
|
30
|
+
<v-card-title class="my-3 text-center">{{userApiKey.name}}</v-card-title>
|
|
31
|
+
|
|
32
|
+
<v-card-text>
|
|
33
|
+
<v-text-field
|
|
34
|
+
label="API KEY"
|
|
35
|
+
v-model="userApiKey.secret"
|
|
36
|
+
color="success"
|
|
37
|
+
base-color="success"
|
|
38
|
+
variant="outlined"
|
|
39
|
+
@click:append="copy(userApiKey.secret)"
|
|
40
|
+
:hint="t('userapikey.secretWarning')"
|
|
41
|
+
persistent-hint readonly
|
|
42
|
+
>
|
|
43
|
+
<template v-slot:append>
|
|
44
|
+
<v-btn icon class="text-success" @click="copy(userApiKey.secret)">
|
|
45
|
+
<v-icon>mdi mdi-content-copy</v-icon>
|
|
46
|
+
</v-btn>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
</v-text-field>
|
|
50
|
+
</v-card-text>
|
|
51
|
+
<v-card-actions>
|
|
52
|
+
<v-spacer></v-spacer>
|
|
53
|
+
<v-btn @click="dialog = false">{{ t('action.close') }}</v-btn>
|
|
54
|
+
</v-card-actions>
|
|
55
|
+
</v-card>
|
|
56
|
+
</v-sheet>
|
|
57
|
+
</v-dialog>
|
|
58
|
+
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
import {EntityCrud} from "@drax/crud-vue";
|
|
3
|
+
import {UserApiKeySystemFactory} from "@drax/identity-front";
|
|
4
|
+
import type {IEntityCrud, IEntityCrudField, IEntityCrudFilter, IEntityCrudHeader} from "@drax/crud-share";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UserApiKeyCrud extends EntityCrud implements IEntityCrud {
|
|
8
|
+
|
|
9
|
+
static singleton: UserApiKeyCrud
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.name = 'userApiKey'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static get instance(): UserApiKeyCrud {
|
|
17
|
+
if(!UserApiKeyCrud.singleton){
|
|
18
|
+
UserApiKeyCrud.singleton = new UserApiKeyCrud()
|
|
19
|
+
}
|
|
20
|
+
return UserApiKeyCrud.singleton
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get permissions(){
|
|
24
|
+
return {
|
|
25
|
+
manage: 'userApiKey:manage',
|
|
26
|
+
view: 'userApiKey:viewMy',
|
|
27
|
+
create: 'userApiKey:createMy',
|
|
28
|
+
update: 'userApiKey:update',
|
|
29
|
+
delete: 'userApiKey:delete'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get headers():IEntityCrudHeader[] {
|
|
34
|
+
return [
|
|
35
|
+
//{title: 'id',key:'_id', align: 'start'},
|
|
36
|
+
{title: 'user',key:'user', align: 'start'},
|
|
37
|
+
{title: 'name',key:'name', align: 'start'},
|
|
38
|
+
{title: 'ipv4',key:'ipv4', align: 'start'},
|
|
39
|
+
{title: 'ipv6',key:'ipv6', align: 'start'},
|
|
40
|
+
// {title: 'createdBy',key:'createdBy', align: 'start'},
|
|
41
|
+
{title: 'createdAt',key:'createdAt', align: 'start'},
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get provider(){
|
|
46
|
+
return UserApiKeySystemFactory.getInstance()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get refs(){
|
|
50
|
+
return {
|
|
51
|
+
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get rules(){
|
|
56
|
+
return {
|
|
57
|
+
name: [(v: any) => !!v || 'Requerido']
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get fields(): IEntityCrudField[]{
|
|
62
|
+
return [
|
|
63
|
+
{name: 'name', type: 'string', label: 'name', default:'', prependInnerIcon:'mdi-text-short' },
|
|
64
|
+
{name: 'ipv4', type: 'string', label: 'name', default:[], prependInnerIcon:'mdi-text-short' },
|
|
65
|
+
{name: 'ipv6', type: 'string', label: 'name', default:[], prependInnerIcon:'mdi-text-short' },
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get filters():IEntityCrudFilter[]{
|
|
70
|
+
return [
|
|
71
|
+
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get dialogFullscreen(){
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get exportHeaders(){
|
|
80
|
+
return ['id', 'name']
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get isExportable(){
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get isImportable(){
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default UserApiKeyCrud
|
|
94
|
+
export { UserApiKeyCrud }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {useFormUtils, useCrudStore} from "@drax/crud-vue";
|
|
3
|
+
import {defineEmits, defineModel, ref} from "vue";
|
|
4
|
+
import {useI18nValidation} from "@drax/common-vue";
|
|
5
|
+
import PermissionSelector from "../../components/PermissionSelector/PermissionSelector.vue";
|
|
6
|
+
import RoleCombobox from "../../combobox/RoleCombobox.vue";
|
|
7
|
+
import {useI18n} from "vue-i18n";
|
|
8
|
+
|
|
9
|
+
const {$ta} = useI18nValidation()
|
|
10
|
+
const {t, te} = useI18n()
|
|
11
|
+
|
|
12
|
+
const valueModel = defineModel({type: [Object]})
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(['submit', 'cancel'])
|
|
15
|
+
|
|
16
|
+
const store = useCrudStore()
|
|
17
|
+
|
|
18
|
+
const valid = ref()
|
|
19
|
+
const formRef = ref()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async function submit() {
|
|
23
|
+
store.resetErrors()
|
|
24
|
+
await formRef.value.validate()
|
|
25
|
+
if (valid.value) {
|
|
26
|
+
emit('submit', valueModel.value)
|
|
27
|
+
} else {
|
|
28
|
+
console.log('Invalid form')
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function cancel() {
|
|
33
|
+
emit('cancel')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
variant, submitColor, readonly
|
|
38
|
+
} = useFormUtils(store.operation)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const ipv6Regex = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/
|
|
42
|
+
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/
|
|
43
|
+
|
|
44
|
+
const ipv4Address = [
|
|
45
|
+
(v: string[]) => !v.some(ip => !ipv4Regex.test(ip)) || t('validation.invalidIpv4'),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const ipv6Address = [
|
|
49
|
+
(v: string[]) => !v.some(ip => !ipv6Regex.test(ip)) || t('validation.invalidIpv6'),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
<v-form v-model="valid" ref="formRef" @submit.prevent="submit">
|
|
58
|
+
<v-card flat>
|
|
59
|
+
|
|
60
|
+
<template v-if="!valueModel.readonly || store.operation == 'view'">
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
<v-card-subtitle v-if="valueModel._id">ID: {{ valueModel._id }}</v-card-subtitle>
|
|
64
|
+
|
|
65
|
+
<v-card-text v-if="store.error">
|
|
66
|
+
<v-alert color="error">{{ te(store.error) ? t(store.error) : store.error }}</v-alert>
|
|
67
|
+
</v-card-text>
|
|
68
|
+
|
|
69
|
+
<v-card-text>
|
|
70
|
+
<v-text-field
|
|
71
|
+
variant="outlined"
|
|
72
|
+
id="name-input"
|
|
73
|
+
:label="t('userapikey.field.name')"
|
|
74
|
+
v-model="valueModel.name"
|
|
75
|
+
prepend-inner-icon="mdi-card-account-details"
|
|
76
|
+
required
|
|
77
|
+
:error-messages="$ta(store.inputErrors?.name)"
|
|
78
|
+
></v-text-field>
|
|
79
|
+
|
|
80
|
+
<v-combobox
|
|
81
|
+
v-model="valueModel.ipv4 as string[]"
|
|
82
|
+
:label="t('userapikey.field.ipv4')"
|
|
83
|
+
variant="outlined"
|
|
84
|
+
multiple chips
|
|
85
|
+
validate-on="blur"
|
|
86
|
+
:rules="ipv4Address"
|
|
87
|
+
:error-messages="$ta(store.inputErrors?.ipv4)"
|
|
88
|
+
></v-combobox>
|
|
89
|
+
|
|
90
|
+
<v-combobox
|
|
91
|
+
v-model="valueModel.ipv6 as string[]"
|
|
92
|
+
:label="t('userapikey.field.ipv6')"
|
|
93
|
+
variant="outlined"
|
|
94
|
+
multiple chips
|
|
95
|
+
validate-on="blur"
|
|
96
|
+
:rules="ipv6Address"
|
|
97
|
+
:error-messages="$ta(store.inputErrors?.ipv6)"
|
|
98
|
+
></v-combobox>
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
</v-card-text>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
<v-card-actions>
|
|
105
|
+
<v-spacer></v-spacer>
|
|
106
|
+
<v-btn
|
|
107
|
+
variant="text"
|
|
108
|
+
color="grey"
|
|
109
|
+
@click="cancel">
|
|
110
|
+
{{ t('action.cancel') }}
|
|
111
|
+
</v-btn>
|
|
112
|
+
<v-btn
|
|
113
|
+
v-if="!valueModel.readonly && store.operation != 'view'"
|
|
114
|
+
variant="flat"
|
|
115
|
+
:color="submitColor"
|
|
116
|
+
@click="submit"
|
|
117
|
+
>
|
|
118
|
+
{{ store.operation ? t('action.' + store.operation) : t('action.sent') }}
|
|
119
|
+
</v-btn>
|
|
120
|
+
</v-card-actions>
|
|
121
|
+
</v-card>
|
|
122
|
+
</v-form>
|
|
123
|
+
</template>
|
|
124
|
+
|
|
125
|
+
<style scoped>
|
|
126
|
+
|
|
127
|
+
</style>
|
|
@@ -92,7 +92,7 @@ class UserCrud extends EntityCrud implements IEntityCrud {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
get exportHeaders(){
|
|
95
|
-
return ['
|
|
95
|
+
return ['id', 'name','username','email','phone','role.name','tenant.name','active']
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
get exportFormats(){
|
|
@@ -62,6 +62,8 @@ let passwordVisibility = ref(false)
|
|
|
62
62
|
{{ t('role.readonly') }}
|
|
63
63
|
</v-alert>
|
|
64
64
|
|
|
65
|
+
<v-card-subtitle v-if="valueModel.id">ID: {{ valueModel.id }}</v-card-subtitle>
|
|
66
|
+
|
|
65
67
|
<v-card-text v-if="store.error">
|
|
66
68
|
<v-alert color="error">{{ te(store.error) ? t(store.error) : store.error }}</v-alert>
|
|
67
69
|
</v-card-text>
|
|
@@ -25,10 +25,8 @@ let userError = ref<string>('')
|
|
|
25
25
|
|
|
26
26
|
async function savePassword() {
|
|
27
27
|
if (passwordForm.value.newPassword === passwordForm.value.confirmPassword) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} else {
|
|
31
|
-
return
|
|
28
|
+
await changeUserPassword(user._id, passwordForm.value.newPassword)
|
|
29
|
+
passwordChanged.value = true
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
@@ -54,8 +52,8 @@ async function changeUserPassword(id: string, newPassword: string) {
|
|
|
54
52
|
<template>
|
|
55
53
|
<v-dialog v-model="valueModel" max-width="800">
|
|
56
54
|
<v-card>
|
|
57
|
-
<v-card-title>{{t('user.operation.changePassword')}}</v-card-title>
|
|
58
|
-
<v-card-subtitle>{{t('user.field.username')}}: {{user.username}}</v-card-subtitle>
|
|
55
|
+
<v-card-title>{{ t('user.operation.changePassword') }}</v-card-title>
|
|
56
|
+
<v-card-subtitle>{{ t('user.field.username') }}: {{ user.username }}</v-card-subtitle>
|
|
59
57
|
<v-card-text>
|
|
60
58
|
<user-password-form
|
|
61
59
|
v-model="passwordForm"
|
|
@@ -80,7 +78,7 @@ async function changeUserPassword(id: string, newPassword: string) {
|
|
|
80
78
|
@click="savePassword"
|
|
81
79
|
:loading="loading"
|
|
82
80
|
>
|
|
83
|
-
{{
|
|
81
|
+
{{ t('action.change') }}
|
|
84
82
|
</v-btn>
|
|
85
83
|
</v-card-actions>
|
|
86
84
|
|
package/src/index.ts
CHANGED
|
@@ -19,10 +19,9 @@ import TenantView from "./views/TenantView.vue";
|
|
|
19
19
|
import TenantCrudPage from "./pages/crud/TenantCrudPage.vue";
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
import UserApiKeyForm from "./
|
|
22
|
+
import UserApiKeyForm from "./cruds/user-api-key-crud/UserApiKeyForm.vue";
|
|
23
|
+
import UserApiKeyCreated from "./cruds/user-api-key-crud/UserApiKeyCreated.vue";
|
|
23
24
|
import UserApiKeyView from "./views/UserApiKeyView.vue";
|
|
24
|
-
import UserApiKeyCrud from "./cruds/user-api-key-crud/UserApiKeyCrud.vue";
|
|
25
|
-
import UserApiKeyList from "./cruds/user-api-key-crud/UserApiKeyList.vue";
|
|
26
25
|
import UserApiKeyCrudPage from "./pages/crud/UserApiKeyCrudPage.vue";
|
|
27
26
|
|
|
28
27
|
import {useAuth} from "./composables/useAuth.js";
|
|
@@ -83,8 +82,7 @@ export {
|
|
|
83
82
|
//UserApiKey
|
|
84
83
|
UserApiKeyView,
|
|
85
84
|
UserApiKeyForm,
|
|
86
|
-
|
|
87
|
-
UserApiKeyList,
|
|
85
|
+
UserApiKeyCreated,
|
|
88
86
|
UserApiKeyCrudPage,
|
|
89
87
|
useUserApiKey,
|
|
90
88
|
|
|
@@ -1,10 +1,78 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
|
|
3
|
-
import UserApiKeyCrud from "../../cruds/user-api-key-crud/UserApiKeyCrud
|
|
3
|
+
import UserApiKeyCrud from "../../cruds/user-api-key-crud/UserApiKeyCrud";
|
|
4
|
+
import UserApiKeyForm from "../../cruds/user-api-key-crud/UserApiKeyForm.vue";
|
|
5
|
+
import {Crud, useCrud} from "@drax/crud-vue";
|
|
6
|
+
import type {IUserApiKey} from "@drax/identity-share";
|
|
7
|
+
import {formatDateTime} from "@drax/common-front";
|
|
8
|
+
import UserApiKeyCreated from "../../cruds/user-api-key-crud/UserApiKeyCreated.vue";
|
|
9
|
+
import {ref} from "vue";
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
onCancel, onSubmit,form
|
|
13
|
+
} = useCrud(UserApiKeyCrud.instance);
|
|
14
|
+
|
|
15
|
+
const userApiKeyCreated = ref<IUserApiKey>();
|
|
16
|
+
const userApiKeyCreatedDialog = ref<boolean>(false);
|
|
17
|
+
|
|
18
|
+
async function submit() {
|
|
19
|
+
let result = await onSubmit(form.value)
|
|
20
|
+
if(result.status === 'created'){
|
|
21
|
+
onCreated(result.item);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function onCreated(item:IUserApiKey) {
|
|
26
|
+
userApiKeyCreated.value = item;
|
|
27
|
+
userApiKeyCreatedDialog.value = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
4
30
|
</script>
|
|
5
31
|
|
|
6
32
|
<template>
|
|
7
|
-
<
|
|
33
|
+
<div>
|
|
34
|
+
<user-api-key-created
|
|
35
|
+
v-if="userApiKeyCreated"
|
|
36
|
+
:user-api-key="userApiKeyCreated"
|
|
37
|
+
v-model="userApiKeyCreatedDialog"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<crud :entity="UserApiKeyCrud.instance" @created="onCreated">
|
|
41
|
+
|
|
42
|
+
<template v-slot:form>
|
|
43
|
+
<user-api-key-form
|
|
44
|
+
v-model="form"
|
|
45
|
+
@submit="submit"
|
|
46
|
+
@cancel="onCancel"
|
|
47
|
+
/>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<template v-slot:item.ipv4="{ value }" >
|
|
51
|
+
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<template v-slot:item.ipv6="{ value }" >
|
|
55
|
+
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<template v-slot:item.createdAt="{ value }" >
|
|
59
|
+
{{formatDateTime(value)}}
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<template v-slot:item.createdBy="{ value }" >
|
|
63
|
+
{{value ? value.name : 'Unknown' }}
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
<template v-slot:item.user="{ value }" >
|
|
68
|
+
{{value.username }}
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
</crud>
|
|
72
|
+
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
|
|
8
76
|
</template>
|
|
9
77
|
|
|
10
78
|
<style scoped>
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {computed, ref} from 'vue'
|
|
3
|
-
import UserApiKeyList from "./UserApiKeyList.vue";
|
|
4
|
-
import {useUserApiKey} from "../../composables/useUserApiKey";
|
|
5
|
-
import type {IUserApiKey, IUserApiKeyBase} from "@drax/identity-share";
|
|
6
|
-
import {useCopy} from "@drax/common-vue";
|
|
7
|
-
import UserApiKeyForm from "../../forms/UserApiKeyForm.vue";
|
|
8
|
-
import UserApiKeyView from "../../views/UserApiKeyView.vue";
|
|
9
|
-
import {useI18n} from "vue-i18n";
|
|
10
|
-
const {t} = useI18n()
|
|
11
|
-
const {createUserApiKey, editUserApiKey, deleteUserApiKey, loading, userApiKeyError, inputErrors} = useUserApiKey()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const {copy} = useCopy()
|
|
15
|
-
|
|
16
|
-
interface UserApiKeyList {
|
|
17
|
-
loadItems: () => void;
|
|
18
|
-
items: IUserApiKey[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type DialogMode = 'create' | 'edit' | 'delete' | null;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let dialog = ref(false);
|
|
25
|
-
let success = ref(false);
|
|
26
|
-
let dialogMode = ref<DialogMode>(null);
|
|
27
|
-
let dialogTitle = ref('');
|
|
28
|
-
const userApiKeyList = ref<UserApiKeyList | null>(null);
|
|
29
|
-
let form = ref<IUserApiKeyBase>({name: "", ipv4: [], ipv6: []})
|
|
30
|
-
let target = ref<IUserApiKey>();
|
|
31
|
-
let targetId = ref<string>('');
|
|
32
|
-
let filterEnable = ref(false);
|
|
33
|
-
|
|
34
|
-
let userApiKeyCreated = ref<IUserApiKey>();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
function cancel() {
|
|
38
|
-
dialog.value = false
|
|
39
|
-
userApiKeyCreated.value = undefined
|
|
40
|
-
success.value = false
|
|
41
|
-
inputErrors.value = {}
|
|
42
|
-
userApiKeyError.value = '';
|
|
43
|
-
dialogMode.value = null
|
|
44
|
-
dialogTitle.value = ''
|
|
45
|
-
targetId.value = ''
|
|
46
|
-
target.value = undefined
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function save() {
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
if (dialogMode.value === 'create') {
|
|
53
|
-
userApiKeyCreated.value = await createUserApiKey(form.value)
|
|
54
|
-
|
|
55
|
-
} else if (dialogMode.value === 'edit') {
|
|
56
|
-
await editUserApiKey(targetId.value, form.value)
|
|
57
|
-
} else if (dialogMode.value === 'delete') {
|
|
58
|
-
await deleteUserApiKey(targetId.value)
|
|
59
|
-
}
|
|
60
|
-
success.value = true
|
|
61
|
-
inputErrors.value = {}
|
|
62
|
-
userApiKeyError.value = '';
|
|
63
|
-
if (userApiKeyList.value !== null) {
|
|
64
|
-
userApiKeyList.value.loadItems()
|
|
65
|
-
}
|
|
66
|
-
} catch (e) {
|
|
67
|
-
console.error(e)
|
|
68
|
-
if (e instanceof Error) {
|
|
69
|
-
userApiKeyError.value = e.message
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let buttonText = computed(() => {
|
|
75
|
-
switch (dialogMode.value) {
|
|
76
|
-
case 'create':
|
|
77
|
-
return 'action.create'
|
|
78
|
-
case 'edit':
|
|
79
|
-
return 'action.update'
|
|
80
|
-
case 'delete':
|
|
81
|
-
return 'action.delete'
|
|
82
|
-
default:
|
|
83
|
-
return 'action.sent'
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
function toCreate() {
|
|
88
|
-
dialogMode.value = 'create';
|
|
89
|
-
dialogTitle.value = 'userApiKey.creating';
|
|
90
|
-
form.value = {name: "", ipv4: [], ipv6: []}
|
|
91
|
-
dialog.value = true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function toEdit(item: IUserApiKey) {
|
|
95
|
-
console.log('toEdit', item)
|
|
96
|
-
dialogMode.value = 'edit';
|
|
97
|
-
dialogTitle.value = 'userApiKey.updating';
|
|
98
|
-
targetId.value = item.id;
|
|
99
|
-
form.value = {
|
|
100
|
-
name: item.name,
|
|
101
|
-
ipv4: item.ipv4 ? item.ipv4 : [],
|
|
102
|
-
ipv6: item.ipv6 ? item.ipv6 : [],
|
|
103
|
-
}
|
|
104
|
-
dialog.value = true;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function toDelete(item: IUserApiKey) {
|
|
108
|
-
console.log('toDelete', item)
|
|
109
|
-
dialogMode.value = 'delete';
|
|
110
|
-
dialogTitle.value = 'userApiKey.deleting';
|
|
111
|
-
target.value = item
|
|
112
|
-
const {id} = item;
|
|
113
|
-
targetId.value = id;
|
|
114
|
-
dialog.value = true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
</script>
|
|
118
|
-
|
|
119
|
-
<template>
|
|
120
|
-
<v-container fluid>
|
|
121
|
-
|
|
122
|
-
<v-card border rounded>
|
|
123
|
-
<v-toolbar class="bg-toolbar">
|
|
124
|
-
<v-toolbar-title>{{ t('userApiKey.managing') }}</v-toolbar-title>
|
|
125
|
-
<v-spacer></v-spacer>
|
|
126
|
-
<v-btn icon @click="filterEnable = !filterEnable">
|
|
127
|
-
<v-icon>{{ filterEnable ? 'mdi-filter' : 'mdi-filter-off' }}</v-icon>
|
|
128
|
-
</v-btn>
|
|
129
|
-
<v-btn class="font-weight-bold" color="primary" @click="toCreate">
|
|
130
|
-
{{ t('action.create') }}
|
|
131
|
-
</v-btn>
|
|
132
|
-
</v-toolbar>
|
|
133
|
-
<v-theme-provider with-background class="pa-2 rounded-b">
|
|
134
|
-
<UserApiKeyList
|
|
135
|
-
ref="userApiKeyList"
|
|
136
|
-
@toEdit="toEdit"
|
|
137
|
-
@toDelete="toDelete"
|
|
138
|
-
:filterEnable="filterEnable"
|
|
139
|
-
/>
|
|
140
|
-
</v-theme-provider>
|
|
141
|
-
</v-card>
|
|
142
|
-
|
|
143
|
-
<v-dialog v-model="dialog" max-width="800">
|
|
144
|
-
<v-sheet border>
|
|
145
|
-
<v-toolbar>
|
|
146
|
-
<v-toolbar-title>{{ dialogTitle ? t(dialogTitle) : '-' }}</v-toolbar-title>
|
|
147
|
-
</v-toolbar>
|
|
148
|
-
<v-card class="pa-10">
|
|
149
|
-
<v-card-text v-if="userApiKeyError">
|
|
150
|
-
<v-alert type="error">{{ userApiKeyError }}</v-alert>
|
|
151
|
-
</v-card-text>
|
|
152
|
-
<v-card-text v-if="success">
|
|
153
|
-
<v-alert type="success">{{ t('action.success') }}</v-alert>
|
|
154
|
-
</v-card-text>
|
|
155
|
-
<v-card-text>
|
|
156
|
-
<UserApiKeyForm v-if="dialogMode === 'create' || dialogMode === 'edit'"
|
|
157
|
-
v-model="form"
|
|
158
|
-
:inputErrors="inputErrors"
|
|
159
|
-
@formSubmit="save"
|
|
160
|
-
/>
|
|
161
|
-
<v-text-field
|
|
162
|
-
v-if="userApiKeyCreated"
|
|
163
|
-
label="API KEY"
|
|
164
|
-
v-model="userApiKeyCreated.secret"
|
|
165
|
-
color="success"
|
|
166
|
-
base-color="success"
|
|
167
|
-
variant="outlined"
|
|
168
|
-
@click:append="copy(userApiKeyCreated.secret)"
|
|
169
|
-
:hint="t('userApiKey.secretWarning')"
|
|
170
|
-
persistent-hint
|
|
171
|
-
>
|
|
172
|
-
<template v-slot:append>
|
|
173
|
-
<v-btn icon class="text-success" @click="copy(userApiKeyCreated.secret)"><v-icon>mdi mdi-content-copy</v-icon></v-btn>
|
|
174
|
-
</template>
|
|
175
|
-
|
|
176
|
-
</v-text-field>
|
|
177
|
-
<UserApiKeyView v-if="dialogMode === 'delete' && target" :userApiKey="target"></UserApiKeyView>
|
|
178
|
-
</v-card-text>
|
|
179
|
-
<v-card-actions>
|
|
180
|
-
<v-spacer></v-spacer>
|
|
181
|
-
<v-btn variant="text" @click="cancel" :loading="loading">
|
|
182
|
-
{{success ? t('action.close') : t('action.cancel')}}
|
|
183
|
-
</v-btn>
|
|
184
|
-
<v-btn
|
|
185
|
-
v-if="!success"
|
|
186
|
-
variant="flat"
|
|
187
|
-
:color="dialogMode==='delete' ? 'red' : 'primary'"
|
|
188
|
-
@click="save"
|
|
189
|
-
:loading="loading"
|
|
190
|
-
>
|
|
191
|
-
{{ t(buttonText) }}
|
|
192
|
-
</v-btn>
|
|
193
|
-
</v-card-actions>
|
|
194
|
-
|
|
195
|
-
</v-card>
|
|
196
|
-
</v-sheet>
|
|
197
|
-
</v-dialog>
|
|
198
|
-
|
|
199
|
-
</v-container>
|
|
200
|
-
</template>
|
|
201
|
-
|
|
202
|
-
<style scoped>
|
|
203
|
-
|
|
204
|
-
</style>
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
import {defineProps, type Ref, ref} from "vue";
|
|
4
|
-
import {useUserApiKey} from "../../composables/useUserApiKey";
|
|
5
|
-
import {useAuth} from "../../composables/useAuth";
|
|
6
|
-
import {useI18n} from "vue-i18n";
|
|
7
|
-
import type {IUserApiKey} from "@drax/identity-share";
|
|
8
|
-
import {formatDateTime} from "@drax/common-front";
|
|
9
|
-
|
|
10
|
-
const {hasPermission} = useAuth()
|
|
11
|
-
const {paginateUserApiKey} = useUserApiKey()
|
|
12
|
-
const {t} = useI18n()
|
|
13
|
-
|
|
14
|
-
defineProps({
|
|
15
|
-
filterEnable: {
|
|
16
|
-
type: Boolean,
|
|
17
|
-
default: false,
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
const itemsPerPage = ref(5)
|
|
22
|
-
const page = ref(1)
|
|
23
|
-
const serverItems: Ref<IUserApiKey[]> = ref([]);
|
|
24
|
-
const totalItems = ref(0)
|
|
25
|
-
const loading = ref(false)
|
|
26
|
-
const search = ref('')
|
|
27
|
-
const sortBy : Ref<any> = ref([])
|
|
28
|
-
|
|
29
|
-
const headers = ref<any>([
|
|
30
|
-
...( hasPermission('userApiKey:view') ? [{ title: t('userApiKey.user') as string, key: 'user.username', align: 'start', sortable: false }] : []),
|
|
31
|
-
{ title: t('userApiKey.name') as string, key: 'name', align: 'start' },
|
|
32
|
-
{ title: t('userApiKey.ipv4') as string, key: 'ipv4', align: 'start' },
|
|
33
|
-
{ title: t('userApiKey.ipv6') as string, key: 'ipv6', align: 'start' },
|
|
34
|
-
{ title: t('userApiKey.createdAt') as string, key: 'createdAt', align: 'start' },
|
|
35
|
-
{ title: '', key: 'actions', align: 'end', minWidth: '150px' },
|
|
36
|
-
])
|
|
37
|
-
|
|
38
|
-
async function loadItems(){
|
|
39
|
-
try{
|
|
40
|
-
loading.value = true
|
|
41
|
-
const r = await paginateUserApiKey({
|
|
42
|
-
page: page.value,
|
|
43
|
-
limit: itemsPerPage.value,
|
|
44
|
-
orderBy: sortBy.value[0]?.key,
|
|
45
|
-
order: sortBy.value[0]?.order,
|
|
46
|
-
search: search.value})
|
|
47
|
-
serverItems.value = r.items
|
|
48
|
-
totalItems.value = r.total
|
|
49
|
-
}catch (e){
|
|
50
|
-
console.error(e)
|
|
51
|
-
}finally {
|
|
52
|
-
loading.value = false
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
defineExpose({
|
|
59
|
-
loadItems
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
</script>
|
|
63
|
-
|
|
64
|
-
<template>
|
|
65
|
-
<v-data-table-server
|
|
66
|
-
class="border"
|
|
67
|
-
v-if="hasPermission('userApiKey:manage')"
|
|
68
|
-
v-model:items-per-page="itemsPerPage"
|
|
69
|
-
:items-per-page-options="[5, 10, 20, 50]"
|
|
70
|
-
v-model:page="page"
|
|
71
|
-
v-model:sort-by="sortBy"
|
|
72
|
-
:headers="headers"
|
|
73
|
-
:items="serverItems"
|
|
74
|
-
:items-length="totalItems"
|
|
75
|
-
:loading="loading"
|
|
76
|
-
:search="search"
|
|
77
|
-
item-value="name"
|
|
78
|
-
@update:options="loadItems"
|
|
79
|
-
>
|
|
80
|
-
<template v-slot:top>
|
|
81
|
-
<v-toolbar density="compact" v-if="filterEnable">
|
|
82
|
-
<v-toolbar-title>{{ t('action.filters') }}</v-toolbar-title>
|
|
83
|
-
<v-spacer></v-spacer>
|
|
84
|
-
<v-text-field v-model="search" hide-details
|
|
85
|
-
density="compact" class="mr-2"
|
|
86
|
-
variant="outlined"
|
|
87
|
-
append-inner-icon="mdi-magnify"
|
|
88
|
-
:label="t('action.search')"
|
|
89
|
-
single-line clearable @click:clear="() => search = ''"
|
|
90
|
-
/>
|
|
91
|
-
|
|
92
|
-
</v-toolbar>
|
|
93
|
-
</template>
|
|
94
|
-
|
|
95
|
-
<template v-slot:item.ipv4="{ value }" >
|
|
96
|
-
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
97
|
-
</template>
|
|
98
|
-
|
|
99
|
-
<template v-slot:item.ipv6="{ value }" >
|
|
100
|
-
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
101
|
-
</template>
|
|
102
|
-
|
|
103
|
-
<template v-slot:item.createdAt="{ value }" >
|
|
104
|
-
{{formatDateTime(value)}}
|
|
105
|
-
</template>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<template v-slot:item.actions="{item}" >
|
|
109
|
-
<v-btn v-if="hasPermission('userApiKey:update')" icon="mdi-pencil" variant="text" color="primary" @click="$emit('toEdit', item)"></v-btn>
|
|
110
|
-
<v-btn v-if="hasPermission('userApiKey:delete')" icon="mdi-delete" class="mr-1" variant="text" color="red" @click="$emit('toDelete', item)"></v-btn>
|
|
111
|
-
</template>
|
|
112
|
-
|
|
113
|
-
</v-data-table-server>
|
|
114
|
-
</template>
|
|
115
|
-
|
|
116
|
-
<style scoped>
|
|
117
|
-
|
|
118
|
-
</style>
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import {defineModel, type PropType} from "vue";
|
|
3
|
-
import type {IUserApiKeyBase} from "@drax/identity-share";
|
|
4
|
-
import {useI18nValidation} from "@drax/common-vue";
|
|
5
|
-
import type {IClientInputError} from "@drax/common-front";
|
|
6
|
-
import {useI18n} from "vue-i18n";
|
|
7
|
-
|
|
8
|
-
const {$ta} = useI18nValidation()
|
|
9
|
-
const {t} = useI18n()
|
|
10
|
-
|
|
11
|
-
defineProps({
|
|
12
|
-
inputErrors: {
|
|
13
|
-
type: Object as PropType<IClientInputError>,
|
|
14
|
-
default: () => ({name: "", ipv4: "", ipv6: ""})
|
|
15
|
-
}
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
const form = defineModel<IUserApiKeyBase>({
|
|
19
|
-
type: Object,
|
|
20
|
-
default: () => ({name: "", ipv4: [], ipv6: []})
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
// Define emits
|
|
24
|
-
const emits = defineEmits(['formSubmit'])
|
|
25
|
-
|
|
26
|
-
// Function to call when form is attempted to be submitted
|
|
27
|
-
const onSubmit = () => {
|
|
28
|
-
emits('formSubmit', form); // Emitting an event with the form data
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const ipv6Regex = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/
|
|
32
|
-
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/
|
|
33
|
-
|
|
34
|
-
const ipv4Address = [
|
|
35
|
-
(v: string[]) => !v.some(ip => !ipv4Regex.test(ip)) || t('validation.invalidIpv4'),
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
const ipv6Address = [
|
|
39
|
-
(v: string[]) => !v.some(ip => !ipv6Regex.test(ip)) || t('validation.invalidIpv6'),
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
<template>
|
|
45
|
-
<v-form @submit.prevent="onSubmit" validate-on="blur">
|
|
46
|
-
<v-text-field
|
|
47
|
-
variant="outlined"
|
|
48
|
-
id="name-input"
|
|
49
|
-
:label="t('userApiKey.name')"
|
|
50
|
-
v-model="form.name"
|
|
51
|
-
prepend-inner-icon="mdi-card-account-details"
|
|
52
|
-
required
|
|
53
|
-
:error-messages="$ta(inputErrors.name)"
|
|
54
|
-
></v-text-field>
|
|
55
|
-
|
|
56
|
-
<v-combobox
|
|
57
|
-
v-model="form.ipv4 as string[]"
|
|
58
|
-
:label="t('userApiKey.ipv4')"
|
|
59
|
-
variant="outlined"
|
|
60
|
-
multiple chips
|
|
61
|
-
validate-on="blur"
|
|
62
|
-
:rules="ipv4Address"
|
|
63
|
-
></v-combobox>
|
|
64
|
-
|
|
65
|
-
<v-combobox
|
|
66
|
-
v-model="form.ipv6 as string[]"
|
|
67
|
-
:label="t('userApiKey.ipv6')"
|
|
68
|
-
variant="outlined"
|
|
69
|
-
multiple chips
|
|
70
|
-
validate-on="blur"
|
|
71
|
-
:rules="ipv6Address"
|
|
72
|
-
></v-combobox>
|
|
73
|
-
|
|
74
|
-
</v-form>
|
|
75
|
-
</template>
|
|
76
|
-
|
|
77
|
-
<style scoped>
|
|
78
|
-
|
|
79
|
-
</style>
|