@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.8.0"
4
+ "version": "1.8.2"
5
5
  }
@@ -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 delta only" v-model="showOnlyDelta" :color="$light.color"></q-checkbox>
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
- <div class="text-h4 q-mb-md">
126
- Revision
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>Delta</th>
172
+ <th>Changed</th>
139
173
  </tr>
140
174
  </thead>
141
175
  <tbody>
142
- <tr v-for="(value, key) in getFilteredContent" :key="key">
143
-
144
- <td><q-checkbox v-model="restore_fields" :val="key" :color="$light.color"></q-checkbox>
145
- </td>
146
- <td :class="{ 'bg-yellow-3': selectedRevision.delta[key] != undefined }">
147
- {{ key }}</td>
148
- <td>
149
- <pre>{{ value }}</pre>
150
- </td>
151
- <td>
152
- <pre>{{ selectedRevision.delta[key] }}</pre>
153
- </td>
154
- </tr>
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, inject } from 'vue';
6
+ import { ref, computed, reactive, provide, watch, toRaw } from 'vue';
7
7
  import { useRuntimeConfig } from 'nuxt/app';
8
8
 
9
9
  Loading.show()
10
- //download system value
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: ["devMode", "time"],
21
- app: ["menus", "viewAsMode", "languages",
22
- "copyrightYear",
23
- "copyrightName",
24
- "hasFavorite",
25
- { i18nMessages: ["name", "value"] }],
26
- my: ['username', 'first_name', 'last_name', 'roles', "styles", "language", "permissions", "default_page", { myFavorites: ["my_favorite_id", "label", "path", "icon"] }],
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
- if (system.diskFreeSpacePercent < 0.05) {
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 (system.diskFreeSpacePercent < 0.1) {
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" :color="storageColor">
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
- <q-card style="width:250px">
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"> <!-- Error message -->
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
- Build {{
453
- light.getVersion() }}
441
+ }}. Build
442
+ {{ light.getVersion() }}
454
443
  </q-item-section>
455
444
  </q-item>
456
445
  </q-footer>
@@ -48,7 +48,13 @@ provide('color', color)
48
48
  </q-layout>
49
49
 
50
50
  <l-app-main v-else>
51
+
52
+ <template #header>
53
+ <slot name="header"></slot>
54
+ </template>
55
+
51
56
  <slot></slot>
57
+
58
+
52
59
  </l-app-main>
53
60
  </template>
54
-
@@ -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 cl = computed(() => {
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.fullscreenable) return true;
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': fullScreen, 'no-margin': 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="cl" v-if="showBar">
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="icon" @click="minimize = !minimize" v-if="!fullScreen && minimizable" />
138
- <q-btn dense flat @click="fullScreen = !fullScreen" :icon="fullScreenIcon" v-if="fullscreenable" />
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="showBody">
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
- <template v-if="errors.length > 0">
504
- <div class="q-gutter-sm">
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
- <q-td v-if="hasActions" auto-width>
535
- <div :class="{ 'text-grey-8': !isDark }" v-if="primaryKey">
536
-
537
- <l-view-btn v-if="actionView && props.row.canView"
538
- :to="`/${modelName}/${props.row[primaryKey]}/view`" />
539
-
540
- <l-edit-btn v-if="activeEdit && props.row.canUpdate"
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
- <template v-for="col in props.cols">
552
- <template v-if="ss.indexOf('body-cell-' + col.name) >= 0">
553
- <slot :name="'body-cell-' + col.name" v-bind="props"></slot>
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
- <template v-else>
556
- <q-td :key="col.name" :props="props" :auto-width="col.autoWidth ?? false"
557
- :style="getCellStyle(col, props.row)" :class="getCellClass(col, props.row)"><template
558
- v-if="col.to" class="bg-primary">
559
- <l-link :to="col.to(props.row)">{{ col.value }}</l-link>
560
- </template><template v-else>{{ col.value }}</template></q-td>
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
- </template>
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
- <q-btn v-if="fullscreen" flat round dense :icon="props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'"
581
- @click="props.toggleFullscreen" class="q-ml-md" />
582
- </template>
593
+
594
+ </template>
583
595
 
584
596
 
585
- <!-- template v-for="col in toColumns" v-slot:[col.slot_name]="props">
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
- <template #top-row="props" v-if="hasSearch && isServerSide">
595
- <q-tr>
596
- <q-td v-if="selection != 'none'" auto-width />
597
- <q-td v-if="hasRowExpand" auto-width />
598
- <q-td v-if="hasActions" auto-width />
599
- <q-td v-for="col in props.cols">
600
- <template v-if="col.searchable">
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
- <template v-if="col.searchType == 'number'">
603
- <q-input style="min-width: 80px;" dense clearable filled square
604
- v-model.number="filters[col.name]" @keydown.enter.prevent="onFilters" @clear="onFilters"
605
- mask="##########" enterkeyhint="search"></q-input>
606
- </template>
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
- <template v-if="col.searchType == 'select'">
609
- <q-select dense clearable filled square v-model="filters[col.name]"
610
- @update:model-value="onFilters" options-dense :options="col.searchOptions" emit-value
611
- map-options :multiple="col.searchMultiple" :color="$light.color" />
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
- </template>
625
+ </template>
614
626
 
615
627
 
616
- <template v-if="col.searchType == 'date'">
617
- <l-date-picker dense clearable filled square :outlined="false" hide-bottom-space
618
- v-model="filters[col.name]" @update:model-value="onFilters" range @clear="onFilters" />
619
- </template>
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
- <template v-if="!col.searchType">
622
- <q-input style="min-width: 80px;" dense clearable filled square v-model="filters[col.name]"
623
- @keydown.enter.prevent="onFilters" @clear="onFilters" enterkeyhint="search"
624
- :color="$light.color"></q-input>
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
- </template>
639
+ </template>
627
640
 
628
- </template>
629
- </q-td>
630
- </q-tr>
631
- </template>
641
+ </template>
642
+ </q-td>
643
+ </q-tr>
644
+ </template>
632
645
 
633
- </q-table>
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
- <l-table fullscreen @request="$event.loadObjects('EventLog')" :columns="columns" sort-by="eventlog_id:desc"
8
- :actions="['view']">
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
- router.back();
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
- <p>
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
- </p>
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.0",
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",