@hostlink/nuxt-light 1.73.1 → 1.74.0
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/dist/module.json +1 -1
- package/dist/module.mjs +3 -1
- package/dist/runtime/components/L/App.vue +0 -19
- package/dist/runtime/components/L/AppMain.d.vue.ts +3 -3
- package/dist/runtime/components/L/AppMain.vue +43 -10
- package/dist/runtime/components/L/AppMain.vue.d.ts +3 -3
- package/dist/runtime/components/L/FacebookButton.d.vue.ts +19 -3
- package/dist/runtime/components/L/FacebookButton.vue +73 -12
- package/dist/runtime/components/L/FacebookButton.vue.d.ts +19 -3
- package/dist/runtime/components/L/FavMenu.d.vue.ts +2 -0
- package/dist/runtime/components/L/FavMenu.vue +10 -2
- package/dist/runtime/components/L/FavMenu.vue.d.ts +2 -0
- package/dist/runtime/components/L/FileUpload.vue +19 -8
- package/dist/runtime/components/L/Form.d.vue.ts +29 -56
- package/dist/runtime/components/L/Form.vue +70 -80
- package/dist/runtime/components/L/Form.vue.d.ts +29 -56
- package/dist/runtime/components/L/FormLayout.d.vue.ts +36 -0
- package/dist/runtime/components/L/FormLayout.vue +36 -0
- package/dist/runtime/components/L/FormLayout.vue.d.ts +36 -0
- package/dist/runtime/components/L/GroupSelect.vue +1 -1
- package/dist/runtime/components/L/Login.vue +1 -1
- package/dist/runtime/components/L/Menu.d.vue.ts +2 -0
- package/dist/runtime/components/L/Menu.vue +4 -4
- package/dist/runtime/components/L/Menu.vue.d.ts +2 -0
- package/dist/runtime/components/L/System/Setting/security.d.vue.ts +2 -0
- package/dist/runtime/components/L/System/Setting/security.vue +7 -1
- package/dist/runtime/components/L/System/Setting/security.vue.d.ts +2 -0
- package/dist/runtime/composables/useLight.d.ts +0 -4
- package/dist/runtime/formkit/Form.d.vue.ts +2 -2
- package/dist/runtime/formkit/Form.vue +15 -18
- package/dist/runtime/formkit/Form.vue.d.ts +2 -2
- package/dist/runtime/formkit/index.js +2 -0
- package/dist/runtime/locales/en.json +8 -2
- package/dist/runtime/locales/zh-hk.json +8 -2
- package/dist/runtime/pages/Role/[name]/update-child.vue +4 -1
- package/dist/runtime/pages/System/fs.vue +116 -15
- package/dist/runtime/pages/User/setting/index.vue +29 -12
- package/dist/runtime/pages/User/setting/information.vue +4 -28
- package/dist/runtime/pages/User/setting/open_id.vue +132 -91
- package/dist/runtime/pages/User/setting/two-factor-auth.vue +388 -143
- package/dist/runtime/pages/User/setting.vue +3 -4
- package/dist/runtime/types/form.d.ts +2 -0
- package/dist/runtime/types/form.js +0 -0
- package/dist/runtime/utils/filterMenuItems.d.ts +10 -0
- package/dist/runtime/utils/filterMenuItems.js +34 -0
- package/dist/runtime/utils/formLayout.d.ts +2 -0
- package/dist/runtime/utils/formLayout.js +7 -0
- package/dist/runtime/utils/socialSdk.d.ts +2 -0
- package/dist/runtime/utils/socialSdk.js +127 -0
- package/package.json +14 -14
- package/dist/runtime/pages/Role/add2.d.vue.ts +0 -3
- package/dist/runtime/pages/Role/add2.vue +0 -57
- package/dist/runtime/pages/Role/add2.vue.d.ts +0 -3
- package/dist/runtime/pages/System/test.d.vue.ts +0 -3
- package/dist/runtime/pages/System/test.vue +0 -13
- package/dist/runtime/pages/System/test.vue.d.ts +0 -3
|
@@ -1,192 +1,437 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { computed, reactive, ref } from "vue";
|
|
3
3
|
import { useQuasar } from "quasar";
|
|
4
|
-
import {
|
|
4
|
+
import { m, q } from "#imports";
|
|
5
5
|
const $q = useQuasar();
|
|
6
|
-
const
|
|
6
|
+
const initial = await q({
|
|
7
7
|
my: {
|
|
8
|
-
|
|
8
|
+
twoFactorEnabled: true
|
|
9
|
+
},
|
|
10
|
+
app: {
|
|
11
|
+
twoFactorAuthentication: true
|
|
9
12
|
}
|
|
10
13
|
});
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const twoFactorEnabled = ref(Boolean(initial.my?.twoFactorEnabled));
|
|
15
|
+
const systemRequiresTwoFactor = Boolean(initial.app?.twoFactorAuthentication);
|
|
16
|
+
const setup = ref(null);
|
|
14
17
|
const currentStep = ref(1);
|
|
15
18
|
const secretVisible = ref(false);
|
|
16
|
-
const
|
|
19
|
+
const setupLoading = ref(false);
|
|
20
|
+
const saveLoading = ref(false);
|
|
21
|
+
const disableLoading = ref(false);
|
|
22
|
+
const disableDialog = ref(false);
|
|
23
|
+
const setupForm = reactive({
|
|
24
|
+
code: ""
|
|
25
|
+
});
|
|
26
|
+
const disableForm = reactive({
|
|
27
|
+
password: "",
|
|
28
|
+
code: ""
|
|
29
|
+
});
|
|
30
|
+
const validSetupCode = computed(() => /^\d{6}$/.test(setupForm.code));
|
|
31
|
+
const validDisableForm = computed(() => disableForm.password.length > 0 && /^\d{6}$/.test(disableForm.code));
|
|
32
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
33
|
+
const resetSetup = () => {
|
|
34
|
+
setup.value = null;
|
|
35
|
+
setupForm.code = "";
|
|
36
|
+
currentStep.value = 1;
|
|
37
|
+
secretVisible.value = false;
|
|
38
|
+
};
|
|
39
|
+
const startSetup = async () => {
|
|
40
|
+
setupLoading.value = true;
|
|
41
|
+
try {
|
|
42
|
+
const result = await q({
|
|
43
|
+
my: {
|
|
44
|
+
my2FA: true
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
setup.value = result.my.my2FA;
|
|
48
|
+
currentStep.value = 1;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
$q.notify({
|
|
51
|
+
message: `Setup failed: ${errorMessage(error)}`,
|
|
52
|
+
color: "negative"
|
|
53
|
+
});
|
|
54
|
+
} finally {
|
|
55
|
+
setupLoading.value = false;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
17
58
|
const save = async () => {
|
|
18
|
-
|
|
59
|
+
if (!setup.value || !validSetupCode.value) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
saveLoading.value = true;
|
|
19
63
|
try {
|
|
20
64
|
await m("updateMy2FA", {
|
|
21
|
-
code:
|
|
22
|
-
secret:
|
|
65
|
+
code: setupForm.code,
|
|
66
|
+
secret: setup.value.secret
|
|
23
67
|
});
|
|
68
|
+
twoFactorEnabled.value = true;
|
|
69
|
+
resetSetup();
|
|
24
70
|
$q.notify({
|
|
25
|
-
message: "
|
|
71
|
+
message: "Two-Factor Authentication has been successfully enabled!",
|
|
26
72
|
color: "positive",
|
|
27
|
-
timeout: 5e3
|
|
28
|
-
actions: [
|
|
29
|
-
{ label: "Dismiss", color: "white" }
|
|
30
|
-
]
|
|
73
|
+
timeout: 5e3
|
|
31
74
|
});
|
|
32
|
-
|
|
33
|
-
} catch (e) {
|
|
75
|
+
} catch (error) {
|
|
34
76
|
$q.notify({
|
|
35
|
-
message:
|
|
77
|
+
message: `Setup failed: ${errorMessage(error)}`,
|
|
36
78
|
color: "negative",
|
|
37
|
-
timeout: 5e3
|
|
38
|
-
actions: [
|
|
39
|
-
{ label: "Retry", color: "white" }
|
|
40
|
-
]
|
|
79
|
+
timeout: 5e3
|
|
41
80
|
});
|
|
42
81
|
} finally {
|
|
43
|
-
|
|
82
|
+
saveLoading.value = false;
|
|
44
83
|
}
|
|
45
84
|
};
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
const
|
|
85
|
+
const openDisableDialog = () => {
|
|
86
|
+
disableForm.password = "";
|
|
87
|
+
disableForm.code = "";
|
|
88
|
+
disableDialog.value = true;
|
|
89
|
+
};
|
|
90
|
+
const disableTwoFactor = async () => {
|
|
91
|
+
if (!validDisableForm.value) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
disableLoading.value = true;
|
|
52
95
|
try {
|
|
53
|
-
await
|
|
96
|
+
await m("disableMy2FA", {
|
|
97
|
+
password: disableForm.password,
|
|
98
|
+
code: disableForm.code
|
|
99
|
+
});
|
|
100
|
+
twoFactorEnabled.value = false;
|
|
101
|
+
disableDialog.value = false;
|
|
102
|
+
disableForm.password = "";
|
|
103
|
+
disableForm.code = "";
|
|
104
|
+
resetSetup();
|
|
54
105
|
$q.notify({
|
|
55
|
-
message: "
|
|
106
|
+
message: "Two-Factor Authentication has been disabled.",
|
|
56
107
|
color: "positive",
|
|
57
|
-
timeout:
|
|
58
|
-
icon: "content_copy"
|
|
108
|
+
timeout: 5e3
|
|
59
109
|
});
|
|
60
|
-
} catch (
|
|
110
|
+
} catch (error) {
|
|
111
|
+
$q.notify({
|
|
112
|
+
message: `Unable to disable Two-Factor Authentication: ${errorMessage(error)}`,
|
|
113
|
+
color: "negative",
|
|
114
|
+
timeout: 5e3
|
|
115
|
+
});
|
|
116
|
+
} finally {
|
|
117
|
+
disableLoading.value = false;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const onCopy = async () => {
|
|
121
|
+
if (!setup.value?.secret) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
await navigator.clipboard.writeText(setup.value.secret);
|
|
126
|
+
} catch {
|
|
61
127
|
const textArea = document.createElement("textarea");
|
|
62
|
-
textArea.value =
|
|
128
|
+
textArea.value = setup.value.secret;
|
|
63
129
|
document.body.appendChild(textArea);
|
|
64
130
|
textArea.select();
|
|
65
131
|
document.execCommand("copy");
|
|
66
132
|
document.body.removeChild(textArea);
|
|
67
|
-
$q.notify({
|
|
68
|
-
message: "\u2705 Secret key copied to clipboard",
|
|
69
|
-
color: "positive",
|
|
70
|
-
timeout: 2e3
|
|
71
|
-
});
|
|
72
133
|
}
|
|
134
|
+
$q.notify({
|
|
135
|
+
message: "Secret key copied to clipboard",
|
|
136
|
+
color: "positive",
|
|
137
|
+
timeout: 2e3,
|
|
138
|
+
icon: "content_copy"
|
|
139
|
+
});
|
|
73
140
|
};
|
|
74
141
|
</script>
|
|
75
142
|
|
|
76
|
-
<style scoped>
|
|
77
|
-
.two-factor-setup{margin:0 auto;max-width:800px}.setup-header{text-align:center}.app-card{transition:all .3s ease}.app-card:hover{box-shadow:0 4px 12px rgba(0,0,0,.1);transform:translateY(-2px)}.qr-card{background:linear-gradient(135deg,#f5f7fa,#c3cfe2);border:2px dashed #1976d2}.secret-input{font-family:Courier New,monospace;font-size:14px}.code-input .q-field__control input{font-family:Courier New,monospace;font-size:24px;font-weight:700;letter-spacing:8px;text-align:center}.verify-section{text-align:center}@media (max-width:600px){.app-card{margin-bottom:1rem}.qr-card img{width:180px!important}.code-input .q-field__control input{font-size:20px;letter-spacing:4px}}.q-btn--loading{pointer-events:none}.q-stepper--vertical .q-stepper__step--done .q-stepper__label{opacity:.7}.q-btn,.q-card{transition:all .2s ease}.q-btn:hover{transform:translateY(-1px)}
|
|
78
|
-
</style>
|
|
79
|
-
|
|
80
143
|
<template>
|
|
81
144
|
<div class="two-factor-setup">
|
|
82
|
-
<!-- Header -->
|
|
83
145
|
<div class="setup-header q-mb-lg">
|
|
84
|
-
<h5 class="q-my-md">{{ $t(
|
|
85
|
-
|
|
86
|
-
<q-step :name="1" title="Download Authenticator App" icon="download" :done="currentStep > 1">
|
|
87
|
-
<div class="q-mb-md">
|
|
88
|
-
<p class="text-body1 q-mb-md">{{ $t('Choose and download an authenticator app for your device:')
|
|
89
|
-
}}</p>
|
|
90
|
-
|
|
91
|
-
<!-- App Selection Cards -->
|
|
92
|
-
<div class="row q-gutter-md q-mb-lg">
|
|
93
|
-
<div class="col-12 col-md-5">
|
|
94
|
-
<q-card flat bordered class="app-card">
|
|
95
|
-
<q-card-section class="q-pa-md">
|
|
96
|
-
<div class="text-h6 q-mb-sm">📱 Android</div>
|
|
97
|
-
<div class="q-gutter-sm">
|
|
98
|
-
<q-btn outline color="primary" size="sm" target="_blank"
|
|
99
|
-
href="https://play.google.com/store/apps/details?id=com.azure.authenticator"
|
|
100
|
-
label="Microsoft Authenticator" class="full-width" />
|
|
101
|
-
<q-btn outline color="primary" size="sm" target="_blank"
|
|
102
|
-
href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2"
|
|
103
|
-
label="Google Authenticator" class="full-width" />
|
|
104
|
-
</div>
|
|
105
|
-
</q-card-section>
|
|
106
|
-
</q-card>
|
|
107
|
-
</div>
|
|
146
|
+
<h5 class="q-my-md">{{ $t("Two-Factor Authentication") }}</h5>
|
|
147
|
+
</div>
|
|
108
148
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
href="https://apps.apple.com/us/app/google-authenticator/id388497605"
|
|
119
|
-
label="Google Authenticator" class="full-width" />
|
|
120
|
-
</div>
|
|
121
|
-
</q-card-section>
|
|
122
|
-
</q-card>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
149
|
+
<q-card v-if="twoFactorEnabled" flat bordered>
|
|
150
|
+
<q-card-section class="text-center q-pa-xl">
|
|
151
|
+
<q-icon name="verified_user" color="positive" size="64px" />
|
|
152
|
+
<div class="text-h6 q-mt-md">
|
|
153
|
+
{{ $t("Two-Factor Authentication is enabled") }}
|
|
154
|
+
</div>
|
|
155
|
+
<p class="text-grey-7 q-mt-sm">
|
|
156
|
+
{{ $t("Your account is protected by an authenticator app.") }}
|
|
157
|
+
</p>
|
|
125
158
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
159
|
+
<q-banner v-if="systemRequiresTwoFactor" rounded class="bg-blue-1 text-primary q-mt-lg">
|
|
160
|
+
<template #avatar>
|
|
161
|
+
<q-icon name="policy" />
|
|
162
|
+
</template>
|
|
163
|
+
{{ $t("Two-Factor Authentication is required by your administrator and cannot be disabled.") }}
|
|
164
|
+
</q-banner>
|
|
165
|
+
|
|
166
|
+
<q-btn
|
|
167
|
+
v-else
|
|
168
|
+
outline
|
|
169
|
+
color="negative"
|
|
170
|
+
icon="remove_moderator"
|
|
171
|
+
:label="$t('Disable Two-Factor Authentication')"
|
|
172
|
+
class="q-mt-lg"
|
|
173
|
+
@click="openDisableDialog"
|
|
174
|
+
/>
|
|
175
|
+
</q-card-section>
|
|
176
|
+
</q-card>
|
|
177
|
+
|
|
178
|
+
<q-card v-else-if="!setup" flat bordered>
|
|
179
|
+
<q-card-section class="text-center q-pa-xl">
|
|
180
|
+
<q-icon name="security" color="grey-6" size="64px" />
|
|
181
|
+
<div class="text-h6 q-mt-md">
|
|
182
|
+
{{ $t("Two-Factor Authentication is not enabled") }}
|
|
183
|
+
</div>
|
|
184
|
+
<p class="text-grey-7 q-mt-sm">
|
|
185
|
+
{{ $t("Add an extra layer of security to your account with an authenticator app.") }}
|
|
186
|
+
</p>
|
|
187
|
+
<q-btn
|
|
188
|
+
color="primary"
|
|
189
|
+
icon="add_moderator"
|
|
190
|
+
:label="$t('Set up Two-Factor Authentication')"
|
|
191
|
+
:loading="setupLoading"
|
|
192
|
+
class="q-mt-md"
|
|
193
|
+
@click="startSetup"
|
|
194
|
+
/>
|
|
195
|
+
</q-card-section>
|
|
196
|
+
</q-card>
|
|
197
|
+
|
|
198
|
+
<q-stepper v-else v-model="currentStep" vertical color="primary" animated flat bordered>
|
|
199
|
+
<q-step
|
|
200
|
+
:name="1"
|
|
201
|
+
:title="$t('Download Authenticator App')"
|
|
202
|
+
icon="download"
|
|
203
|
+
:done="currentStep > 1"
|
|
204
|
+
>
|
|
205
|
+
<p class="text-body1 q-mb-md">
|
|
206
|
+
{{ $t("Choose and download an authenticator app for your device:") }}
|
|
207
|
+
</p>
|
|
208
|
+
|
|
209
|
+
<div class="row q-col-gutter-md q-mb-lg">
|
|
210
|
+
<div class="col-12 col-md-6">
|
|
211
|
+
<q-card flat bordered class="app-card">
|
|
212
|
+
<q-card-section>
|
|
213
|
+
<div class="text-h6 q-mb-sm">📱 Android</div>
|
|
214
|
+
<div class="q-gutter-sm">
|
|
215
|
+
<q-btn
|
|
216
|
+
outline
|
|
217
|
+
color="primary"
|
|
218
|
+
size="sm"
|
|
219
|
+
target="_blank"
|
|
220
|
+
href="https://play.google.com/store/apps/details?id=com.azure.authenticator"
|
|
221
|
+
label="Microsoft Authenticator"
|
|
222
|
+
class="full-width"
|
|
223
|
+
/>
|
|
224
|
+
<q-btn
|
|
225
|
+
outline
|
|
226
|
+
color="primary"
|
|
227
|
+
size="sm"
|
|
228
|
+
target="_blank"
|
|
229
|
+
href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2"
|
|
230
|
+
label="Google Authenticator"
|
|
231
|
+
class="full-width"
|
|
232
|
+
/>
|
|
154
233
|
</div>
|
|
155
|
-
</q-
|
|
234
|
+
</q-card-section>
|
|
156
235
|
</q-card>
|
|
236
|
+
</div>
|
|
157
237
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
238
|
+
<div class="col-12 col-md-6">
|
|
239
|
+
<q-card flat bordered class="app-card">
|
|
240
|
+
<q-card-section>
|
|
241
|
+
<div class="text-h6 q-mb-sm">🍎 iOS</div>
|
|
242
|
+
<div class="q-gutter-sm">
|
|
243
|
+
<q-btn
|
|
244
|
+
outline
|
|
245
|
+
color="primary"
|
|
246
|
+
size="sm"
|
|
247
|
+
target="_blank"
|
|
248
|
+
href="https://apps.apple.com/us/app/microsoft-authenticator/id983156458"
|
|
249
|
+
label="Microsoft Authenticator"
|
|
250
|
+
class="full-width"
|
|
251
|
+
/>
|
|
252
|
+
<q-btn
|
|
253
|
+
outline
|
|
254
|
+
color="primary"
|
|
255
|
+
size="sm"
|
|
256
|
+
target="_blank"
|
|
257
|
+
href="https://apps.apple.com/us/app/google-authenticator/id388497605"
|
|
258
|
+
label="Google Authenticator"
|
|
259
|
+
class="full-width"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
</q-card-section>
|
|
263
|
+
</q-card>
|
|
162
264
|
</div>
|
|
163
|
-
</
|
|
164
|
-
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="row q-gutter-sm">
|
|
268
|
+
<q-btn
|
|
269
|
+
color="primary"
|
|
270
|
+
:label="$t('I have installed the app')"
|
|
271
|
+
icon-right="arrow_forward"
|
|
272
|
+
@click="currentStep = 2"
|
|
273
|
+
/>
|
|
274
|
+
<q-btn flat :label="$t('Cancel')" @click="resetSetup" />
|
|
275
|
+
</div>
|
|
276
|
+
</q-step>
|
|
277
|
+
|
|
278
|
+
<q-step :name="2" :title="$t('Scan QR Code')" icon="qr_code" :done="currentStep > 2">
|
|
279
|
+
<div class="text-center q-mb-md">
|
|
280
|
+
<p class="text-body1 q-mb-md">
|
|
281
|
+
{{ $t("Scan this QR code with your authenticator app:") }}
|
|
282
|
+
</p>
|
|
283
|
+
|
|
284
|
+
<q-card flat bordered class="qr-card inline-block q-pa-lg">
|
|
285
|
+
<q-img :src="setup.image" width="200px" class="q-mb-md" />
|
|
286
|
+
|
|
287
|
+
<q-expansion-item
|
|
288
|
+
icon="key"
|
|
289
|
+
:label="$t('Enter manually instead')"
|
|
290
|
+
class="text-left"
|
|
291
|
+
>
|
|
292
|
+
<div class="q-pa-md bg-grey-1 rounded-borders">
|
|
293
|
+
<div class="text-caption text-grey-7 q-mb-xs">
|
|
294
|
+
{{ $t("Secret Key:") }}
|
|
295
|
+
</div>
|
|
296
|
+
<div class="row items-center q-gutter-sm">
|
|
297
|
+
<div class="col">
|
|
298
|
+
<q-input
|
|
299
|
+
:model-value="secretVisible ? setup.secret : '●'.repeat(setup.secret.length)"
|
|
300
|
+
readonly
|
|
301
|
+
dense
|
|
302
|
+
outlined
|
|
303
|
+
class="secret-input"
|
|
304
|
+
/>
|
|
305
|
+
</div>
|
|
306
|
+
<q-btn
|
|
307
|
+
flat
|
|
308
|
+
round
|
|
309
|
+
dense
|
|
310
|
+
:icon="secretVisible ? 'visibility_off' : 'visibility'"
|
|
311
|
+
:title="secretVisible ? $t('Hide') : $t('Show')"
|
|
312
|
+
@click="secretVisible = !secretVisible"
|
|
313
|
+
/>
|
|
314
|
+
<q-btn
|
|
315
|
+
flat
|
|
316
|
+
round
|
|
317
|
+
dense
|
|
318
|
+
icon="content_copy"
|
|
319
|
+
:title="$t('Copy to clipboard')"
|
|
320
|
+
@click="onCopy"
|
|
321
|
+
/>
|
|
322
|
+
</div>
|
|
180
323
|
</div>
|
|
181
|
-
</
|
|
324
|
+
</q-expansion-item>
|
|
325
|
+
</q-card>
|
|
182
326
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
327
|
+
<div class="q-mt-md q-gutter-sm">
|
|
328
|
+
<q-btn flat icon="arrow_back" :label="$t('Back')" @click="currentStep = 1" />
|
|
329
|
+
<q-btn
|
|
330
|
+
color="primary"
|
|
331
|
+
:label="$t('I have scanned the code')"
|
|
332
|
+
icon-right="arrow_forward"
|
|
333
|
+
@click="currentStep = 3"
|
|
334
|
+
/>
|
|
335
|
+
<q-btn flat :label="$t('Cancel')" @click="resetSetup" />
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</q-step>
|
|
339
|
+
|
|
340
|
+
<q-step :name="3" :title="$t('Verify Setup')" icon="verified_user">
|
|
341
|
+
<div class="verify-section">
|
|
342
|
+
<p class="text-body1 q-mb-md">
|
|
343
|
+
{{ $t("Enter the 6-digit code from your authenticator app to complete setup:") }}
|
|
344
|
+
</p>
|
|
345
|
+
|
|
346
|
+
<div class="row justify-center q-mb-lg">
|
|
347
|
+
<div class="col-12 col-sm-8 col-md-6">
|
|
348
|
+
<q-input
|
|
349
|
+
v-model.trim="setupForm.code"
|
|
350
|
+
:label="$t('Verification Code')"
|
|
351
|
+
:hint="$t('6-digit code from your authenticator app')"
|
|
352
|
+
type="tel"
|
|
353
|
+
inputmode="numeric"
|
|
354
|
+
pattern="[0-9]*"
|
|
355
|
+
outlined
|
|
356
|
+
maxlength="6"
|
|
357
|
+
class="text-center code-input"
|
|
358
|
+
autocomplete="one-time-code"
|
|
359
|
+
>
|
|
360
|
+
<template #prepend>
|
|
361
|
+
<q-icon name="security" />
|
|
362
|
+
</template>
|
|
363
|
+
</q-input>
|
|
186
364
|
</div>
|
|
187
365
|
</div>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
366
|
+
|
|
367
|
+
<div class="text-center q-gutter-sm">
|
|
368
|
+
<q-btn flat icon="arrow_back" :label="$t('Back')" @click="currentStep = 2" />
|
|
369
|
+
<q-btn
|
|
370
|
+
color="positive"
|
|
371
|
+
size="lg"
|
|
372
|
+
:label="$t('Complete Setup')"
|
|
373
|
+
icon="check_circle"
|
|
374
|
+
:loading="saveLoading"
|
|
375
|
+
:disable="!validSetupCode"
|
|
376
|
+
@click="save"
|
|
377
|
+
/>
|
|
378
|
+
<q-btn flat :label="$t('Cancel')" @click="resetSetup" />
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
</q-step>
|
|
382
|
+
</q-stepper>
|
|
383
|
+
|
|
384
|
+
<q-dialog v-model="disableDialog" persistent>
|
|
385
|
+
<q-card style="width: 480px; max-width: 90vw">
|
|
386
|
+
<q-form @submit.prevent="disableTwoFactor">
|
|
387
|
+
<q-card-section>
|
|
388
|
+
<div class="text-h6">{{ $t("Disable Two-Factor Authentication") }}</div>
|
|
389
|
+
</q-card-section>
|
|
390
|
+
|
|
391
|
+
<q-card-section class="q-pt-none">
|
|
392
|
+
<q-banner rounded class="bg-orange-1 text-dark q-mb-lg">
|
|
393
|
+
<template #avatar>
|
|
394
|
+
<q-icon name="warning" color="warning" />
|
|
395
|
+
</template>
|
|
396
|
+
{{ $t("This will reduce the security of your account.") }}
|
|
397
|
+
</q-banner>
|
|
398
|
+
|
|
399
|
+
<q-input
|
|
400
|
+
v-model="disableForm.password"
|
|
401
|
+
type="password"
|
|
402
|
+
:label="$t('Current password')"
|
|
403
|
+
autocomplete="current-password"
|
|
404
|
+
outlined
|
|
405
|
+
class="q-mb-md"
|
|
406
|
+
/>
|
|
407
|
+
<q-input
|
|
408
|
+
v-model.trim="disableForm.code"
|
|
409
|
+
type="tel"
|
|
410
|
+
inputmode="numeric"
|
|
411
|
+
pattern="[0-9]*"
|
|
412
|
+
maxlength="6"
|
|
413
|
+
:label="$t('Current 2FA code')"
|
|
414
|
+
autocomplete="one-time-code"
|
|
415
|
+
outlined
|
|
416
|
+
/>
|
|
417
|
+
</q-card-section>
|
|
418
|
+
|
|
419
|
+
<q-card-actions align="right">
|
|
420
|
+
<q-btn v-close-popup flat :label="$t('Cancel')" :disable="disableLoading" />
|
|
421
|
+
<q-btn
|
|
422
|
+
type="submit"
|
|
423
|
+
color="negative"
|
|
424
|
+
:label="$t('Disable')"
|
|
425
|
+
:loading="disableLoading"
|
|
426
|
+
:disable="!validDisableForm"
|
|
427
|
+
/>
|
|
428
|
+
</q-card-actions>
|
|
429
|
+
</q-form>
|
|
430
|
+
</q-card>
|
|
431
|
+
</q-dialog>
|
|
191
432
|
</div>
|
|
192
433
|
</template>
|
|
434
|
+
|
|
435
|
+
<style scoped>
|
|
436
|
+
.two-factor-setup{margin:0 auto;max-width:800px}.setup-header,.verify-section{text-align:center}.app-card{height:100%;transition:all .2s ease}.app-card:hover{box-shadow:0 4px 12px rgba(0,0,0,.1);transform:translateY(-2px)}.qr-card{background:linear-gradient(135deg,#f5f7fa,#c3cfe2);border:2px dashed #1976d2}.secret-input{font-family:Courier New,monospace;font-size:14px}.code-input :deep(input){font-family:Courier New,monospace;font-size:24px;font-weight:700;letter-spacing:8px;text-align:center}@media (max-width:600px){.qr-card img{width:180px!important}.code-input :deep(input){font-size:20px;letter-spacing:4px}}
|
|
437
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref } from "vue";
|
|
3
3
|
import { useRoute } from "vue-router";
|
|
4
|
-
const tab = ref("
|
|
4
|
+
const tab = ref("profile");
|
|
5
5
|
const route = useRoute();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
@@ -11,9 +11,8 @@ const route = useRoute();
|
|
|
11
11
|
<q-splitter unit="px" :model-value="120">
|
|
12
12
|
<template #before>
|
|
13
13
|
<q-tabs v-model="tab" vertical active-color="primary">
|
|
14
|
-
<q-route-tab name="
|
|
15
|
-
|
|
16
|
-
to="/User/setting/information" exact />
|
|
14
|
+
<q-route-tab name="profile" icon="sym_o_person" :label="$t('Profile')" to="/User/setting"
|
|
15
|
+
exact />
|
|
17
16
|
<q-route-tab name="password" icon="sym_o_password" :label="$t('Password')"
|
|
18
17
|
to="/User/setting/password" exact />
|
|
19
18
|
<q-route-tab name="style" icon="sym_o_style" :label="$t('Style')" to="/User/setting/style"
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SearchableMenuItem {
|
|
2
|
+
label?: string;
|
|
3
|
+
to?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
children?: SearchableMenuItem[];
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
type TranslateLabel = (label: string) => string;
|
|
9
|
+
export declare function filterMenuItems<T extends SearchableMenuItem>(items: readonly T[] | null | undefined, query: string | null | undefined, translateLabel?: TranslateLabel): T[];
|
|
10
|
+
export {};
|