@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.11.4",
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.11.3",
28
- "@drax/common-vue": "^0.11.3",
29
- "@drax/crud-front": "^0.11.3",
30
- "@drax/crud-share": "^0.11.4",
31
- "@drax/crud-vue": "^0.11.4",
32
- "@drax/identity-share": "^0.11.3"
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": "543e9fc9947da228d0f2dc37d2bae3a39ec32250"
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="name"
39
- item-value="id"
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="name"
35
- item-value="id"
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 = true
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.event.registrationComplete') }}
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 = false, search = ""}) {
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=false, search = ""}) {
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=false, search = ""}) {
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=false, search = ""}) {
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 ['_id', 'name','permissions','childRoles','readonly']
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._id">ID: {{ valueModel._id }}</v-card-subtitle>
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 ['_id', 'name']
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 ['_id', 'name','username','email','phone','role.name','tenant.name','active']
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
- passwordChanged.value = await changeUserPassword(user.id, passwordForm.value.newPassword)
29
- return
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
- {{ t('action.change') }}
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 "./forms/UserApiKeyForm.vue";
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
- UserApiKeyCrud,
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.vue";
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
- <UserApiKeyCrud></UserApiKeyCrud>
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>