adminforth 1.5.5-next.3 → 1.5.5-next.5
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/spa/src/App.vue +2 -2
- package/dist/spa/src/afcl/Checkbox.vue +24 -0
- package/dist/spa/src/afcl/Dropzone.vue +98 -0
- package/dist/spa/src/afcl/Select.vue +5 -0
- package/dist/spa/src/afcl/index.ts +3 -1
- package/dist/spa/src/components/ResourceListTable.vue +0 -1
- package/dist/spa/src/stores/core.ts +1 -0
- package/dist/spa/src/utils.ts +15 -2
- package/dist/spa/src/views/CreateView.vue +0 -1
- package/dist/spa/src/views/LoginView.vue +7 -11
- package/package.json +3 -3
package/dist/spa/src/App.vue
CHANGED
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
</div>
|
|
174
174
|
|
|
175
175
|
<div v-else-if="routerIsReady && loginRedirectCheckIsReady && publicConfigLoaded">
|
|
176
|
-
<RouterView/>
|
|
176
|
+
<RouterView />
|
|
177
177
|
</div>
|
|
178
178
|
|
|
179
179
|
<div v-else class="flex items-center justify-center h-screen">
|
|
@@ -268,7 +268,7 @@ const sidebarAside = ref(null);
|
|
|
268
268
|
const routerIsReady = ref(false);
|
|
269
269
|
const loginRedirectCheckIsReady = ref(false);
|
|
270
270
|
|
|
271
|
-
const loggedIn = computed(() =>
|
|
271
|
+
const loggedIn = computed(() => !!coreStore?.adminUser);
|
|
272
272
|
|
|
273
273
|
const theme = ref('light');
|
|
274
274
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center h-5">
|
|
3
|
+
<input :id="id"
|
|
4
|
+
ref="rememberInput"
|
|
5
|
+
type="checkbox"
|
|
6
|
+
:checked="props.modelValue"
|
|
7
|
+
@change="$emit('update:modelValue', $event.target.checked)"
|
|
8
|
+
class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-lightPrimary focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 checked:bg-lightPrimary checked:dark:bg-darkPrimary" />
|
|
9
|
+
</div>
|
|
10
|
+
<label :for="id" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">
|
|
11
|
+
<slot></slot>
|
|
12
|
+
</label>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
modelValue: Boolean,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
defineEmits(['update:modelValue']);
|
|
22
|
+
|
|
23
|
+
const id = `afcb-${Math.random().toString(36).substring(7)}`
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex items-center justify-center w-full"
|
|
3
|
+
@dragover.prevent="dragging = true"
|
|
4
|
+
@dragleave.prevent="dragging = false"
|
|
5
|
+
@drop.prevent="dragging = false; doEmit($event.dataTransfer.files)"
|
|
6
|
+
>
|
|
7
|
+
<label :id="id" class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer dark:hover:bg-gray-800
|
|
8
|
+
hover:bg-gray-100 dark:hover:border-gray-500 dark:hover:bg-gray-600"
|
|
9
|
+
:class="{
|
|
10
|
+
'border-blue-600 dark:border-blue-400': dragging,
|
|
11
|
+
'border-gray-300 dark:border-gray-600': !dragging,
|
|
12
|
+
'bg-blue-50 dark:bg-blue-800': dragging,
|
|
13
|
+
'bg-gray-50 dark:bg-gray-800': !dragging,
|
|
14
|
+
}"
|
|
15
|
+
>
|
|
16
|
+
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<svg v-if="!selectedFiles.length" class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
|
|
20
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
|
|
21
|
+
</svg>
|
|
22
|
+
<div v-else class="flex items-center justify-center w-full mt-1 mb-4">
|
|
23
|
+
<template v-for="file in selectedFiles">
|
|
24
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
|
|
25
|
+
<IconFileSolid class="w-5 h-5" />
|
|
26
|
+
{{ file.name }} ({{ humanifySize(file.size) }})
|
|
27
|
+
</p>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<p v-if="!selectedFiles.length" class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span class="font-semibold">Click to upload</span> or drag and drop</p>
|
|
33
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
34
|
+
{{ props.extensions.join(', ').toUpperCase().replace(/\./g, '') }}
|
|
35
|
+
<template v-if="props.maxSizeBytes">
|
|
36
|
+
(Max size: {{ humanifySize(props.maxSizeBytes) }})
|
|
37
|
+
</template>
|
|
38
|
+
</p>
|
|
39
|
+
</div>
|
|
40
|
+
<input :id="id" type="file" class="hidden"
|
|
41
|
+
:accept="props.extensions.join(', ')"
|
|
42
|
+
@change="doEmit($event.target.files)"
|
|
43
|
+
:multiple="props.multiple || false"
|
|
44
|
+
/>
|
|
45
|
+
</label>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import { humanifySize } from '@/utils';
|
|
51
|
+
import { ref } from 'vue';
|
|
52
|
+
import { IconFileSolid } from '@iconify-prerendered/vue-flowbite';
|
|
53
|
+
|
|
54
|
+
const props = defineProps<{
|
|
55
|
+
extensions: string[],
|
|
56
|
+
maxSizeBytes: number,
|
|
57
|
+
multiple: boolean,
|
|
58
|
+
modelValue: FileList,
|
|
59
|
+
}>();
|
|
60
|
+
|
|
61
|
+
const emit = defineEmits(['update:modelValue']);
|
|
62
|
+
|
|
63
|
+
const id = `afcl-dropzone-${Math.random().toString(36).substring(7)}`;
|
|
64
|
+
|
|
65
|
+
const selectedFiles: Ref<{
|
|
66
|
+
name: string,
|
|
67
|
+
size: number,
|
|
68
|
+
mime: string,
|
|
69
|
+
}[]> = ref([]);
|
|
70
|
+
|
|
71
|
+
watch(() => props.modelValue, (files) => {
|
|
72
|
+
selectedFiles.value = Array.from(files).map(file => ({
|
|
73
|
+
name: file.name,
|
|
74
|
+
size: file.size,
|
|
75
|
+
mime: file.type,
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function doEmit(filesIn: FileList) {
|
|
80
|
+
|
|
81
|
+
const multiple = props.multiple || false;
|
|
82
|
+
const files = Array.from(filesIn);
|
|
83
|
+
if (!files.length) return;
|
|
84
|
+
if (!multiple) {
|
|
85
|
+
files.splice(1);
|
|
86
|
+
}
|
|
87
|
+
selectedFiles.value = files.map(file => ({
|
|
88
|
+
name: file.name,
|
|
89
|
+
size: file.size,
|
|
90
|
+
mime: file.type,
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
emit('update:modelValue', Array.from(files));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const dragging = ref(false);
|
|
97
|
+
|
|
98
|
+
</script>
|
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
|
|
44
44
|
No results found
|
|
45
45
|
</div>
|
|
46
|
+
|
|
47
|
+
<div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-gray-400">
|
|
48
|
+
<slot name="extra-item"></slot>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
46
51
|
</div>
|
|
47
52
|
|
|
48
53
|
<div v-if="multiple && selectedItems.length"
|
|
@@ -6,4 +6,6 @@ export { default as Button } from './Button.vue';
|
|
|
6
6
|
export { default as Input } from './Input.vue';
|
|
7
7
|
export { default as Tooltip } from './Tooltip.vue';
|
|
8
8
|
export { default as LinkButton } from './LinkButton.vue';
|
|
9
|
-
export { default as VerticalTabs } from './VerticalTabs.vue';
|
|
9
|
+
export { default as VerticalTabs } from './VerticalTabs.vue';
|
|
10
|
+
export { default as Checkbox } from './Checkbox.vue';
|
|
11
|
+
export { default as Dropzone } from './Dropzone.vue';
|
package/dist/spa/src/utils.ts
CHANGED
|
@@ -24,7 +24,7 @@ export async function callApi({path, method, body=undefined}: {
|
|
|
24
24
|
const r = await fetch(fullPath, options);
|
|
25
25
|
if (r.status == 401 ) {
|
|
26
26
|
useUserStore().unauthorize();
|
|
27
|
-
router.push({ name: 'login' });
|
|
27
|
+
await router.push({ name: 'login' });
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
return await r.json();
|
|
@@ -161,4 +161,17 @@ export function setQuery(query: any) {
|
|
|
161
161
|
|
|
162
162
|
export function verySimpleHash(str: string): string {
|
|
163
163
|
return `${str.split('').reduce((a, b)=>{a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)}`;
|
|
164
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function humanifySize(size) {
|
|
167
|
+
if (!size) {
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
171
|
+
let i = 0
|
|
172
|
+
while (size >= 1024 && i < units.length - 1) {
|
|
173
|
+
size /= 1024
|
|
174
|
+
i++
|
|
175
|
+
}
|
|
176
|
+
return `${size.toFixed(1)} ${units[i]}`
|
|
177
|
+
}
|
|
@@ -65,14 +65,10 @@
|
|
|
65
65
|
class="flex items-start mb-5"
|
|
66
66
|
:title="`Stay logged in for ${coreStore.config.rememberMeDays} days`"
|
|
67
67
|
>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
value=""
|
|
73
|
-
class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-lightPrimary focus:ring-opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800 checked:bg-lightPrimary checked:dark:bg-darkPrimary" />
|
|
74
|
-
</div>
|
|
75
|
-
<label for="remember" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">Remember me</label>
|
|
68
|
+
<Checkbox v-model="rememberMeValue" class="mr-2">
|
|
69
|
+
Remember me
|
|
70
|
+
</Checkbox>
|
|
71
|
+
|
|
76
72
|
</div>
|
|
77
73
|
|
|
78
74
|
<component
|
|
@@ -123,11 +119,11 @@ import { useUserStore } from '@/stores/user';
|
|
|
123
119
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
124
120
|
import { callAdminForthApi, loadFile } from '@/utils';
|
|
125
121
|
import { useRouter } from 'vue-router';
|
|
126
|
-
import { Button } from '@/afcl';
|
|
122
|
+
import { Button, Checkbox } from '@/afcl';
|
|
127
123
|
|
|
128
124
|
const passwordInput = ref(null);
|
|
129
125
|
const usernameInput = ref(null);
|
|
130
|
-
const
|
|
126
|
+
const rememberMeValue= ref(false);
|
|
131
127
|
|
|
132
128
|
const router = useRouter();
|
|
133
129
|
const inProgress = ref(false);
|
|
@@ -172,7 +168,7 @@ async function login() {
|
|
|
172
168
|
body: {
|
|
173
169
|
username,
|
|
174
170
|
password,
|
|
175
|
-
rememberMe:
|
|
171
|
+
rememberMe: rememberMeValue.value,
|
|
176
172
|
}
|
|
177
173
|
});
|
|
178
174
|
if (resp.error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.5.5-next.
|
|
3
|
+
"version": "1.5.5-next.5",
|
|
4
4
|
"description": "OpenSource Vue3 powered forth-generation admin panel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"rollout-next": "npm run build && npm version prerelease --preid=next && npm publish --tag next && npm run put-git-tag",
|
|
23
23
|
"rollout-doc": "cd documentation && npm run build && npm run deploy",
|
|
24
24
|
"docs": "typedoc",
|
|
25
|
-
"--comment_postinstall": "postinstall executed after package installed in other project package",
|
|
26
|
-
"postinstall": "cd ./dist/spa/ && npm ci && echo 'installed spa dependencies'",
|
|
25
|
+
"--comment_postinstall": "postinstall executed after package installed in other project package and when we do npm ci in the package",
|
|
26
|
+
"postinstall": "if test -d ./dist/spa/; then cd ./dist/spa/ && npm ci && echo 'installed spa dependencies'; fi",
|
|
27
27
|
"ci-plugins": "for d in plugins/*; do cd $d && npm ci && cd ../..; done"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|