@hostlink/nuxt-light 1.59.3 → 1.60.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/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/components/l-app-main.vue +1 -1
- package/dist/runtime/components/l-menu.vue +2 -2
- package/dist/runtime/components/l-tab.d.vue.ts +2 -1
- package/dist/runtime/components/l-tab.vue +4 -4
- package/dist/runtime/components/l-tab.vue.d.ts +2 -1
- package/dist/runtime/components/l-table.d.vue.ts +1 -2
- package/dist/runtime/components/l-table.vue +4 -14
- package/dist/runtime/components/l-table.vue.d.ts +1 -2
- package/dist/runtime/models/UserLog.js +14 -4
- package/dist/runtime/pages/System/database/table.vue +2 -3
- package/dist/runtime/pages/User/setting/sessions.d.vue.ts +3 -0
- package/dist/runtime/pages/User/setting/sessions.vue +136 -0
- package/dist/runtime/pages/User/setting/sessions.vue.d.ts +3 -0
- package/dist/runtime/pages/User/setting.vue +2 -0
- package/dist/runtime/pages/UserLog/index.vue +2 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -264,6 +264,11 @@ const routes = [
|
|
|
264
264
|
name: "User-setting-two-factor-auth",
|
|
265
265
|
path: "two-factor-auth",
|
|
266
266
|
file: "runtime/pages/User/setting/two-factor-auth.vue"
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "User-setting-sessions",
|
|
270
|
+
path: "sessions",
|
|
271
|
+
file: "runtime/pages/User/setting/sessions.vue"
|
|
267
272
|
}
|
|
268
273
|
]
|
|
269
274
|
}
|
|
@@ -315,7 +315,7 @@ const onLogout = async () => {
|
|
|
315
315
|
<q-item-section avatar>
|
|
316
316
|
<q-icon name="sym_o_settings_account_box" />
|
|
317
317
|
</q-item-section>
|
|
318
|
-
<q-item-section>{{ $t("
|
|
318
|
+
<q-item-section>{{ $t("Settings") }}</q-item-section>
|
|
319
319
|
</q-item>
|
|
320
320
|
|
|
321
321
|
<q-item v-close-popup v-if="showViewAs" to="/System/view_as">
|
|
@@ -58,7 +58,7 @@ const hasChildLink = () => {
|
|
|
58
58
|
<template v-if="menu.to && (menu.to.startsWith('http://') || menu.to.startsWith('https://'))">
|
|
59
59
|
<q-item v-ripple :href="menu.to" v-if="!value.type" target="_blank">
|
|
60
60
|
<q-item-section avatar>
|
|
61
|
-
<q-icon :name="menu.icon"
|
|
61
|
+
<q-icon :name="menu.icon" />
|
|
62
62
|
</q-item-section>
|
|
63
63
|
<q-item-section>{{ $t(menu.label) }}</q-item-section>
|
|
64
64
|
</q-item>
|
|
@@ -67,7 +67,7 @@ const hasChildLink = () => {
|
|
|
67
67
|
<template v-else>
|
|
68
68
|
<q-item v-ripple :to="menu.to" v-if="!value.type">
|
|
69
69
|
<q-item-section avatar>
|
|
70
|
-
<q-icon :name="menu.icon"
|
|
70
|
+
<q-icon :name="menu.icon" />
|
|
71
71
|
</q-item-section>
|
|
72
72
|
<q-item-section>{{ $t(menu.label) }}</q-item-section>
|
|
73
73
|
</q-item>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { QTabPanelProps, QTabProps } from 'quasar';
|
|
2
|
+
type __VLS_Props = QTabPanelProps & QTabProps;
|
|
2
3
|
declare var __VLS_7: {};
|
|
3
4
|
type __VLS_Slots = {} & {
|
|
4
5
|
default?: (props: typeof __VLS_7) => any;
|
|
5
6
|
};
|
|
6
|
-
declare const __VLS_base: import("vue").DefineComponent<
|
|
7
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
8
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
8
9
|
declare const _default: typeof __VLS_export;
|
|
9
10
|
export default _default;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { getCurrentInstance } from "vue";
|
|
3
3
|
const instance = getCurrentInstance();
|
|
4
|
-
defineProps({
|
|
5
|
-
name: { type:
|
|
6
|
-
disable: { type: Boolean, required: false
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
name: { type: [Number, String], required: false },
|
|
6
|
+
disable: { type: Boolean, required: false },
|
|
7
7
|
icon: { type: null, required: false },
|
|
8
8
|
label: { type: null, required: false },
|
|
9
9
|
alert: { type: [Boolean, String], required: false, skipCheck: true },
|
|
@@ -24,7 +24,7 @@ const parent_type = instance?.parent?.type.name;
|
|
|
24
24
|
</template>
|
|
25
25
|
|
|
26
26
|
<template v-if="parent_type == 'QTabs'">
|
|
27
|
-
<q-tab v-bind="$props" :label="$t($props.label
|
|
27
|
+
<q-tab v-bind="$props" :label="$t(String($props.label))">
|
|
28
28
|
</q-tab>
|
|
29
29
|
</template>
|
|
30
30
|
</template>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { QTabPanelProps, QTabProps } from 'quasar';
|
|
2
|
+
type __VLS_Props = QTabPanelProps & QTabProps;
|
|
2
3
|
declare var __VLS_7: {};
|
|
3
4
|
type __VLS_Slots = {} & {
|
|
4
5
|
default?: (props: typeof __VLS_7) => any;
|
|
5
6
|
};
|
|
6
|
-
declare const __VLS_base: import("vue").DefineComponent<
|
|
7
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
8
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
8
9
|
declare const _default: typeof __VLS_export;
|
|
9
10
|
export default _default;
|
|
@@ -15,8 +15,7 @@ export type LTableColumn = QTableColumn & {
|
|
|
15
15
|
* @deprecated use gql instead
|
|
16
16
|
*/
|
|
17
17
|
gqlField?: string | Array<string> | Object;
|
|
18
|
-
|
|
19
|
-
searchMethod?: string;
|
|
18
|
+
searchMethod?: "equals" | "contains";
|
|
20
19
|
autoWidth?: boolean;
|
|
21
20
|
};
|
|
22
21
|
export type LTableProps = QTableProps & {
|
|
@@ -429,17 +429,6 @@ const hasRowExpand = computed(() => {
|
|
|
429
429
|
const hasActions = computed(() => {
|
|
430
430
|
return props.actions.length > 0 || ss.indexOf("actions") >= 0;
|
|
431
431
|
});
|
|
432
|
-
const getCellStyle = (col, row) => {
|
|
433
|
-
const style = col.cellStyle ?? {};
|
|
434
|
-
if (col.backgroundColor) {
|
|
435
|
-
if (typeof col.backgroundColor == "function") {
|
|
436
|
-
style.backgroundColor = col.backgroundColor(row);
|
|
437
|
-
} else {
|
|
438
|
-
style.backgroundColor = col.backgroundColor;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return style;
|
|
442
|
-
};
|
|
443
432
|
const localSelected = computed({
|
|
444
433
|
get() {
|
|
445
434
|
return props.selected;
|
|
@@ -578,8 +567,8 @@ const hasFilters = computed(() => {
|
|
|
578
567
|
<slot :name="'body-cell-' + col.name" v-bind="props"></slot>
|
|
579
568
|
</template>
|
|
580
569
|
<template v-else>
|
|
581
|
-
<q-td :key="col.name" :props="props" :auto-width="col.autoWidth ?? false"
|
|
582
|
-
|
|
570
|
+
<q-td :key="col.name" :props="props" :auto-width="col.autoWidth ?? false"><template
|
|
571
|
+
v-if="col.to" class="bg-primary">
|
|
583
572
|
<l-link :to="col.to(props.row)" v-if="col.to(props.row)">{{ col.value }}</l-link>
|
|
584
573
|
</template>
|
|
585
574
|
<template v-else-if="col.component">
|
|
@@ -655,7 +644,8 @@ const hasFilters = computed(() => {
|
|
|
655
644
|
<q-td v-if="selection != 'none'" auto-width />
|
|
656
645
|
<q-td v-if="hasRowExpand" auto-width />
|
|
657
646
|
<q-td v-if="hasActions" auto-width />
|
|
658
|
-
<q-td v-for="col in props.cols">
|
|
647
|
+
<q-td v-for="col in props.cols" :key="col.name" :style="col.style">
|
|
648
|
+
|
|
659
649
|
<template v-if="col.searchable">
|
|
660
650
|
|
|
661
651
|
<template v-if="col.searchType == 'number'">
|
|
@@ -15,8 +15,7 @@ export type LTableColumn = QTableColumn & {
|
|
|
15
15
|
* @deprecated use gql instead
|
|
16
16
|
*/
|
|
17
17
|
gqlField?: string | Array<string> | Object;
|
|
18
|
-
|
|
19
|
-
searchMethod?: string;
|
|
18
|
+
searchMethod?: "equals" | "contains";
|
|
20
19
|
autoWidth?: boolean;
|
|
21
20
|
};
|
|
22
21
|
export type LTableProps = QTableProps & {
|
|
@@ -6,7 +6,8 @@ export default defineLightModel({
|
|
|
6
6
|
label: "ID",
|
|
7
7
|
sortable: true,
|
|
8
8
|
searchable: true,
|
|
9
|
-
autoWidth: true
|
|
9
|
+
autoWidth: true,
|
|
10
|
+
searchMethod: "equals"
|
|
10
11
|
},
|
|
11
12
|
username: {
|
|
12
13
|
label: "User",
|
|
@@ -22,7 +23,8 @@ export default defineLightModel({
|
|
|
22
23
|
logout_dt: {
|
|
23
24
|
label: "Logout time",
|
|
24
25
|
sortable: true,
|
|
25
|
-
searchable: true
|
|
26
|
+
searchable: true,
|
|
27
|
+
searchType: "date"
|
|
26
28
|
},
|
|
27
29
|
result: {
|
|
28
30
|
label: "Result",
|
|
@@ -38,8 +40,11 @@ export default defineLightModel({
|
|
|
38
40
|
label: "FAIL",
|
|
39
41
|
value: "FAIL"
|
|
40
42
|
}
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
+
]
|
|
44
|
+
/* style(row) {
|
|
45
|
+
return row.result === "SUCCESS" ? "background-color: var(--q-positive);" : "background-color: var(--q-negative);";
|
|
46
|
+
|
|
47
|
+
} */
|
|
43
48
|
},
|
|
44
49
|
user_agent: {
|
|
45
50
|
label: "User agent",
|
|
@@ -52,6 +57,11 @@ export default defineLightModel({
|
|
|
52
57
|
sortable: true,
|
|
53
58
|
searchable: true,
|
|
54
59
|
searchType: "date"
|
|
60
|
+
},
|
|
61
|
+
ip: {
|
|
62
|
+
label: "IP Address",
|
|
63
|
+
sortable: true,
|
|
64
|
+
searchable: true
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
});
|
|
@@ -3,7 +3,7 @@ import { q, m, useLight, useAsyncData } from "#imports";
|
|
|
3
3
|
import { resolveComponent, ref, reactive, computed } from "vue";
|
|
4
4
|
const light = useLight();
|
|
5
5
|
const { data, refresh } = await useAsyncData("database", async () => {
|
|
6
|
-
|
|
6
|
+
return await q({
|
|
7
7
|
system: {
|
|
8
8
|
database: {
|
|
9
9
|
table: true,
|
|
@@ -12,8 +12,7 @@ const { data, refresh } = await useAsyncData("database", async () => {
|
|
|
12
12
|
tableStatus: true
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
});
|
|
16
|
-
return system.database;
|
|
15
|
+
}).then((res) => res.system.database);
|
|
17
16
|
});
|
|
18
17
|
const SYSTEM_TABLES = ["Config", "EventLog", "MailLog", "Permission", "Role", "SystemValue", "Translate", "User", "UserLog", "UserRole", "MyFavorite", "CustomField"];
|
|
19
18
|
const custom_tables = computed(() => {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const _default: typeof __VLS_export;
|
|
2
|
+
export default _default;
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { q, m, useQuasar, useAsyncData } from "#imports";
|
|
3
|
+
const $q = useQuasar();
|
|
4
|
+
const { data: my, refresh } = await useAsyncData("user-sessions", async () => {
|
|
5
|
+
return await q({
|
|
6
|
+
my: {
|
|
7
|
+
sessions: true
|
|
8
|
+
}
|
|
9
|
+
}).then((res) => res.my);
|
|
10
|
+
});
|
|
11
|
+
const getDeviceIcon = (userAgent) => {
|
|
12
|
+
if (userAgent.includes("Mobile")) return "smartphone";
|
|
13
|
+
if (userAgent.includes("Tablet")) return "tablet";
|
|
14
|
+
return "desktop_mac";
|
|
15
|
+
};
|
|
16
|
+
const getLocationDisplay = (session) => {
|
|
17
|
+
if (session.location?.country) {
|
|
18
|
+
return `${session.location.country} ${session.ip}`;
|
|
19
|
+
}
|
|
20
|
+
return session.ip;
|
|
21
|
+
};
|
|
22
|
+
const getLocationCode = (session) => {
|
|
23
|
+
return session.location?.countryCode || "Unknown";
|
|
24
|
+
};
|
|
25
|
+
const revokeSession = async (jti) => {
|
|
26
|
+
$q.dialog({
|
|
27
|
+
title: "Confirm",
|
|
28
|
+
message: "Are you sure you want to revoke this session?",
|
|
29
|
+
cancel: true,
|
|
30
|
+
persistent: true
|
|
31
|
+
}).onOk(async () => {
|
|
32
|
+
try {
|
|
33
|
+
await m("revokeSession", {
|
|
34
|
+
jti
|
|
35
|
+
});
|
|
36
|
+
$q.notify({
|
|
37
|
+
type: "positive",
|
|
38
|
+
message: "Session revoked successfully."
|
|
39
|
+
});
|
|
40
|
+
await refresh();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
$q.notify({
|
|
43
|
+
type: "negative",
|
|
44
|
+
message: "Failed to revoke session."
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div>
|
|
53
|
+
|
|
54
|
+
<q-list bordered separator>
|
|
55
|
+
<q-item-label header>This is a list of devices that have logged into your account. Revoke any sessions that
|
|
56
|
+
you do not recognize.</q-item-label>
|
|
57
|
+
|
|
58
|
+
<q-item v-for="session in my.sessions" :key="session.jti">
|
|
59
|
+
<q-item-section avatar>
|
|
60
|
+
<q-icon :name="getDeviceIcon(session.user_agent)" color="primary" size="lg" />
|
|
61
|
+
</q-item-section>
|
|
62
|
+
|
|
63
|
+
<q-item-section>
|
|
64
|
+
<q-item-label class="text-weight-bold">
|
|
65
|
+
{{ getLocationDisplay(session) }}
|
|
66
|
+
</q-item-label>
|
|
67
|
+
<q-item-label caption lines="2">
|
|
68
|
+
<div class="flex items-center q-my-xs">
|
|
69
|
+
<q-badge color="positive" label="active" />
|
|
70
|
+
<q-badge v-if="session.is_current" color="info" label="This device" class="q-ml-sm" />
|
|
71
|
+
</div>
|
|
72
|
+
<div class="q-mt-sm">
|
|
73
|
+
Last accessed on {{ session.last_access_time }}
|
|
74
|
+
</div>
|
|
75
|
+
<div v-if="session.location?.city" class="text-caption">
|
|
76
|
+
Seen in {{ getLocationCode(session) }}
|
|
77
|
+
</div>
|
|
78
|
+
</q-item-label>
|
|
79
|
+
</q-item-section>
|
|
80
|
+
|
|
81
|
+
<q-item-section side top>
|
|
82
|
+
<q-btn flat dense round icon="info" color="primary">
|
|
83
|
+
<q-popup-proxy anchor="top right" self="top left" :offset="[0, 10]">
|
|
84
|
+
<q-card style="min-width: 300px">
|
|
85
|
+
<q-card-section>
|
|
86
|
+
<div class="text-h6">Session Details</div>
|
|
87
|
+
</q-card-section>
|
|
88
|
+
|
|
89
|
+
<q-separator />
|
|
90
|
+
|
|
91
|
+
<q-card-section>
|
|
92
|
+
<div class="q-mb-md">
|
|
93
|
+
<div class="text-caption text-grey">Session ID</div>
|
|
94
|
+
<div class="text-body2">{{ session.jti }}</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div class="q-mb-md">
|
|
98
|
+
<div class="text-caption text-grey">IP Address</div>
|
|
99
|
+
<div class="text-body2">{{ session.ip }}</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="q-mb-md">
|
|
103
|
+
<div class="text-caption text-grey">Login Time</div>
|
|
104
|
+
<div class="text-body2">{{ session.login_dt }}</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div v-if="session.location" class="q-mb-md">
|
|
108
|
+
<div class="text-caption text-grey">Location</div>
|
|
109
|
+
<div class="text-body2">
|
|
110
|
+
{{ session.location.city }}, {{ session.location.regionName }}, {{
|
|
111
|
+
session.location.country }}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div class="q-mb-md">
|
|
116
|
+
<div class="text-caption text-grey">User Agent</div>
|
|
117
|
+
<div class="text-body2 text-break" style="font-size: 12px">{{ session.user_agent
|
|
118
|
+
}}</div>
|
|
119
|
+
</div>
|
|
120
|
+
</q-card-section>
|
|
121
|
+
|
|
122
|
+
<q-separator />
|
|
123
|
+
|
|
124
|
+
<q-card-actions align="right">
|
|
125
|
+
<q-btn flat label="Close" v-close-popup />
|
|
126
|
+
<q-btn flat label="Revoke" color="negative" :disable="session.is_current"
|
|
127
|
+
@click="revokeSession(session.jti)" />
|
|
128
|
+
</q-card-actions>
|
|
129
|
+
</q-card>
|
|
130
|
+
</q-popup-proxy>
|
|
131
|
+
</q-btn>
|
|
132
|
+
</q-item-section>
|
|
133
|
+
</q-item>
|
|
134
|
+
</q-list>
|
|
135
|
+
</div>
|
|
136
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const _default: typeof __VLS_export;
|
|
2
|
+
export default _default;
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -28,6 +28,8 @@ const route = useRoute();
|
|
|
28
28
|
to="/User/setting/favorite" exact />
|
|
29
29
|
|
|
30
30
|
<q-route-tab name="menu" icon="sym_o_menu" :label="$t('Menu')" to="/User/setting/menu" exact />
|
|
31
|
+
<q-route-tab name="sessions" icon="sym_o_devices" :label="$t('Sessions')"
|
|
32
|
+
to="/User/setting/sessions" exact />
|
|
31
33
|
</q-tabs>
|
|
32
34
|
</template>
|
|
33
35
|
|