@hostlink/nuxt-light 1.8.0 → 1.8.2
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/runtime/components/L/Revision.vue +65 -20
- package/dist/runtime/components/L/Storage.vue +36 -0
- package/dist/runtime/components/l-app-main.vue +50 -61
- package/dist/runtime/components/l-app.vue +7 -1
- package/dist/runtime/components/l-card.vue +14 -19
- package/dist/runtime/components/l-table.vue +124 -110
- package/dist/runtime/components/l-tabs.vue +1 -1
- package/dist/runtime/pages/EventLog/index.vue +4 -2
- package/dist/runtime/pages/System/view_as.vue +8 -6
- package/dist/runtime/pages/User/index.vue +0 -2
- package/package.json +2 -1
package/dist/module.json
CHANGED
|
@@ -3,6 +3,17 @@ import { Dialog, Notify } from "quasar"
|
|
|
3
3
|
import { computed, ref, reactive } from "vue";
|
|
4
4
|
import { q, m } from "#imports";
|
|
5
5
|
|
|
6
|
+
import * as Diff2Html from 'diff2html';
|
|
7
|
+
import 'diff2html/bundles/css/diff2html.min.css';
|
|
8
|
+
|
|
9
|
+
/* const diffs = '--- a/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n+++ b/server/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go\n@@ -1035,6 +1035,17 @@ func Prctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n+func Pselect(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {\n+\tr0, _, e1 := Syscall6(SYS_PSELECT6, uintptr(nfd), uintptr(unsafe.Pointer(r)), uintptr(unsafe.Pointer(w)), uintptr(unsafe.Pointer(e)), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)))\n+\tn = int(r0)\n+\tif e1 != 0 {\n+\t\terr = errnoErr(e1)\n+\t}\n+\treturn\n+}\n+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n+\n func read(fd int, p []byte) (n int, err error) {\n \tvar _p0 unsafe.Pointer\n \tif len(p) > 0 {\n';
|
|
10
|
+
const html = Diff2Html.html(diffs, {
|
|
11
|
+
drawFileList: true,
|
|
12
|
+
matching: 'lines',
|
|
13
|
+
outputFormat: 'side-by-side',
|
|
14
|
+
});
|
|
15
|
+
*/
|
|
16
|
+
|
|
6
17
|
const props = defineProps({
|
|
7
18
|
modelId: {
|
|
8
19
|
type: String,
|
|
@@ -25,7 +36,8 @@ const { revisionsByModel } = await q({
|
|
|
25
36
|
revisionBy: true,
|
|
26
37
|
createdTime: true,
|
|
27
38
|
content: true,
|
|
28
|
-
delta: true
|
|
39
|
+
delta: true,
|
|
40
|
+
diff: true
|
|
29
41
|
}
|
|
30
42
|
})
|
|
31
43
|
|
|
@@ -102,28 +114,50 @@ const getFilteredContent = computed(() => {
|
|
|
102
114
|
} else {
|
|
103
115
|
return selectedRevision.value.content;
|
|
104
116
|
}
|
|
105
|
-
|
|
106
117
|
})
|
|
118
|
+
|
|
119
|
+
const outputFormat = ref('side-by-side');
|
|
120
|
+
const getDiffHtml = (diffs) => {
|
|
121
|
+
if (!diffs) {
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
return Diff2Html.html(diffs, {
|
|
125
|
+
drawFileList: false,
|
|
126
|
+
matching: 'lines',
|
|
127
|
+
outputFormat: outputFormat.value,
|
|
128
|
+
highlight: true
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
107
133
|
</script>
|
|
134
|
+
|
|
108
135
|
<template>
|
|
109
136
|
<div>
|
|
110
137
|
|
|
111
|
-
|
|
138
|
+
<div v-html="html"></div>
|
|
112
139
|
<q-dialog v-model="showDialog" full-height full-width>
|
|
113
140
|
<q-card>
|
|
114
141
|
<q-toolbar>
|
|
115
142
|
<l-btn label="Restore" icon="sym_o_restore" permission="revision.restore" @click="onRestore"
|
|
116
143
|
:disabled="!isAllowRestore" />
|
|
117
144
|
|
|
118
|
-
<q-checkbox label="Show
|
|
145
|
+
<q-checkbox label="Show changed only" v-model="showOnlyDelta" :color="$light.color"></q-checkbox>
|
|
146
|
+
|
|
147
|
+
<q-btn-toggle class="q-mx-md" v-model="outputFormat" :toggle-color="$light.color" :options="[
|
|
148
|
+
{ label: 'side-by-side', value: 'side-by-side' }, { label: 'line-by-line', value: 'line-by-line' }
|
|
149
|
+
]" />
|
|
119
150
|
<q-space />
|
|
120
151
|
<q-btn flat round dense icon="close" @click="showDialog = false" />
|
|
121
152
|
</q-toolbar>
|
|
122
153
|
|
|
123
154
|
|
|
124
155
|
<div class="q-pa-md">
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
|
|
157
|
+
<div class="text-h6 q-mb-md">
|
|
158
|
+
Revision by: {{ selectedRevision.revisionBy }} <br>
|
|
159
|
+
Created at: {{ selectedRevision.createdTime }}
|
|
160
|
+
|
|
127
161
|
</div>
|
|
128
162
|
|
|
129
163
|
<q-markup-table dense separator="cell" flat bordered>
|
|
@@ -135,23 +169,34 @@ const getFilteredContent = computed(() => {
|
|
|
135
169
|
</th>
|
|
136
170
|
<th>Field</th>
|
|
137
171
|
<th>Value</th>
|
|
138
|
-
<th>
|
|
172
|
+
<th>Changed</th>
|
|
139
173
|
</tr>
|
|
140
174
|
</thead>
|
|
141
175
|
<tbody>
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
176
|
+
<template v-for="(value, key) in getFilteredContent" :key="key">
|
|
177
|
+
|
|
178
|
+
<tr>
|
|
179
|
+
|
|
180
|
+
<td><q-checkbox v-model="restore_fields" :val="key"
|
|
181
|
+
:color="$light.color"></q-checkbox>
|
|
182
|
+
</td>
|
|
183
|
+
<td :class="{ 'bg-yellow-3': selectedRevision.delta[key] != undefined }">{{ key }}
|
|
184
|
+
</td>
|
|
185
|
+
<td>
|
|
186
|
+
<pre>{{ value }}</pre>
|
|
187
|
+
</td>
|
|
188
|
+
<td>
|
|
189
|
+
<pre>{{ selectedRevision.delta[key] }}</pre>
|
|
190
|
+
</td>
|
|
191
|
+
|
|
192
|
+
</tr>
|
|
193
|
+
|
|
194
|
+
<tr v-if="getDiffHtml(selectedRevision.diff[key])">
|
|
195
|
+
<td colspan="4">
|
|
196
|
+
<div v-html="getDiffHtml(selectedRevision.diff[key])"></div>
|
|
197
|
+
</td>
|
|
198
|
+
</tr>
|
|
199
|
+
</template>
|
|
155
200
|
</tbody>
|
|
156
201
|
|
|
157
202
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { format } from "quasar"
|
|
4
|
+
const { humanStorageSize } = format
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
storage: {
|
|
7
|
+
type: Object,
|
|
8
|
+
required: true
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const usagePercent = computed(() => {
|
|
14
|
+
return Math.round((props.storage.totalSpace - props.storage.freeSpace) / props.storage.totalSpace)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const progressColor = computed(() => {
|
|
18
|
+
if (usagePercent.value > 0.95) {
|
|
19
|
+
return "negative"
|
|
20
|
+
}
|
|
21
|
+
if (usagePercent.value > 0.9) {
|
|
22
|
+
return "warning"
|
|
23
|
+
}
|
|
24
|
+
return "primary"
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
</script>
|
|
28
|
+
<template>
|
|
29
|
+
<q-card>
|
|
30
|
+
<q-card-section>
|
|
31
|
+
{{ $t("Storage") }}
|
|
32
|
+
<q-linear-progress rounded size="20px" :value="usagePercent" :color="progressColor" class="q-mt-sm" />
|
|
33
|
+
{{ $t('storage_usage', [humanStorageSize(storage.freeSpace), humanStorageSize(storage.totalSpace)]) }}
|
|
34
|
+
</q-card-section>
|
|
35
|
+
</q-card>
|
|
36
|
+
</template>
|
|
@@ -3,29 +3,56 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
3
3
|
import { useLight, q, m } from "#imports";
|
|
4
4
|
import { useQuasar, Loading, Dialog } from 'quasar';
|
|
5
5
|
import { useI18n } from 'vue-i18n';
|
|
6
|
-
import { ref, computed, reactive, provide, watch, toRaw
|
|
6
|
+
import { ref, computed, reactive, provide, watch, toRaw } from 'vue';
|
|
7
7
|
import { useRuntimeConfig } from 'nuxt/app';
|
|
8
8
|
|
|
9
9
|
Loading.show()
|
|
10
|
-
|
|
11
|
-
/* import { download } from './../lib/SystemValue'
|
|
12
|
-
await download();
|
|
13
|
-
*/
|
|
10
|
+
|
|
14
11
|
const config = useRuntimeConfig();
|
|
15
12
|
|
|
16
13
|
const appVersion = config.public.appVersion ?? '0.0.1';
|
|
17
14
|
|
|
18
15
|
const quasar = useQuasar();
|
|
19
16
|
const tt = await q({
|
|
20
|
-
system:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
system: {
|
|
18
|
+
devMode: true,
|
|
19
|
+
time: true,
|
|
20
|
+
storage: {
|
|
21
|
+
freeSpace: true,
|
|
22
|
+
usageSpace: true,
|
|
23
|
+
totalSpace: true,
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
app: {
|
|
27
|
+
menus: true,
|
|
28
|
+
viewAsMode: true,
|
|
29
|
+
languages: true,
|
|
30
|
+
copyrightName: true,
|
|
31
|
+
copyrightYear: true,
|
|
32
|
+
hasFavorite: true,
|
|
33
|
+
i18nMessages: {
|
|
34
|
+
name: true,
|
|
35
|
+
value: true
|
|
36
|
+
}
|
|
37
|
+
}, my: {
|
|
38
|
+
username: true,
|
|
39
|
+
first_name: true,
|
|
40
|
+
last_name: true,
|
|
41
|
+
roles: true,
|
|
42
|
+
styles: true,
|
|
43
|
+
language: true,
|
|
44
|
+
permissions: true,
|
|
45
|
+
default_page: true,
|
|
46
|
+
myFavorites: {
|
|
47
|
+
my_favorite_id: true,
|
|
48
|
+
label: true,
|
|
49
|
+
path: true,
|
|
50
|
+
icon: true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
27
53
|
})
|
|
28
54
|
|
|
55
|
+
|
|
29
56
|
let app = tt.app
|
|
30
57
|
let my = reactive(tt.my)
|
|
31
58
|
|
|
@@ -54,13 +81,6 @@ i18n.locale = my.language || 'en';
|
|
|
54
81
|
|
|
55
82
|
let system = tt.system;
|
|
56
83
|
|
|
57
|
-
if (light.isGranted("system.storage")) {
|
|
58
|
-
let s = await q("system", ["diskFreeSpace", "diskUsageSpace", "diskTotalSpace", "diskFreeSpacePercent"]);
|
|
59
|
-
//merge system value
|
|
60
|
-
system = { ...system, ...s };
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
84
|
// message
|
|
65
85
|
let messages = i18n.messages.value[i18n.locale];
|
|
66
86
|
for (let t of app.i18nMessages) {
|
|
@@ -168,35 +188,17 @@ const exitViewAs = async () => {
|
|
|
168
188
|
}
|
|
169
189
|
|
|
170
190
|
const storageColor = computed(() => {
|
|
171
|
-
if (system) {
|
|
172
|
-
|
|
191
|
+
if (system.storage) {
|
|
192
|
+
const percent = system.storage.freeSpace / system.storage.totalSpace;
|
|
193
|
+
if (percent < 0.05) {
|
|
173
194
|
return "negative"
|
|
174
195
|
}
|
|
175
|
-
if (
|
|
196
|
+
if (percent < 0.1) {
|
|
176
197
|
return "warning"
|
|
177
198
|
}
|
|
178
199
|
}
|
|
179
200
|
})
|
|
180
201
|
|
|
181
|
-
const storageProgressColor = computed(() => {
|
|
182
|
-
if (system) {
|
|
183
|
-
if (system.diskFreeSpacePercent < 0.05) {
|
|
184
|
-
return "negative"
|
|
185
|
-
}
|
|
186
|
-
if (system.diskFreeSpacePercent < 0.1) {
|
|
187
|
-
return "warning"
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return "primary"
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
const storageUsagePercent = computed(() => {
|
|
194
|
-
if (system) {
|
|
195
|
-
return 1 - system.diskFreeSpacePercent;
|
|
196
|
-
}
|
|
197
|
-
return 0;
|
|
198
|
-
})
|
|
199
|
-
|
|
200
202
|
const containerClass = computed(() => {
|
|
201
203
|
return (light.theme == 'dark') ? {} : { 'bg-grey-2': true }
|
|
202
204
|
})
|
|
@@ -223,8 +225,6 @@ const onToggleFav = async () => {
|
|
|
223
225
|
|
|
224
226
|
//reload my.myFavorites
|
|
225
227
|
light.reloadMyFavorites();
|
|
226
|
-
|
|
227
|
-
|
|
228
228
|
return;
|
|
229
229
|
}
|
|
230
230
|
|
|
@@ -320,24 +320,13 @@ if (route.fullPath == "/" && my.default_page) {
|
|
|
320
320
|
</q-item>
|
|
321
321
|
</q-list>
|
|
322
322
|
</q-menu>
|
|
323
|
-
|
|
324
|
-
|
|
325
323
|
</q-btn>
|
|
326
324
|
|
|
327
|
-
<q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="system"
|
|
325
|
+
<q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="system.storage"
|
|
326
|
+
:color="storageColor">
|
|
328
327
|
<q-menu>
|
|
329
|
-
<
|
|
330
|
-
<q-card-section>
|
|
331
|
-
{{ $t("Storage") }}
|
|
332
|
-
<q-linear-progress rounded size="20px" :value="storageUsagePercent"
|
|
333
|
-
:color="storageProgressColor" class="q-mt-sm" />
|
|
334
|
-
{{ $t('storage_usage', [system.diskFreeSpace, system.diskTotalSpace]) }}
|
|
335
|
-
</q-card-section>
|
|
336
|
-
|
|
337
|
-
</q-card>
|
|
328
|
+
<l-storage :storage="system.storage" style="width:250px" />
|
|
338
329
|
</q-menu>
|
|
339
|
-
|
|
340
|
-
|
|
341
330
|
</q-btn>
|
|
342
331
|
|
|
343
332
|
<div class="q-mx-sm" v-if="$q.screen.gt.xs">
|
|
@@ -422,7 +411,8 @@ if (route.fullPath == "/" && my.default_page) {
|
|
|
422
411
|
</q-scroll-area>
|
|
423
412
|
</q-drawer>
|
|
424
413
|
|
|
425
|
-
<q-page-container :class="containerClass" :style="containerStyle">
|
|
414
|
+
<q-page-container :class="containerClass" :style="containerStyle"> <!-- Error message -->
|
|
415
|
+
<slot name="header"></slot>
|
|
426
416
|
<q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for=" error in errors " rounded>
|
|
427
417
|
{{ error }}
|
|
428
418
|
|
|
@@ -448,9 +438,8 @@ if (route.fullPath == "/" && my.default_page) {
|
|
|
448
438
|
<q-item>
|
|
449
439
|
<q-item-section>
|
|
450
440
|
{{ light.getCompany() }} {{ appVersion }} - Copyright {{ app.copyrightYear }} {{ app.copyrightName
|
|
451
|
-
}}.
|
|
452
|
-
|
|
453
|
-
light.getVersion() }}
|
|
441
|
+
}}. Build
|
|
442
|
+
{{ light.getVersion() }}
|
|
454
443
|
</q-item-section>
|
|
455
444
|
</q-item>
|
|
456
445
|
</q-footer>
|
|
@@ -3,6 +3,9 @@ import { useLight, q, m } from '#imports'
|
|
|
3
3
|
import { ref, computed } from 'vue'
|
|
4
4
|
import { type QCardProps } from 'quasar';
|
|
5
5
|
|
|
6
|
+
const minimized = defineModel<boolean>("minimized", { default: false })
|
|
7
|
+
const maximized = defineModel<boolean>("maximized", { default: false })
|
|
8
|
+
|
|
6
9
|
export interface LCardProps extends QCardProps {
|
|
7
10
|
loading?: boolean;
|
|
8
11
|
title?: string;
|
|
@@ -10,9 +13,9 @@ export interface LCardProps extends QCardProps {
|
|
|
10
13
|
* Permission to access this card, if not granted, the card will not be shown, if the user is admin, a lock icon will be shown to allow the user to grant the permission
|
|
11
14
|
*/
|
|
12
15
|
permission?: string
|
|
13
|
-
fullscreenable?: boolean
|
|
14
16
|
closeable?: boolean
|
|
15
|
-
minimizable?: boolean
|
|
17
|
+
minimizable?: boolean,
|
|
18
|
+
maximizable?: boolean
|
|
16
19
|
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -31,26 +34,16 @@ const attrs = computed(() => {
|
|
|
31
34
|
return a;
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
const
|
|
37
|
+
const barClass = computed(() => {
|
|
35
38
|
const c = ["text-white"];
|
|
36
39
|
const color = light.color;
|
|
37
40
|
c.push(`bg-${color}`);
|
|
38
41
|
return c;
|
|
39
42
|
});
|
|
40
43
|
|
|
41
|
-
const minimize = ref(false);
|
|
42
|
-
|
|
43
|
-
const icon = computed(() => minimize.value ? "sym_o_add" : "sym_o_remove");
|
|
44
|
-
|
|
45
|
-
const fullScreen = ref(false);
|
|
46
|
-
|
|
47
|
-
const fullScreenIcon = computed(() => fullScreen.value ? "sym_o_fullscreen_exit" : "sym_o_fullscreen");
|
|
48
|
-
|
|
49
|
-
const showBody = computed(() => !minimize.value || fullScreen.value);
|
|
50
|
-
|
|
51
44
|
const showBar = computed(() => {
|
|
52
45
|
if (props.closeable) return true;
|
|
53
|
-
if (props.
|
|
46
|
+
if (props.maximizable) return true;
|
|
54
47
|
if (props.minimizable) return true;
|
|
55
48
|
if (props.title !== undefined) return true;
|
|
56
49
|
if (showSecurity.value) return true;
|
|
@@ -115,9 +108,9 @@ const onUpdatePermission = async (role: any) => {
|
|
|
115
108
|
|
|
116
109
|
</script>
|
|
117
110
|
<template>
|
|
118
|
-
<q-card v-bind="attrs" :class="{ 'fullscreen':
|
|
111
|
+
<q-card v-bind="attrs" :class="{ 'fullscreen': maximized, 'no-margin': maximized }"
|
|
119
112
|
v-if="$light.isGranted(permission)" :dark="$q.dark.isActive">
|
|
120
|
-
<q-bar :class="
|
|
113
|
+
<q-bar :class="barClass" v-if="showBar">
|
|
121
114
|
<div>{{ $t(title ?? "") }}</div>
|
|
122
115
|
<q-space />
|
|
123
116
|
|
|
@@ -134,11 +127,13 @@ const onUpdatePermission = async (role: any) => {
|
|
|
134
127
|
</q-menu>
|
|
135
128
|
</q-btn>
|
|
136
129
|
|
|
137
|
-
<q-btn dense flat :icon="
|
|
138
|
-
|
|
130
|
+
<q-btn dense flat :icon="minimized ? 'sym_o_add' : 'sym_o_remove'" @click="minimized = !minimized"
|
|
131
|
+
v-if="minimizable && !maximized" />
|
|
132
|
+
<q-btn dense flat @click="maximized = !maximized"
|
|
133
|
+
:icon="maximized ? 'sym_o_select_window_2' : 'sym_o_crop_square'" v-if="maximizable" />
|
|
139
134
|
<q-btn dense flat icon="sym_o_close" v-close-popup v-if="closeable" />
|
|
140
135
|
</q-bar>
|
|
141
|
-
<template v-if="
|
|
136
|
+
<template v-if="!minimized || maximized">
|
|
142
137
|
<slot></slot>
|
|
143
138
|
</template>
|
|
144
139
|
|
|
@@ -6,6 +6,9 @@ import { t, q, useLight, GQLFieldBuilder, model } from '../';
|
|
|
6
6
|
import { toQuery } from '@hostlink/light';
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
const minimized = defineModel<boolean>("minimized", { default: false })
|
|
10
|
+
const maximized = defineModel<boolean>("maximized", { default: false })
|
|
11
|
+
|
|
9
12
|
// extends QTableColumn
|
|
10
13
|
export interface LTableColumn extends QTableColumn {
|
|
11
14
|
searchable?: boolean,
|
|
@@ -16,7 +19,8 @@ export interface LTableColumn extends QTableColumn {
|
|
|
16
19
|
backgroundColor?: string | Function
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
const errors = ref<InstanceType<any>>([
|
|
22
|
+
const errors = ref<InstanceType<any>>([
|
|
23
|
+
]);
|
|
20
24
|
|
|
21
25
|
export interface LTableProps extends QTableProps {
|
|
22
26
|
columns?: Array<LTableColumn>,
|
|
@@ -26,6 +30,8 @@ export interface LTableProps extends QTableProps {
|
|
|
26
30
|
modelName?: string | null | undefined,
|
|
27
31
|
searchable?: boolean | null | undefined,
|
|
28
32
|
selected?: Array<any>,
|
|
33
|
+
maximizable: boolean,
|
|
34
|
+
minimizable: boolean,
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
const props = withDefaults(defineProps<LTableProps>(), {
|
|
@@ -43,6 +49,8 @@ const props = withDefaults(defineProps<LTableProps>(), {
|
|
|
43
49
|
},
|
|
44
50
|
fullscreen: false,
|
|
45
51
|
searchable: false,
|
|
52
|
+
maximizable: false,
|
|
53
|
+
minimizable: false,
|
|
46
54
|
selected: () => [],
|
|
47
55
|
loadingLabel: "Loading...",
|
|
48
56
|
noDataLabel: "No data available",
|
|
@@ -158,9 +166,6 @@ const validateData = () => {
|
|
|
158
166
|
|
|
159
167
|
if (props.actions.includes("edit")) {
|
|
160
168
|
if (rows.value.length > 0) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
169
|
//get first row
|
|
165
170
|
let row = rows.value[0];
|
|
166
171
|
//check has primary key
|
|
@@ -375,7 +380,7 @@ const getFilterValue = () => {
|
|
|
375
380
|
|
|
376
381
|
|
|
377
382
|
const onFilters = () => {
|
|
378
|
-
|
|
383
|
+
|
|
379
384
|
//clone the filters
|
|
380
385
|
onRequest({
|
|
381
386
|
pagination: {
|
|
@@ -420,7 +425,6 @@ const attrs = computed(() => {
|
|
|
420
425
|
bordered: light.getStyle("tableBorder"),
|
|
421
426
|
separator: light.getStyle("tableSeparator"),
|
|
422
427
|
},
|
|
423
|
-
title: props.title,
|
|
424
428
|
loadingLabel: t(props.loadingLabel),
|
|
425
429
|
noDataLabel: t(props.noDataLabel),
|
|
426
430
|
rowsPerPageOptions: props.rowsPerPageOptions,
|
|
@@ -496,93 +500,101 @@ const localSelected = computed({
|
|
|
496
500
|
}
|
|
497
501
|
});
|
|
498
502
|
|
|
503
|
+
|
|
499
504
|
</script>
|
|
500
505
|
|
|
501
506
|
|
|
502
507
|
<template>
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
<q-banner inline-actions v-for="e in errors" rounded class="bg-negative text-white">{{ e }}</q-banner>
|
|
506
|
-
</div>
|
|
507
|
-
</template>
|
|
508
|
-
|
|
509
|
-
<q-table v-bind="attrs" :loading="loading" :rows="rows" ref="table" @request="onRequest" :columns="columns"
|
|
510
|
-
v-model:pagination="pagination" :filter="filter" v-model:selected="localSelected">
|
|
511
|
-
|
|
512
|
-
<template #header="props">
|
|
513
|
-
<q-tr :props="props">
|
|
514
|
-
<q-td v-if="selection != 'none'" auto-width>
|
|
515
|
-
<q-checkbox v-model="props.selected" />
|
|
516
|
-
</q-td>
|
|
517
|
-
<q-th v-if="hasRowExpand" auto-width></q-th>
|
|
518
|
-
<q-th v-if="hasActions" auto-width></q-th>
|
|
519
|
-
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
|
|
520
|
-
</q-tr>
|
|
521
|
-
</template>
|
|
508
|
+
<l-card flat bordered :maximizable="maximizable" :minimizable="minimizable" :title="title"
|
|
509
|
+
v-model:minimized="minimized" v-model:maximized="maximized">
|
|
522
510
|
|
|
523
|
-
<template #body="props">
|
|
524
|
-
<q-tr :props="props">
|
|
525
|
-
<q-td v-if="selection != 'none'" auto-width>
|
|
526
|
-
<q-checkbox v-model="props.selected" />
|
|
527
|
-
</q-td>
|
|
528
|
-
<q-td v-if="hasRowExpand" auto-width>
|
|
529
|
-
<q-btn :class="{ 'text-grey-8': !isDark }" flat round dense
|
|
530
|
-
:icon="props.expand ? 'sym_o_expand_more' : 'sym_o_expand_less'"
|
|
531
|
-
@click="props.expand = !props.expand"></q-btn>
|
|
532
|
-
</q-td>
|
|
533
511
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
:to="`/${modelName}/${props.row[primaryKey]}/edit`" />
|
|
542
|
-
|
|
543
|
-
<l-delete-btn v-if="actionDelete && props.row.canDelete"
|
|
544
|
-
@submit="onDelete(props.row[primaryKey])"></l-delete-btn>
|
|
545
|
-
|
|
546
|
-
<slot name="actions" v-bind="props"></slot>
|
|
547
|
-
</div>
|
|
548
|
-
|
|
549
|
-
</q-td>
|
|
512
|
+
<template v-if="errors.length > 0">
|
|
513
|
+
<div class="q-mb-sm">
|
|
514
|
+
<div class="q-gutter-sm">
|
|
515
|
+
<l-alert type="negative" v-for="e in errors">{{ e }}</l-alert>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
</template>
|
|
550
519
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
520
|
+
<q-table v-bind="attrs" :loading="loading" :rows="rows" ref="table" @request="onRequest" :columns="columns"
|
|
521
|
+
v-model:pagination="pagination" :filter="filter" v-model:selected="localSelected">
|
|
522
|
+
|
|
523
|
+
<template #header="props">
|
|
524
|
+
<q-tr :props="props">
|
|
525
|
+
<q-td v-if="selection != 'none'" auto-width>
|
|
526
|
+
<q-checkbox v-model="props.selected" />
|
|
527
|
+
</q-td>
|
|
528
|
+
<q-th v-if="hasRowExpand" auto-width></q-th>
|
|
529
|
+
<q-th v-if="hasActions" auto-width></q-th>
|
|
530
|
+
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
|
|
531
|
+
</q-tr>
|
|
532
|
+
</template>
|
|
533
|
+
|
|
534
|
+
<template #body="props">
|
|
535
|
+
<q-tr :props="props">
|
|
536
|
+
<q-td v-if="selection != 'none'" auto-width>
|
|
537
|
+
<q-checkbox v-model="props.selected" />
|
|
538
|
+
</q-td>
|
|
539
|
+
<q-td v-if="hasRowExpand" auto-width>
|
|
540
|
+
<q-btn :class="{ 'text-grey-8': !isDark }" flat round dense
|
|
541
|
+
:icon="props.expand ? 'sym_o_expand_more' : 'sym_o_expand_less'"
|
|
542
|
+
@click="props.expand = !props.expand"></q-btn>
|
|
543
|
+
</q-td>
|
|
544
|
+
|
|
545
|
+
<q-td v-if="hasActions" auto-width>
|
|
546
|
+
<div :class="{ 'text-grey-8': !isDark }" v-if="primaryKey">
|
|
547
|
+
|
|
548
|
+
<l-view-btn v-if="actionView && props.row.canView"
|
|
549
|
+
:to="`/${modelName}/${props.row[primaryKey]}/view`" />
|
|
550
|
+
|
|
551
|
+
<l-edit-btn v-if="activeEdit && props.row.canUpdate"
|
|
552
|
+
:to="`/${modelName}/${props.row[primaryKey]}/edit`" />
|
|
553
|
+
|
|
554
|
+
<l-delete-btn v-if="actionDelete && props.row.canDelete"
|
|
555
|
+
@submit="onDelete(props.row[primaryKey])"></l-delete-btn>
|
|
556
|
+
|
|
557
|
+
<slot name="actions" v-bind="props"></slot>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
</q-td>
|
|
561
|
+
|
|
562
|
+
<template v-for="col in props.cols">
|
|
563
|
+
<template v-if="ss.indexOf('body-cell-' + col.name) >= 0">
|
|
564
|
+
<slot :name="'body-cell-' + col.name" v-bind="props"></slot>
|
|
565
|
+
</template>
|
|
566
|
+
<template v-else>
|
|
567
|
+
<q-td :key="col.name" :props="props" :auto-width="col.autoWidth ?? false"
|
|
568
|
+
:style="getCellStyle(col, props.row)" :class="getCellClass(col, props.row)"><template
|
|
569
|
+
v-if="col.to" class="bg-primary">
|
|
570
|
+
<l-link :to="col.to(props.row)">{{ col.value }}</l-link>
|
|
571
|
+
</template><template v-else>{{ col.value }}</template></q-td>
|
|
572
|
+
</template>
|
|
554
573
|
</template>
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
574
|
+
</q-tr>
|
|
575
|
+
<q-tr v-show="props.expand" :props="props">
|
|
576
|
+
<q-td colspan="100%">
|
|
577
|
+
<slot name="row-expand" v-bind="props"></slot>
|
|
578
|
+
</q-td>
|
|
579
|
+
</q-tr>
|
|
580
|
+
</template>
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
<template #top-right="props" v-if="searchable">
|
|
584
|
+
<q-input v-if="searchable" outlined dense debounce="300" v-model="filter" :placeholder="$t('Search')"
|
|
585
|
+
:color="$light.color">
|
|
586
|
+
<template v-slot:append>
|
|
587
|
+
<q-icon name="search" />
|
|
561
588
|
</template>
|
|
562
|
-
</
|
|
563
|
-
</q-tr>
|
|
564
|
-
<q-tr v-show="props.expand" :props="props">
|
|
565
|
-
<q-td colspan="100%">
|
|
566
|
-
<slot name="row-expand" v-bind="props"></slot>
|
|
567
|
-
</q-td>
|
|
568
|
-
</q-tr>
|
|
569
|
-
</template>
|
|
589
|
+
</q-input>
|
|
570
590
|
|
|
571
591
|
|
|
572
|
-
<template #top-right="props" v-if="fullscreen || searchable">
|
|
573
|
-
<q-input v-if="searchable" outlined dense debounce="300" v-model="filter" :placeholder="$t('Search')"
|
|
574
|
-
:color="$light.color">
|
|
575
|
-
<template v-slot:append>
|
|
576
|
-
<q-icon name="search" />
|
|
577
|
-
</template>
|
|
578
|
-
</q-input>
|
|
579
592
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
</template>
|
|
593
|
+
|
|
594
|
+
</template>
|
|
583
595
|
|
|
584
596
|
|
|
585
|
-
|
|
597
|
+
<!-- template v-for="col in toColumns" v-slot:[col.slot_name]="props">
|
|
586
598
|
<q-td :props="props">
|
|
587
599
|
<l-link :to="col.to(props.row)">
|
|
588
600
|
{{ col.field(props.row) }}
|
|
@@ -591,44 +603,46 @@ const localSelected = computed({
|
|
|
591
603
|
</template -->
|
|
592
604
|
|
|
593
605
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
606
|
+
<template #top-row="props" v-if="hasSearch && isServerSide">
|
|
607
|
+
<q-tr>
|
|
608
|
+
<q-td v-if="selection != 'none'" auto-width />
|
|
609
|
+
<q-td v-if="hasRowExpand" auto-width />
|
|
610
|
+
<q-td v-if="hasActions" auto-width />
|
|
611
|
+
<q-td v-for="col in props.cols">
|
|
612
|
+
<template v-if="col.searchable">
|
|
601
613
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
614
|
+
<template v-if="col.searchType == 'number'">
|
|
615
|
+
<q-input style="min-width: 80px;" dense clearable filled square
|
|
616
|
+
v-model.number="filters[col.name]" @keydown.enter.prevent="onFilters"
|
|
617
|
+
@clear="onFilters" mask="##########" enterkeyhint="search"></q-input>
|
|
618
|
+
</template>
|
|
607
619
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
620
|
+
<template v-if="col.searchType == 'select'">
|
|
621
|
+
<q-select dense clearable filled square v-model="filters[col.name]"
|
|
622
|
+
@update:model-value="onFilters" options-dense :options="col.searchOptions"
|
|
623
|
+
emit-value map-options :multiple="col.searchMultiple" :color="$light.color" />
|
|
612
624
|
|
|
613
|
-
|
|
625
|
+
</template>
|
|
614
626
|
|
|
615
627
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
628
|
+
<template v-if="col.searchType == 'date'">
|
|
629
|
+
<l-date-picker dense clearable filled square :outlined="false" hide-bottom-space
|
|
630
|
+
v-model="filters[col.name]" @update:model-value="onFilters" range
|
|
631
|
+
@clear="onFilters" />
|
|
632
|
+
</template>
|
|
620
633
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
634
|
+
<template v-if="!col.searchType">
|
|
635
|
+
<q-input style="min-width: 80px;" dense clearable filled square
|
|
636
|
+
v-model="filters[col.name]" @keydown.enter.prevent="onFilters" @clear="onFilters"
|
|
637
|
+
enterkeyhint="search" :color="$light.color"></q-input>
|
|
625
638
|
|
|
626
|
-
|
|
639
|
+
</template>
|
|
627
640
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
641
|
+
</template>
|
|
642
|
+
</q-td>
|
|
643
|
+
</q-tr>
|
|
644
|
+
</template>
|
|
632
645
|
|
|
633
|
-
|
|
646
|
+
</q-table>
|
|
647
|
+
</l-card>
|
|
634
648
|
</template>
|
|
@@ -48,7 +48,7 @@ const localValue = computed({
|
|
|
48
48
|
v-model="localValue">
|
|
49
49
|
<q-tab v-for="tab in tabContents" :label="$t(tab.label)" :name="tab.name"></q-tab>
|
|
50
50
|
</q-tabs>
|
|
51
|
-
<q-tab-panels v-model="localValue">
|
|
51
|
+
<q-tab-panels v-model="localValue" keep-alive >
|
|
52
52
|
<q-tab-panel v-for="tab in tabContents" :name="tab.name">
|
|
53
53
|
<component :is="tab.content.default"></component>
|
|
54
54
|
</q-tab-panel>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { model } from "#imports"
|
|
3
|
+
import { ref } from 'vue'
|
|
3
4
|
const columns = model('EventLog').columns(['eventlog_id', 'class', 'id', 'action', 'created_time', 'username'])
|
|
4
5
|
</script>
|
|
5
6
|
<template>
|
|
6
7
|
<l-page>
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
<l-table @request="$event.loadObjects('EventLog')" :columns="columns"
|
|
10
|
+
sort-by="eventlog_id:desc" :actions="['view']">
|
|
9
11
|
|
|
10
12
|
</l-table>
|
|
11
13
|
</l-page>
|
|
@@ -12,11 +12,10 @@ let columns = [
|
|
|
12
12
|
name: "username",
|
|
13
13
|
field: "username",
|
|
14
14
|
align: "left",
|
|
15
|
-
|
|
16
15
|
},
|
|
17
16
|
{
|
|
18
17
|
name: "name",
|
|
19
|
-
label: "
|
|
18
|
+
label: "Name",
|
|
20
19
|
field: "name",
|
|
21
20
|
align: "left",
|
|
22
21
|
}, {
|
|
@@ -35,7 +34,10 @@ const router = useRouter();
|
|
|
35
34
|
const onCickView = async (id) => {
|
|
36
35
|
try {
|
|
37
36
|
if (await m("viewAs", { user_id: id })) {
|
|
38
|
-
|
|
37
|
+
|
|
38
|
+
//redirect to last path
|
|
39
|
+
window.location.reload();
|
|
40
|
+
|
|
39
41
|
}
|
|
40
42
|
} catch (e) {
|
|
41
43
|
Dialog.create({
|
|
@@ -59,10 +61,10 @@ const filtered = computed(() => {
|
|
|
59
61
|
})
|
|
60
62
|
</script>
|
|
61
63
|
<template>
|
|
62
|
-
<l-page>
|
|
63
|
-
<
|
|
64
|
+
<l-page class="q-gutter-md">
|
|
65
|
+
<q-banner >
|
|
64
66
|
Use this page to view the system as another user. This is useful for testing permissions.
|
|
65
|
-
</
|
|
67
|
+
</q-banner>
|
|
66
68
|
|
|
67
69
|
<q-table flat :columns="columns" :rows="filtered" :rows-per-page-options="[0]" dense>
|
|
68
70
|
<template v-slot:top-right>
|
|
@@ -3,11 +3,9 @@ import { ref } from 'vue'
|
|
|
3
3
|
import { model } from "#imports"
|
|
4
4
|
const onRequest = async (request) => {
|
|
5
5
|
request.loadObjects("User", { status: status.value });
|
|
6
|
-
//request.loadObjects("User");
|
|
7
6
|
};
|
|
8
7
|
const columns = model("User").columns(["username", "first_name", "label_name", "email", "phone", "join_date", "status","has2FA"]);
|
|
9
8
|
const status = ref("0");
|
|
10
|
-
const selected = ref([]);
|
|
11
9
|
</script>
|
|
12
10
|
|
|
13
11
|
<template>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostlink/nuxt-light",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "HostLink Nuxt Light Framework",
|
|
5
5
|
"repository": "@hostlink/nuxt-light",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@nuxt/module-builder": "^0.5.2",
|
|
41
41
|
"@quasar/extras": "^1.16.6",
|
|
42
42
|
"axios": "^1.5.0",
|
|
43
|
+
"diff2html": "^3.4.47",
|
|
43
44
|
"formkit-quasar": "^0.0.15",
|
|
44
45
|
"json-to-graphql-query": "^2.2.5",
|
|
45
46
|
"quasar": "^2.12.5",
|