@drax/identity-vue 0.11.3 → 0.11.5
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 +7 -7
- package/src/cruds/role-crud/RoleCrud.ts +1 -1
- 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/index.ts +3 -5
- package/src/pages/crud/UserApiKeyCrudPage.vue +71 -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.11.
|
|
6
|
+
"version": "0.11.5",
|
|
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.11.
|
|
28
|
-
"@drax/common-vue": "^0.11.
|
|
27
|
+
"@drax/common-front": "^0.11.5",
|
|
28
|
+
"@drax/common-vue": "^0.11.5",
|
|
29
29
|
"@drax/crud-front": "^0.11.3",
|
|
30
|
-
"@drax/crud-share": "^0.11.
|
|
31
|
-
"@drax/crud-vue": "^0.11.
|
|
32
|
-
"@drax/identity-share": "^0.11.
|
|
30
|
+
"@drax/crud-share": "^0.11.5",
|
|
31
|
+
"@drax/crud-vue": "^0.11.5",
|
|
32
|
+
"@drax/identity-share": "^0.11.5"
|
|
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": "e67f10f0af29468c9d30f16135cfdbdff166d916"
|
|
70
70
|
}
|
|
@@ -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(){
|
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,79 @@
|
|
|
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
|
+
console.log("User API Key created:", item);
|
|
27
|
+
userApiKeyCreated.value = item;
|
|
28
|
+
userApiKeyCreatedDialog.value = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
4
31
|
</script>
|
|
5
32
|
|
|
6
33
|
<template>
|
|
7
|
-
<
|
|
34
|
+
<div>
|
|
35
|
+
<user-api-key-created
|
|
36
|
+
v-if="userApiKeyCreated"
|
|
37
|
+
:user-api-key="userApiKeyCreated"
|
|
38
|
+
v-model="userApiKeyCreatedDialog"
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<crud :entity="UserApiKeyCrud.instance" @created="onCreated">
|
|
42
|
+
|
|
43
|
+
<template v-slot:form>
|
|
44
|
+
<user-api-key-form
|
|
45
|
+
v-model="form"
|
|
46
|
+
@submit="submit"
|
|
47
|
+
@cancel="onCancel"
|
|
48
|
+
/>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<template v-slot:item.ipv4="{ value }" >
|
|
52
|
+
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template v-slot:item.ipv6="{ value }" >
|
|
56
|
+
<v-chip v-for="(ip,index) in value" :key="index">{{ip}}</v-chip>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<template v-slot:item.createdAt="{ value }" >
|
|
60
|
+
{{formatDateTime(value)}}
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<template v-slot:item.createdBy="{ value }" >
|
|
64
|
+
{{value ? value.name : 'Unknown' }}
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
<template v-slot:item.user="{ value }" >
|
|
69
|
+
{{value.username }}
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
</crud>
|
|
73
|
+
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
|
|
8
77
|
</template>
|
|
9
78
|
|
|
10
79
|
<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>
|