Compare commits
27 Commits
2806e8d889
...
master
Author | SHA1 | Date | |
---|---|---|---|
6160615cdf | |||
989b0c44a4 | |||
11ed18a08b | |||
324a14e055 | |||
f52da5044e | |||
73d549456e | |||
1a23e59903 | |||
8fcdbcc7f4 | |||
5488f8735f | |||
00d878706b | |||
1e5d1a7c44 | |||
acf2e639c5 | |||
d18168910e | |||
f50a02666e | |||
59d4d7d099 | |||
1ceaa772bb | |||
d5fcb864a9 | |||
d9f96c980a | |||
fcb30e52fd | |||
c881fb4a97 | |||
381970563a | |||
53d5bdc08b | |||
b496d51ece | |||
49e9edae14 | |||
d3e880d1d4 | |||
971cdc87d5 | |||
bd812756b7 |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
public
|
13
.eslintrc
Normal file
13
.eslintrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": [
|
||||
"@antfu"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"camelcase": "off"
|
||||
},
|
||||
"plugins": [
|
||||
"snakecasejs"
|
||||
]
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2021 Jesús Pérez Lorenzo based in Anthony Fu developments
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,4 +1,4 @@
|
||||
# Status iterface for ZTerton services
|
||||
# Status interface for ZTerton services
|
||||
|
||||
<img style="margin-top: 1em;width: 500px;border: 0" alt="Fork me on GitHub" src="assets/images/status.svg?sanitize=true">
|
||||
|
||||
|
1
assets/defs.json
Normal file
1
assets/defs.json
Normal file
@ -0,0 +1 @@
|
||||
{"profile":{"image":"jesus.jpg"},"checkin":0,"sidebar":{"header":{"title":"Services Status"},"menu_items":[{"title":"app.title","icon_on":"carbon-campsite","name_to":"app.title","show_to":false,"click":"tophome","type":"app_link","pfx":""},{"title":"nav.Tasks","ctx":"home","icon_on":"carbon-services","name_to":"navm.Tasks","show_to":true,"click":"tsksrvcs","type":"app_link","pfx":""},{"title":"nav.Apps","ctx":"home","icon_on":"carbon-application","name_to":"navm.Apps","show_to":true,"click":"appsrvcs","type":"app_link","pfx":""},{"title":"nav.Informations","ctx":"home","icon_on":"carbon-operation","name_to":"navm.Infos","show_to":true,"click":"srvcstatus","type":"app_link","pfx":""}],"footer":{"itm":{"title":"menu.logout","name_to":"Logout","icon_on":"carbon-logout","type":"router_link"}}},"header":{"title":"Status","name_to":"Home","logo":"","navbar":{"menuright":{"notify":true,"search":true,"title":"menu.user","items":[{"title":"menu.your-profile","type":"app_link","click":"profile"},{"title":"menu.settings","type":"app_link","click":"user_settings"},{"title":"menu.sign-out","type":"router_link","name_to":"Logout"}]}}},"footer":{"left_items":[{"title":"LibreCloud © 2021","icon_on":"","href":"https://librecloud.online","type":"app_link"}],"right_items":[{"title":"LibreCloud","img":"app_w","href":"https://librecloud.online","type":"app_link"}],"central_items":[{"title":"button.home","mode":"icon","icon_on":"carbon-campsite","click":"tophome","type":"app_link","pfx":""},{"title":"button.toggle_dark","mode":"icon","icon_on":"carbon-moon","icon-off":"carbon-sun","vif":"isDark","click":"toggleDark","type":"app_link"},{"title":"button.toggle_langs","mode":"icon","icon_on":"carbon-language","click":"toggleLocales","type":"app_link"},{"title":"GitHub","mode":"icon","icon_on":"carbon-logo-github","href":"https://rlung.librecloud.online","type":"a_blank"},{"title":"button.about","mode":"icon","icon_on":"carbon-dicom-overlay","to":"/about","type":"router_link"},{"title":"button.login","mode":"icon","icon_on":"carbon-login","vif":"notValidUser","to":"/login","type":"router_link"}]}}
|
111
assets/defs.yaml
Normal file
111
assets/defs.yaml
Normal file
@ -0,0 +1,111 @@
|
||||
profile:
|
||||
image: jesus.jpg
|
||||
# checkin time in millisecond or 0 for not checkin
|
||||
checkin: 0
|
||||
# checkin: 60000
|
||||
sidebar:
|
||||
header:
|
||||
title: Services Status
|
||||
menu_items:
|
||||
- title: app.title
|
||||
icon_on: carbon-campsite
|
||||
name_to: app.title
|
||||
show_to: false
|
||||
click: tophome
|
||||
type: app_link
|
||||
pfx: ''
|
||||
- title: nav.Tasks
|
||||
ctx: home
|
||||
icon_on: carbon-services
|
||||
name_to: navm.Tasks
|
||||
show_to: true
|
||||
click: tsksrvcs
|
||||
type: app_link
|
||||
pfx: ''
|
||||
- title: nav.Apps
|
||||
ctx: home
|
||||
icon_on: carbon-application
|
||||
name_to: navm.Apps
|
||||
show_to: true
|
||||
click: appsrvcs
|
||||
type: app_link
|
||||
pfx: ''
|
||||
- title: nav.Informations
|
||||
ctx: home
|
||||
icon_on: carbon-operation
|
||||
name_to: navm.Infos
|
||||
show_to: true
|
||||
click: srvcstatus
|
||||
type: app_link
|
||||
pfx: ''
|
||||
footer:
|
||||
itm:
|
||||
title: menu.logout
|
||||
name_to: Logout
|
||||
icon_on: carbon-logout
|
||||
type: router_link
|
||||
header:
|
||||
title: Status
|
||||
name_to: Home
|
||||
logo: ""
|
||||
navbar:
|
||||
menuright:
|
||||
notify: true
|
||||
search: true
|
||||
title: menu.user
|
||||
items:
|
||||
- title: menu.your-profile
|
||||
type: app_link
|
||||
click: profile
|
||||
- title: menu.settings
|
||||
type: app_link
|
||||
click: user_settings
|
||||
- title: menu.sign-out
|
||||
type: router_link
|
||||
name_to: Logout
|
||||
footer:
|
||||
left_items:
|
||||
- title: 'LibreCloud © 2021'
|
||||
icon_on: ''
|
||||
href: https://librecloud.online
|
||||
type: app_link
|
||||
right_items:
|
||||
- title: 'LibreCloud'
|
||||
img: 'app_w'
|
||||
href: https://librecloud.online
|
||||
type: app_link
|
||||
central_items:
|
||||
- title: button.home
|
||||
mode: icon
|
||||
icon_on: carbon-campsite
|
||||
click: tophome
|
||||
type: app_link
|
||||
pfx: ''
|
||||
- title: button.toggle_dark
|
||||
mode: icon
|
||||
icon_on: carbon-moon
|
||||
icon-off: carbon-sun
|
||||
vif: isDark
|
||||
click: toggleDark
|
||||
type: app_link
|
||||
- title: button.toggle_langs
|
||||
mode: icon
|
||||
icon_on: carbon-language
|
||||
click: toggleLocales
|
||||
type: app_link
|
||||
- title: GitHub
|
||||
mode: icon
|
||||
icon_on: carbon-logo-github
|
||||
href: https://rlung.librecloud.online
|
||||
type: a_blank
|
||||
- title: button.about
|
||||
mode: icon
|
||||
icon_on: carbon-dicom-overlay
|
||||
to: /about
|
||||
type: router_link
|
||||
- title: button.login
|
||||
mode: icon
|
||||
icon_on: carbon-login
|
||||
vif: notValidUser
|
||||
to: /login
|
||||
type: router_link
|
12
assets/images/cloud-services.svg
Normal file
12
assets/images/cloud-services.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg id="icon" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path d="M25.8218,10.124a9.9991,9.9991,0,0,0-19.6435,0A7.4964,7.4964,0,0,0,7.5,25H8V23H7.5a5.4961,5.4961,0,0,1-.3769-10.9795l.8364-.0571.09-.8335a7.9979,7.9979,0,0,1,15.9013,0l.09.8335.8364.0571A5.4961,5.4961,0,0,1,24.5,23H24v2h.5a7.4964,7.4964,0,0,0,1.3218-14.876Z" transform="translate(0 0)"/>
|
||||
<path d="M23,22V20H20.8989a4.9678,4.9678,0,0,0-.7319-1.7529l1.49-1.49-1.414-1.414-1.49,1.49A4.9678,4.9678,0,0,0,17,16.1011V14H15v2.1011a4.9678,4.9678,0,0,0-1.7529.7319l-1.49-1.49-1.414,1.414,1.49,1.49A4.9678,4.9678,0,0,0,11.1011,20H9v2h2.1011a4.9678,4.9678,0,0,0,.7319,1.7529l-1.49,1.49,1.414,1.414,1.49-1.49A4.9678,4.9678,0,0,0,15,25.8989V28h2V25.8989a4.9678,4.9678,0,0,0,1.7529-.7319l1.49,1.49,1.414-1.414-1.49-1.49A4.9678,4.9678,0,0,0,20.8989,22Zm-7,2a3,3,0,1,1,3-3A3.0033,3.0033,0,0,1,16,24Z" transform="translate(0 0)"/>
|
||||
<rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
assets/images/kloudmandala.svg
Normal file
1
assets/images/kloudmandala.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.0 KiB |
1
assets/images/logo.svg
Normal file
1
assets/images/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.7 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
1
assets/make_json.sh
Executable file
1
assets/make_json.sh
Executable file
@ -0,0 +1 @@
|
||||
cat defs.yaml | yq e -o=json | jq -cr > defs.json
|
172
auto-imports.d.ts
vendored
Normal file
172
auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useHead: typeof import('@vueuse/head')['useHead']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
export {}
|
34
components.d.ts
vendored
Normal file
34
components.d.ts
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AccordionView: typeof import('./src/components/AccordionView.vue')['default']
|
||||
AppImg: typeof import('./src/components/icons/AppImg.vue')['default']
|
||||
AppLogoText: typeof import('./src/components/icons/AppLogoText.vue')['default']
|
||||
AppLogoV: typeof import('./src/components/icons/AppLogoV.vue')['default']
|
||||
CarbonCampsite: typeof import('~icons/carbon/campsite')['default']
|
||||
CarbonCheck: typeof import('~icons/carbon/check')['default']
|
||||
CarbonCheckmark: typeof import('~icons/carbon/checkmark')['default']
|
||||
CarbonLanguage: typeof import('~icons/carbon/language')['default']
|
||||
CarbonLogin: typeof import('~icons/carbon/login')['default']
|
||||
CarbonLogoGithub: typeof import('~icons/carbon/logo-github')['default']
|
||||
CarbonLogout: typeof import('~icons/carbon/logout')['default']
|
||||
CarbonMoon: typeof import('~icons/carbon/moon')['default']
|
||||
CarbonPedestrian: typeof import('~icons/carbon/pedestrian')['default']
|
||||
CarbonScript: typeof import('~icons/carbon/script')['default']
|
||||
CarbonSearch: typeof import('~icons/carbon/search')['default']
|
||||
CarbonSun: typeof import('~icons/carbon/sun')['default']
|
||||
CarbonWarning: typeof import('~icons/carbon/warning')['default']
|
||||
CloudGroups: typeof import('./src/components/CloudGroups.vue')['default']
|
||||
CloudServices: typeof import('./src/components/CloudServices.vue')['default']
|
||||
CloudSrvc: typeof import('./src/components/CloudSrvc.vue')['default']
|
||||
Footer: typeof import('./src/components/Footer.vue')['default']
|
||||
MenuLocales: typeof import('./src/components/menus/MenuLocales.vue')['default']
|
||||
NavMenu: typeof import('./src/components/NavMenu.vue')['default']
|
||||
ProfileView: typeof import('./src/components/ProfileView.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export { }
|
26
index.html
Normal file
26
index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<title>Vitesse Lite</title>
|
||||
<meta name="description" content="Opinionated Vite Starter Template">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
(function() {
|
||||
// window.ROOT_LOCATION='https://10.11.5.1'
|
||||
// window.ROOT_LOCATION='https://status.librecloud.online'
|
||||
// window.ROOT_LOCATION='https://localhost:8302'
|
||||
window.ROOT_LOCATION='https://localhost:8302'
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const setting = localStorage.getItem('color-schema') || 'auto'
|
||||
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
||||
document.documentElement.classList.toggle('dark', true)
|
||||
})()
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
7
locales/README.md
Normal file
7
locales/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
## i18n
|
||||
|
||||
This directory is to serve your locale translation files. YAML under this folder would be loaded automatically and register with their filenames as locale code.
|
||||
|
||||
Check out [`vue-i18n`](https://github.com/intlify/vue-i18n-next) for more details.
|
||||
|
||||
If you are using VS Code, [`i18n Ally`](https://github.com/lokalise/i18n-ally) is recommended to make the i18n experience better.
|
151
locales/en.yml
Normal file
151
locales/en.yml
Normal file
@ -0,0 +1,151 @@
|
||||
All: All
|
||||
auth:
|
||||
invalidcredentials: Invalid credentials
|
||||
loginForm: ''
|
||||
newaccount: new account
|
||||
password: password
|
||||
register: Register
|
||||
signin: Sign in
|
||||
subHeading: Login info
|
||||
subTitle: ''
|
||||
wellcome: Wellcome !
|
||||
bc:
|
||||
home: librecloud
|
||||
topographic_anatomy: klouds
|
||||
bm:
|
||||
topographic-anatomy: Topographic Anatomy
|
||||
button:
|
||||
about: About
|
||||
back: Back
|
||||
cancel: Cancel
|
||||
close-panel: Close panel
|
||||
delete: Delete
|
||||
go: Go
|
||||
home: Home
|
||||
new: Nuevo
|
||||
reset: Reset
|
||||
save: Save
|
||||
toggle_dark: Toggle dark mode
|
||||
toggle_langs: Change language
|
||||
login: Login
|
||||
logout: Logout
|
||||
connection:
|
||||
error: Error conexión
|
||||
dashboard: Dashboard
|
||||
en: English
|
||||
entry:
|
||||
Apps: Apps
|
||||
Code: Code
|
||||
DB: DB
|
||||
Data: Data
|
||||
DataCode: Data / Code
|
||||
Document: Document
|
||||
Documents: Documents
|
||||
Macro: Macro
|
||||
Profiles: Profiles/roles
|
||||
Schema: Schema
|
||||
Services: Services
|
||||
Shortcut: Shortcut
|
||||
Template: Template
|
||||
Trans: Translations
|
||||
es: Español
|
||||
form:
|
||||
delete_done: Delete done
|
||||
delete_error: Delete Error
|
||||
save_done: Data saved
|
||||
save_error: Data save Error
|
||||
intro:
|
||||
desc: Opinionated Vite Starter Template
|
||||
dynamic-route: Demo of dynamic route
|
||||
hi: Hi, {name}!
|
||||
whats-your-name: What's your name?
|
||||
login:
|
||||
end_session: End session
|
||||
form: Credentials
|
||||
invalidcredentials: Incorrect data
|
||||
newuser: New user
|
||||
nodata: No data
|
||||
password: Password
|
||||
register: Register
|
||||
reset: Reset
|
||||
see_you_soon: See You soon !
|
||||
signin: Signin
|
||||
subHeading: Access
|
||||
subtitle: LibreCloud online
|
||||
user_email: User / email
|
||||
wellcome: Wellcome
|
||||
menu:
|
||||
copy: Copy
|
||||
data-grid: Data Grid
|
||||
edit: Edit
|
||||
edit-items: Edit items
|
||||
home: Home
|
||||
login: Init session
|
||||
logout: Close session
|
||||
new: New
|
||||
open-recent: Open recent
|
||||
paste: Paste
|
||||
save: Save
|
||||
settings: Configuration
|
||||
sign-in: Signin
|
||||
sign-out: Signout
|
||||
your-profile: Your profile
|
||||
message:
|
||||
loadding: Loading
|
||||
not-found: Not found
|
||||
notes: Notes
|
||||
notifications:
|
||||
content: content
|
||||
notifications: Notifications
|
||||
search:
|
||||
search: Search
|
||||
settings:
|
||||
above-noedit: ALL above options in "off" configure mode for
|
||||
aside: Aside
|
||||
col: Col
|
||||
columns-settings: Columns
|
||||
filter: Filter
|
||||
grid: Grid
|
||||
grid-options: Grid options
|
||||
hidden: Hide
|
||||
id: Id
|
||||
list: List
|
||||
main: Main
|
||||
no-edition: no edition
|
||||
pagination: Pagination
|
||||
panels: Panels
|
||||
pos: Pos
|
||||
position: position
|
||||
select: Select
|
||||
select-config: Select configuration
|
||||
select-grid-type: Select grid type
|
||||
setting-id: Identificator
|
||||
settings: Configuration
|
||||
slide: Modal
|
||||
sort: Sort
|
||||
table: Table
|
||||
undefined: Undefined
|
||||
srvc:
|
||||
mailserver::smtp::imap: mail imap
|
||||
mailserver::smtp::pop: mail pop
|
||||
mailserver::smtp::tls: mail tls
|
||||
mailserver::smtp: mail smtp
|
||||
pending: Pendiente
|
||||
nav:
|
||||
Services Status: Services Status Servicios
|
||||
Tasks: Services
|
||||
Search: Search
|
||||
Apps: Applications
|
||||
Informations: Informations
|
||||
navm:
|
||||
Infos: Infos
|
||||
Status: Status
|
||||
Tasks: Serv.
|
||||
Apps: Aplic.
|
||||
app:
|
||||
title: Status
|
||||
profile:
|
||||
Close: Close
|
||||
Settings: Settings
|
||||
backgroud_color: Background color
|
||||
Logout: Logout
|
155
locales/es.yml
Normal file
155
locales/es.yml
Normal file
@ -0,0 +1,155 @@
|
||||
All: All
|
||||
auth:
|
||||
invalidcredentials: Invalid credentials
|
||||
loginForm: ''
|
||||
newaccount: new account
|
||||
password: password
|
||||
register: Register
|
||||
signin: Sign in
|
||||
subHeading: Login info
|
||||
subTitle: ''
|
||||
wellcome: Wellcome !
|
||||
bc:
|
||||
home: librecloud
|
||||
topographic_anatomy: klouds
|
||||
bm:
|
||||
topographic-anatomy: Anatomía topográfica
|
||||
button:
|
||||
about: Acerca de
|
||||
back: Atrás
|
||||
cancel: Cancelar
|
||||
close-panel: Cerarr panel
|
||||
delete: Borrar
|
||||
go: Ir
|
||||
home: Inicio
|
||||
new: Nuevo
|
||||
reset: Reset
|
||||
save: Guardar
|
||||
toggle_dark: Alternar modo oscuro
|
||||
toggle_langs: Cambiar idiomas
|
||||
login: Login
|
||||
logout: Desconectar
|
||||
connection:
|
||||
error: Error conexión
|
||||
dashboard: Panel de control
|
||||
en: English
|
||||
entry:
|
||||
Apps: Apps
|
||||
Code: Code
|
||||
DB: DB
|
||||
Data: Data
|
||||
DataCode: Data / Code
|
||||
Document: Document
|
||||
Documents: Documents
|
||||
Macro: Macro
|
||||
Profiles: Profiles/roles
|
||||
Schema: Schema
|
||||
Services: Services
|
||||
Shortcut: Shortcut
|
||||
Template: Template
|
||||
Trans: Translations
|
||||
es: Español
|
||||
form:
|
||||
delete_done: Borrado realizado
|
||||
delete_error: Error al borrar datos
|
||||
save_done: Datos guardados
|
||||
save_error: Error al guardar datos
|
||||
intro:
|
||||
desc: Plantilla de Inicio de Vite Dogmática
|
||||
dynamic-route: Demo de ruta dinámica
|
||||
hi: ¡Hola, {name}!
|
||||
whats-your-name: ¿Cómo te llamas?
|
||||
login:
|
||||
end_session: Sesión finalizada
|
||||
form: Credenciales
|
||||
invalidcredentials: Datos incorrectos
|
||||
newuser: Nuevo usuario
|
||||
nodata: No hay datos
|
||||
password: Password
|
||||
register: Registrarse
|
||||
reset: Reset
|
||||
see_you_soon: ¡ Nos vemos pronto !
|
||||
signin: Iniciar sesión
|
||||
subHeading: Acceso
|
||||
subtitle: LibreCloud online
|
||||
user_email: Usuario / email
|
||||
wellcome: Bienvenido/a
|
||||
menu:
|
||||
copy: Copiar
|
||||
data-grid: Cuadrícula de datos
|
||||
edit: Editar
|
||||
edit-items: Editar items
|
||||
home: Inicio
|
||||
login: Inicio sesión
|
||||
logout: Cerrar sesión
|
||||
new: Nuevo
|
||||
open-recent: Abrir reciente
|
||||
paste: Pegar
|
||||
save: Guardar
|
||||
settings: Configuración
|
||||
sign-in: Registrarse
|
||||
sign-out: Desconectar
|
||||
your-profile: Tu perfil
|
||||
message:
|
||||
loadding: Cargando
|
||||
not-found: No se ha encontrado
|
||||
notes: Notas
|
||||
notifications:
|
||||
content: contenido
|
||||
notifications: Notificiaciones
|
||||
search:
|
||||
search: Buscar
|
||||
settings:
|
||||
above-noedit: TODAS las opciones de arriba en "off" configuran el modo
|
||||
aside: Al lado
|
||||
col: Col
|
||||
columns-settings: Columnas
|
||||
filter: Filtrar
|
||||
grid: Cuadrícula
|
||||
grid-options: Opciones cuadrícula
|
||||
hidden: Ocultar
|
||||
id: Id
|
||||
list: Lista
|
||||
main: Principal
|
||||
no-edition: no edición
|
||||
pagination: Paginación
|
||||
panels: Paneles
|
||||
pos: Pos
|
||||
position: posición
|
||||
select: Seleccionar
|
||||
select-config: Seleccionar configuración
|
||||
select-grid-type: Seleccionar tipo cuadrícula
|
||||
setting-id: Identificador
|
||||
settings: Configuración
|
||||
slide: Modal
|
||||
sort: Ordenar
|
||||
table: Tabla
|
||||
undefined: No definido
|
||||
graphsettings:
|
||||
clouds: Clouds
|
||||
groups: Grupos
|
||||
srvc:
|
||||
mailserver::smtp::imap: correo imap
|
||||
mailserver::smtp::pop: correo pop
|
||||
mailserver::smtp::tls: correo tls
|
||||
mailserver::smtp: correo smtp
|
||||
pending: Pending
|
||||
nav:
|
||||
Informations: Informaciones
|
||||
Services Status: Status Servicios
|
||||
Tasks: Servicios
|
||||
Applications: Aplicaciones
|
||||
Apps: Aplicaciones
|
||||
Search: Buscar
|
||||
navm:
|
||||
Infos: Infos
|
||||
Status: Status
|
||||
Tasks: Serv.
|
||||
Apps: Aplic.
|
||||
app:
|
||||
title: Status
|
||||
profile:
|
||||
Close: Cerrar
|
||||
Settings: Configuración
|
||||
backgroud_color: Color de fondo
|
||||
Logout: Desconectar
|
17
netlify.toml
Executable file
17
netlify.toml
Executable file
@ -0,0 +1,17 @@
|
||||
[build.environment]
|
||||
NPM_FLAGS = "--prefix=/dev/null"
|
||||
NODE_VERSION = "14"
|
||||
|
||||
[build]
|
||||
publish = "dist"
|
||||
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm run build"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
|
||||
[[headers]]
|
||||
for = "/manifest.webmanifest"
|
||||
[headers.values]
|
||||
Content-Type = "application/manifest+json"
|
50
package.json
Normal file
50
package.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --port 3333 --open",
|
||||
"build": "cross-env NODE_ENV=production vite build --debug",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@animxyz/vue": "^0.6.4",
|
||||
"@headlessui/vue": "^1.4.1",
|
||||
"@intlify/vite-plugin-vue-i18n": "^2.4.0",
|
||||
"@vueuse/core": "^6.6.2",
|
||||
"@vueuse/head": "^0.6.0",
|
||||
"a17t": "^0.5.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"toastify-js": "^1.11.2",
|
||||
"vite-imagetools": "^3.8.0",
|
||||
"vite-plugin-inspect": "^0.3.9",
|
||||
"vue": "^3.2.20",
|
||||
"vue-demi": "^0.11.4",
|
||||
"vue-i18n": "^9.1.9",
|
||||
"vue-router": "^4.0.12",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.9.0",
|
||||
"@iconify-json/carbon": "^1.0.7",
|
||||
"@types/node": "^16.11.1",
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.32.0",
|
||||
"pnpm": "^6.18.0",
|
||||
"typescript": "^4.4.4",
|
||||
"unplugin-auto-import": "^0.4.12",
|
||||
"unplugin-icons": "^0.12.16",
|
||||
"unplugin-vue-components": "^0.15.6",
|
||||
"vite": "^2.6.10",
|
||||
"vite-plugin-md": "^0.11.2",
|
||||
"vite-plugin-pages": "^0.18.1",
|
||||
"vite-plugin-vue-layouts": "^0.5.0",
|
||||
"vite-plugin-windicss": "^1.4.12"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@antfu/eslint-config",
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
}
|
9
public/favicon.svg
Normal file
9
public/favicon.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<style>
|
||||
path { fill: #222; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #ffffff; }
|
||||
}
|
||||
</style>
|
||||
<path d="M27.562 26L17.17 8.928l2.366-3.888L17.828 4L16 7.005L14.17 4l-1.708 1.04l2.366 3.888L4.438 26H2v2h28v-2zM16 10.85L25.22 26H17v-8h-2v8H6.78z" />
|
||||
</svg>
|
After Width: | Height: | Size: 347 B |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
37
src/App.vue
Normal file
37
src/App.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<component :is="layout" class="text-gray-700 dark:text-gray-200">
|
||||
<router-view />
|
||||
</component>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// <Footer />
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { AppDefsAction } from '~/store/types'
|
||||
// import AppLayout from '~/layouts/AppLayout.vue'
|
||||
import { useHead } from '@vueuse/head'
|
||||
// https://github.com/vueuse/head
|
||||
// you can use this to manipulate the document head in any components,
|
||||
// they will be rendered correctly in the html results with vite-ssg
|
||||
|
||||
import defs from '../assets/defs.json'
|
||||
|
||||
const store = useStore()
|
||||
useHead({
|
||||
title: 'Status',
|
||||
meta: [
|
||||
{ name: 'description', content: 'Opinionated Vite Starter Template' },
|
||||
],
|
||||
})
|
||||
const { currentRoute } = useRouter()
|
||||
const appLayout = 'AppLayout'
|
||||
const layout = computed(() => {
|
||||
return `${currentRoute.value.meta.layout || appLayout}`
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
if (defs) {
|
||||
store.dispatch(AppDefsAction.addDefs, { key: 'ui', defs })
|
||||
}
|
||||
})
|
||||
</script>
|
46
src/components/AccordionView.vue
Normal file
46
src/components/AccordionView.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { StatusItemType } from '~/typs/clouds'
|
||||
const props = defineProps<{
|
||||
items: Array<StatusItemType>
|
||||
title: string
|
||||
}>()
|
||||
const items = ref(props.items as Array<StatusItemType>)
|
||||
const on_item = (it: any) => {
|
||||
items.value = items.value.map((i: StatusItemType) => ({ ...i, isOpen: i.title !== it.title ? false : !i.isOpen }))
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="container mx-auto px-2 py-2">
|
||||
<div id="srvcstatus" class="text-3xl bg-light-500 p-3 rounded-xl">
|
||||
{{ props.title }}
|
||||
</div>
|
||||
<div class="leading-loose text-lg mt-6">
|
||||
<div v-for="item in items" :key="item.title">
|
||||
<div>
|
||||
<button
|
||||
class="w-full font-bold border-b border-gray-400 py-3 flex justify-between items-center mt-4"
|
||||
@click="on_item(item)"
|
||||
>
|
||||
<!-- Specs has it that only one component can be open at a time and also you should be able to toggle the open state of the active component too -->
|
||||
<div>{{ item.title }}</div>
|
||||
<svg v-show="!item.isOpen" class="fill-current" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path
|
||||
class="heroicon-ui"
|
||||
d="M12 22a10 10 0 110-20 10 10 0 010 20zm0-2a8 8 0 100-16 8 8 0 000 16zm1-9h2a1 1 0 010 2h-2v2a1 1 0 01-2 0v-2H9a1 1 0 010-2h2V9a1 1 0 012 0v2z"
|
||||
/>
|
||||
</svg>
|
||||
<svg v-show="item.isOpen" class="fill-current" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path
|
||||
class="heroicon-ui"
|
||||
d="M12 22a10 10 0 110-20 10 10 0 010 20zm0-2a8 8 0 100-16 8 8 0 000 16zm4-8a1 1 0 01-1 1H9a1 1 0 010-2h6a1 1 0 011 1z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div v-show="item.isOpen" class="text-gray-700 text-sm mt-2">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
115
src/components/Breadcrump.vue
Normal file
115
src/components/Breadcrump.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="breadcrump-box transition duration-300 ease-in-out py-3 ">
|
||||
<div class="card-body">
|
||||
<nav aria-label="breadcrumb" class="flex flex-wrap">
|
||||
<ul v-for="(itm,i) in arrBcPath" :key="`bread-${i}`" class="breadcrumb">
|
||||
<li v-if="i == 0 && itm[0] == '#'" class="breadcrumb-item flex-1 cursor-pointer" @click="bookselec(itm[i])">
|
||||
<span class="shield">
|
||||
<span class="icon">
|
||||
<svg
|
||||
v-if="itm === '#fa-cloud'"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fas"
|
||||
data-icon="cloud"
|
||||
class="svg-inline--fa fa-cloud fa-w-16 dark:text-white text-blue-400"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
><path fill="currentColor" d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4z"></path></svg>
|
||||
<svg
|
||||
v-if="itm === '#fa-book'"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fad"
|
||||
data-icon="book-reader"
|
||||
class="svg-inline--fa fa-book-reader fa-w-16 dark:text-white"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
><g class="fa-group"><path class="fa-secondary" fill="currentColor" d="M256 192a96 96 0 1 1 96-96 96 96 0 0 1-96 96z" opacity="0.4" /><path class="fa-primary" fill="currentColor" d="M233.59 241.1c-59.33-36.32-155.43-46.3-203.79-49C13.55 191.13 0 203.51 0 219.14v222.8c0 14.33 11.59 26.28 26.49 27.06 43.66 2.29 132 10.68 193 41.43 9.37 4.72 20.48-1.71 20.48-11.87v-246a13.31 13.31 0 0 0-6.38-11.46zm248.61-49c-48.35 2.74-144.46 12.73-203.78 49a13.56 13.56 0 0 0-6.42 11.63v245.79c0 10.19 11.14 16.63 20.54 11.9 61-30.72 149.32-39.11 193-41.4C500.42 468.24 512 456.29 512 442V219.14c0-15.63-13.55-28.01-29.8-27.09z" /></g></svg>
|
||||
<span v-else class="iconify" :data-icon="itm.replace('#','').replace('fa-','fa:')" />
|
||||
</span>
|
||||
</span>
|
||||
<span class="slash text-xs">/</span>
|
||||
</li>
|
||||
<li v-else-if="arrBcPath[i+1]" class="breadcrumb-item flex-1 cursor-pointer">
|
||||
<a href="#{itm}" class="mx-1 leading-tight text-xs" @click="(e) => { e.preventDefault(); $router.push(`/${itm}`)}">
|
||||
{{ translate(itm) }}</a>
|
||||
<span class="slash text-xs">/</span>
|
||||
</li>
|
||||
<li v-else class="breadcrumb-item active flex-1 mx-1 ml-1 leading-tight text-xs cursor-pointer" aria-current="page">
|
||||
<span style="line-height: 2.2em" @click="bookselec(itm)"> {{ translate(itm) }} </span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({
|
||||
bcPath: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['onBookSelec'])
|
||||
// const currAppTab = ref('')
|
||||
const arrBcPath = computed(() => props.bcPath.split('/'))
|
||||
const bookselec = (book: any) => {
|
||||
emit('onBookSelec', book)
|
||||
}
|
||||
const { t, locale } = useI18n()
|
||||
const translate = (txt: any) => {
|
||||
const tr_txt = t(`bc.${txt}`)
|
||||
return tr_txt === `bc.${txt}` ? txt : tr_txt
|
||||
}
|
||||
// $: arrBcPath=bcPath.split("/");
|
||||
// const bookselec = (e) => {
|
||||
// dispatch("bookselec","main");
|
||||
// // console.log(`mode: ${mode} id: ${id} target: ${target}`);
|
||||
// const parent=".tabs-menu";
|
||||
// document.querySelectorAll(`${parent} span`).forEach(it => it.classList.remove('active') )
|
||||
// // e.target.parentElement.childNodes.forEach(it => it.classList.remove('active') )
|
||||
// // e.target.classList.add('active')
|
||||
// document.querySelectorAll(`${parent} span`).forEach(it => it.classList.remove('active') )
|
||||
// document.querySelectorAll(`${parent} span.itm-${id}`).forEach(it => it.classList.add('active') )
|
||||
// dispatch("choosetab",id);
|
||||
// //const tab=document.querySelector(`#${currTab}`);
|
||||
// //if (tab) { }
|
||||
// }
|
||||
</script>
|
||||
<style scoped>
|
||||
.breadcrumb-item .shield {
|
||||
padding: 0em 0.5em;
|
||||
/* margin-top: -0.4em; */
|
||||
}
|
||||
.breadcrumb-item .icon {
|
||||
padding: 0em;
|
||||
margin: 0;
|
||||
}
|
||||
.breadcrumb-item .icon svg {
|
||||
width: 100%;
|
||||
}
|
||||
.breadcrumb .breadcrumb-item {
|
||||
position: relative;
|
||||
}
|
||||
.breadcrumb-item a:hover {
|
||||
padding-bottom: 0.2em;
|
||||
border-bottom: 1px solid rgb(154, 154, 154);
|
||||
border-bottom-width: thin;
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
/*
|
||||
.breadcrumb .breadcrumb-item:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
*/
|
||||
.breadcrumb .breadcrumb-item:not(:last-child):after {
|
||||
content: '/';
|
||||
color: rgb(154, 154, 154);
|
||||
}
|
||||
</style>
|
61
src/components/CloudGroups.vue
Normal file
61
src/components/CloudGroups.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div :id="props.target" class="border-t-gray-800 border-opacity-90 bg-light-600 dark:bg-gray-600 w-full p-4 mt-4 md:mt-2 rounded-l">
|
||||
<div class="flex flex justify-between text-2xl bg-light-800 dark:bg-gray-400 p-3 rounded-xl" @click="on_group_item(props.target,$event)">
|
||||
<div class="dark:text-white"> {{ props.title }} </div>
|
||||
<svg v-show="props.hide" class="fill-current" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path
|
||||
class="heroicon-ui"
|
||||
d="M12 22a10 10 0 110-20 10 10 0 010 20zm0-2a8 8 0 100-16 8 8 0 000 16zm1-9h2a1 1 0 010 2h-2v2a1 1 0 01-2 0v-2H9a1 1 0 010-2h2V9a1 1 0 012 0v2z"
|
||||
/>
|
||||
</svg>
|
||||
<svg v-show="!props.hide" class="fill-current" viewBox="0 0 24 24" width="24" height="24">
|
||||
<path
|
||||
class="heroicon-ui"
|
||||
d="M12 22a10 10 0 110-20 10 10 0 010 20zm0-2a8 8 0 100-16 8 8 0 000 16zm4-8a1 1 0 01-1 1H9a1 1 0 010-2h6a1 1 0 011 1z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-for="[grp_key, grp_value] in props.groups" :key="grp_key" class="mt-3" :class="{ 'hidden' : props.hide }">
|
||||
<div
|
||||
v-for="[subgrp_key,subgrp_value] in grp_value"
|
||||
:key="subgrp_key"
|
||||
>
|
||||
<div
|
||||
v-for="(srvr,srvrindex) in subgrp_value"
|
||||
:key="srvrindex"
|
||||
class="flex flex-wrap -mx-2 pb-8"
|
||||
>
|
||||
<CloudServices
|
||||
:source="props.target === 'tsksrvcs' ? srvr.tsksrvcs : srvr.appsrvcs"
|
||||
:target="props.target"
|
||||
:filter="props.search"
|
||||
:hostname="srvr.hostname"
|
||||
@on-cloud-server-item="onCloudServiceItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { CloudGroupServcType,SrvcInfoType } from '~/typs/clouds'
|
||||
const props = defineProps<{
|
||||
groups: Map<string, Map<string, CloudGroupServcType>>
|
||||
search: string
|
||||
target: string
|
||||
title: string
|
||||
hide: boolean
|
||||
}>()
|
||||
const emit = defineEmits(['onCloudGroup'])
|
||||
const on_group_item = (target: string, event: Event) => {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
event.stopPropagation()
|
||||
emit('onCloudGroup', target)
|
||||
}
|
||||
const onCloudServiceItem = (item: SrvcInfoType) => {
|
||||
if (item) {
|
||||
// console.log(item)
|
||||
}
|
||||
}
|
||||
</script>
|
134
src/components/CloudServices.vue
Normal file
134
src/components/CloudServices.vue
Normal file
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="shape-wrapper flex flex-wrap xyz-in" xyz="fade flip-up flip-left" v-if="props.source && props.source.length > 0">
|
||||
<div
|
||||
v-for="(item ,srvcindex) in props.source"
|
||||
:key="`grp_${srvcindex}`"
|
||||
class="shape w-1/2 md:w-1/4 lg:w-1/6 xl:w-1/7 font-light dark:bg-gray-200 bg-gray-300"
|
||||
:class="{hidden: !filter_item(item)}"
|
||||
@click="on_item(item,$event)"
|
||||
>
|
||||
<div
|
||||
class="flex bg-white dark:bg-gray-300 rounded-lg shadow-md m-2 border-l-4 border-gray-500 dark:border-gray-500 hover:shadow-2xl hover:border-pink-500 cursor-pointer relative"
|
||||
>
|
||||
<div class=" p-4 pr-6 leading-normal">
|
||||
<div class="font-medium text-xl truncate">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div
|
||||
class="truncate text-xs text-gray-500 font-semibold pt-1 pb-2 tracking-widest"
|
||||
v-html="map_srvc_target(item.srvc.target)"
|
||||
>
|
||||
</div>
|
||||
<div class="flex font-medium">
|
||||
<div v-if="authData.auth !== ''" class="flex-grow text-gray-400">
|
||||
{{ item.srvc.req || 'tcp' }}
|
||||
</div>
|
||||
<div
|
||||
class="flex-grow-0"
|
||||
:class="{ 'text-indigo-500' : item.info === 'ok' , 'text-red-600' : item.info !== 'ok' && item.srvc.critical === CriticalType.yes, 'text-green-500': item.info !== 'ok' && item.srvc.critical !== CriticalType.yes}"
|
||||
>
|
||||
<span v-if="item.info === 'ok'" class="text-2xl">
|
||||
<carbon-checkmark class="align-bottom" />
|
||||
</span>
|
||||
<span v-else class="ml-3">
|
||||
<span v-if="authData.auth === '' && (item.srvc.critical === CriticalType.ifresized || item.srvc.critical === CriticalType.cloud) ">
|
||||
{{ item.info }} <span class="ml-3">{{ t('srvc.pending','pending')}}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ item.info }} <span v-if="item.srvc.critical !== CriticalType.yes" class="ml-3">{{ item.srvc.critical }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="authData.auth !== ''" class="flex mt-2 font-medium">
|
||||
<div v-if="item.srvc.path" class="flex-grow-0 text-1xl text-blue-600 hover:text-blue-700 mr-2 block" :data="item.srvc.path">
|
||||
<carbon-script class="inline-block" />
|
||||
</div>
|
||||
<div v-if="item.srvc.liveness" class="flex-grow text-xs text-blue-600 hover:text-blue-700 block" :data="item.srvc.liveness">
|
||||
{{ item.srvc.liveness }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
SrvcInfoType,
|
||||
CriticalType,
|
||||
} from '~/typs/clouds'
|
||||
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
const { t } = useI18n()
|
||||
|
||||
let shapWrapper = document.querySelector(".shape-wrapper");
|
||||
const props = defineProps<{
|
||||
source: SrvcInfoType[]
|
||||
hostname: string
|
||||
target: string
|
||||
filter: string
|
||||
}>()
|
||||
const emit = defineEmits(['onCloudServiceItem'])
|
||||
const on_item = (item: SrvcInfoType, event: Event) => {
|
||||
event.preventDefault()
|
||||
event.stopImmediatePropagation()
|
||||
event.stopPropagation()
|
||||
emit('onCloudServiceItem', item)
|
||||
}
|
||||
const authData = auth_data()
|
||||
// TODO review https://medium.com/@KevinBGreene/typescript-enums-and-polymorphism-with-type-matching-fc3dc74b031c
|
||||
const critical_matches = (val: CriticalType, match: string): boolean => {
|
||||
switch (match) {
|
||||
case 'yes':
|
||||
return val === CriticalType.yes
|
||||
case 'no':
|
||||
return val === CriticalType.no
|
||||
case 'cloud':
|
||||
return val === CriticalType.cloud
|
||||
case 'group':
|
||||
return val === CriticalType.group
|
||||
case 'ifresized':
|
||||
return val === CriticalType.ifresized
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
const map_srvc_target = (srvc_target: string): string => {
|
||||
return srvc_target ? srvc_target.replace('::','<br>') : (authData.auth !== '' && props.hostname || '')
|
||||
}
|
||||
const filter_item = (item: SrvcInfoType): boolean => {
|
||||
if (authData.auth !== '')
|
||||
return props.filter === '' || item.name.includes(props.filter) || item.srvc.target.includes(props.filter)
|
||||
|| item.srvc.liveness.includes(props.filter) || item.info.includes(props.filter)
|
||||
|| props.hostname.includes(props.filter) || critical_matches(item.srvc.critical,props.filter)
|
||||
else
|
||||
return props.filter === '' || item.name.includes(props.filter) || item.srvc.target.includes(props.filter)
|
||||
|| item.info.includes(props.filter)
|
||||
}
|
||||
onMounted(() => {
|
||||
shapWrapper = document.querySelector(".shape-wrapper");
|
||||
function animate(selector: Element|null) {
|
||||
if (selector && selector.classList.contains("xyz-in")) {
|
||||
selector.classList.remove("xyz-in");
|
||||
selector.classList.add("xyz-out");
|
||||
} else if (selector) {
|
||||
selector.classList.remove("xyz-out");
|
||||
selector.classList.add("xyz-in");
|
||||
}
|
||||
}
|
||||
|
||||
animate(shapWrapper);
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.shape-wrapper {
|
||||
justify-content: center;
|
||||
}
|
||||
.shape {
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 1rem;
|
||||
border-radius: 20px;
|
||||
}
|
||||
</style>
|
146
src/components/Footer.vue
Normal file
146
src/components/Footer.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<nav class="text-xl mt-6 dark:bg-cool-gray-800 dark:text-white">
|
||||
<span
|
||||
v-for="(itm,index) in footerMenuCentral"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
class="icon-btn mx-2"
|
||||
>
|
||||
<router-link v-if="itm.type === NavItemType.router_link && itemIf(itm)" :to="itm.to">
|
||||
<span v-if="itm.to === '/'"> <carbon-campsite /> </span>
|
||||
<span v-if="itm.to === '/login'"> <carbon-login /> </span>
|
||||
<span v-if="itm.to === '/about'"> <carbon-dicom-overlay /> </span>
|
||||
</router-link>
|
||||
<a v-if="itm.type == NavItemType.app_link" :title="t(itm.title)" @click="itemClick(itm || '')">
|
||||
<span v-if="itm.vif && itm.vif === 'isDark'" class="icon">
|
||||
<svg
|
||||
v-if="isDark"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fal"
|
||||
data-icon="cloud-sun"
|
||||
class="svg-inline--fa fa-cloud-sun fa-w-20 dark:text-white text-blue-400 w-7"
|
||||
style="margin-top: -0.8em"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
><path fill="currentColor" d="M543.7 304.3C539.8 259.4 502 224 456 224c-17.8 0-34.8 5.3-49.2 15.2-22.5-29.5-57.3-47.2-94.8-47.2-66.2 0-120 53.8-120 120v.4c-38.3 16-64 53.5-64 95.6 0 57.3 46.7 104 104 104h304c57.3 0 104-46.7 104-104 0-54.8-42.6-99.8-96.3-103.7zM536 480H232c-39.7 0-72-32.3-72-72 0-32.3 21.9-60.7 53.3-69.2l13.3-3.6-2-17.2c-.3-2-.6-4-.6-6 0-48.5 39.5-88 88-88 32.2 0 61.8 17.9 77.2 46.8l10.6 19.8 15.2-16.5c10.8-11.7 25.4-18.1 41-18.1 30.9 0 56 25.1 56 56 0 1.6-.3 3.1-.8 6.9l-2.5 20 23.5-2.4c1.2-.2 2.5-.4 3.8-.4 39.7 0 72 32.3 72 72S575.7 480 536 480zM92.6 323l12.5-63.2c1.2-6.3-1.4-12.8-6.8-16.4l-53.5-35.8 53.5-35.8c5.4-3.6 8-10.1 6.8-16.4L92.6 92.1l63.2 12.5c6.4 1.3 12.8-1.5 16.4-6.8L208 44.3l35.8 53.5c3.6 5.3 9.9 8.1 16.4 6.8l63.2-12.5-12.5 63.2c-.3 1.6-.1 3.2 0 4.8.4 0 .7-.1 1.1-.1 10.1 0 20 1.1 29.6 3 .2-.5.5-.9.6-1.5l17.2-86.7c1-5.2-.6-10.6-4.4-14.4s-9.2-5.5-14.4-4.4l-76.2 15.1-43.1-64.4c-6-8.9-20.6-8.9-26.6 0l-43.2 64.5-76.1-15.1c-5.3-1.1-10.7.6-14.4 4.4-3.8 3.8-5.4 9.2-4.4 14.4l15.1 76.2-64.6 43.1c-4.4 3-7.1 8-7.1 13.3s2.7 10.3 7.1 13.3L71.6 264l-15.1 76.2c-1 5.2.6 10.6 4.4 14.4 3 3 7.1 4.7 11.3 4.7 1 0 2.1-.1 3.1-.3l32.8-6.5c6.2-13.8 14.6-26.5 25.1-37.6L92.6 323zM208 149.7c18.5 0 34.8 8.9 45.4 22.4 10.2-4.3 20.8-7.6 31.9-9.6-15.6-26.6-44.2-44.8-77.3-44.8-49.5 0-89.8 40.3-89.8 89.8 0 33 18.1 61.7 44.8 77.3 2-11.1 5.3-21.7 9.6-31.9-13.6-10.6-22.4-26.9-22.4-45.4 0-31.8 25.9-57.8 57.8-57.8z"></path></svg>
|
||||
<svg
|
||||
v-else
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fal"
|
||||
data-icon="cloud-moon"
|
||||
class="svg-inline--fa fa-cloud-moon fa-w-20 dark:text-white text-blue-400 w-7"
|
||||
style="margin-top: -0.8em"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512"
|
||||
><path fill="currentColor" d="M351.5 313.6c-4.5-41.3-39.6-73.6-82.1-73.6-13.9 0-27.3 3.5-39.5 10.3-18.3-16.8-41.9-26.3-66.8-26.3-53.7 0-97.5 42.8-99.2 96.2-38.3 14.3-64 50.7-64 92.7C0 467.5 44.5 512 99.2 512h249.6c54.7 0 99.2-44.5 99.2-99.2 0-53.8-43-97.7-96.5-99.2zM348.8 480H99.2C62.1 480 32 449.9 32 412.8c0-31.6 21.5-58.5 52.4-65.4l14-5.9-2-14.6c-.2-1.2-.4-2.5-.4-3.8 0-37.1 30.1-67.2 67.2-67.2 20.1 0 39 9.2 52 25.2l10.1 12.5 12.4-10.1C247 276 258 272 269.4 272c27.9 0 50.6 22.7 50.6 50.6 0 1.5-.2 2.8-.7 6.4l-2.6 21.3 21.2-3.6c3.6-.6 7.2-1.2 11-1.2 37.1 0 67.2 30.2 67.2 67.2S385.9 480 348.8 480zm288.8-192.8c-4.1-8.6-12.4-13.9-21.8-13.9-1.5 0-3 .1-4.6.4-7.7 1.5-15.5 2.2-23.2 2.2-67 0-121.5-54.7-121.5-121.9 0-43.7 23.6-84.3 61.6-106 8.9-5.1 13.6-15 11.9-25.1-1.7-10.2-9.4-17.9-19.5-19.8-11.5-2-23.2-3.1-35-3.1-105.7 0-191.8 86.1-191.8 192 0 6.5.4 12.8 1.1 19.2 12.7 2.9 24.6 7.8 35.4 14.6-2.6-10.9-4.5-22-4.5-33.7 0-88.2 71.7-160 159.8-160 3 0 5.9.1 8.9.2-37.4 28.9-59.9 73.9-59.9 121.9C434.5 239 503.4 308 588 308c2.6 0 5.2-.1 7.8-.2C566.2 336.1 527 352 485.5 352c-7.5 0-14.8-.7-21.9-1.9 5.7 10.3 9.7 21.5 12.5 33.1 3.1.2 6.2.7 9.4.7 58.1 0 112.4-25.9 149-71.1 6-7.2 7.2-17.1 3.1-25.6z"></path></svg>
|
||||
</span>
|
||||
<IconLink v-else :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode || ''" :open="false" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
|
||||
</a>
|
||||
<a
|
||||
v-if="itm.type == NavItemType.a_blank"
|
||||
rel="noreferrer"
|
||||
:href="itm.href || '#'"
|
||||
target="_blank"
|
||||
:title="t(itm.title)"
|
||||
>
|
||||
<span v-if="itm.icon_on === 'carbon-logo-github'">
|
||||
<carbon-logo-github />
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</nav>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { isDark, toggleDark } from '~/logics'
|
||||
import { NavItemType } from '~/typs'
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import useState from '~/hooks/useState'
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
|
||||
// https://vitejs.dev/guide/features.html#static-assets
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const { t, availableLocales, locale } = useI18n()
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const footerMenuCentral = computed(() => {
|
||||
return defs.value && defs.value.footer && defs.value.footer.central_items ? defs.value.footer.central_items : []
|
||||
})
|
||||
const toggleLocales = () => {
|
||||
// change to some real logic
|
||||
const locales = availableLocales
|
||||
locale.value = locales[(locales.indexOf(locale.value) + 1) % locales.length]
|
||||
}
|
||||
const itemIf = (itm: SideMenuItemType) => {
|
||||
switch (itm.vif) {
|
||||
case 'notValidUser':
|
||||
return auth_data().auth === '' ? true : false
|
||||
;;
|
||||
case 'validUser':
|
||||
return auth_data().auth !== '' ? true : false
|
||||
;;
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
const itemClick = (itm: SideMenuItemType) => {
|
||||
switch (itm.click) {
|
||||
case 'toggleDark':
|
||||
toggleDark()
|
||||
break
|
||||
case 'toggleLocales':
|
||||
toggleLocales()
|
||||
break
|
||||
case 'tophome':
|
||||
if (useState().app_home_click.value) {
|
||||
useState().app_home_click.value()
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (useState().side_menu_click.value) {
|
||||
try {
|
||||
useState().side_menu_click.value(itm)
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
<router-link class="icon-btn mx-2" to="/" :title="t('button.home')">
|
||||
<carbon-campsite />
|
||||
</router-link>
|
||||
|
||||
<a class="icon-btn mx-2" :title="t('button.toggle_dark')" @click="toggleDark">
|
||||
<carbon-moon v-if="isDark" />
|
||||
<carbon-sun v-else />
|
||||
</a>
|
||||
|
||||
<a class="icon-btn mx-2" :title="t('button.toggle_langs')" @click="toggleLocales">
|
||||
<carbon-language />
|
||||
</a>
|
||||
|
||||
<router-link class="icon-btn mx-2" to="/about" :title="t('button.about')">
|
||||
<carbon-dicom-overlay />
|
||||
</router-link>
|
||||
|
||||
<a class="icon-btn mx-2" rel="noreferrer" href="https://github.com/antfu/vitesse" target="_blank" title="GitHub">
|
||||
<carbon-logo-github />
|
||||
</a>
|
||||
<router-link class="icon-btn mx-2" to="/login" :title="t('button.login')">
|
||||
<carbon-login />
|
||||
</router-link>
|
||||
*/
|
||||
</script>
|
44
src/components/IconLink.vue
Normal file
44
src/components/IconLink.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<span class="flex" :class="{'justify-start': open}">
|
||||
<span v-if="pfx" class="-ml-5 mr-2 text-xs" :class="{ 'lg:hidden': open }">
|
||||
<small class="text-gray-400">{{ pfx }}</small>
|
||||
</span>
|
||||
<span v-if="icon === 'carbon-home'" class="mt-1"> <carbon-home /> </span>
|
||||
<span v-if="icon === 'carbon-campsite'" class="mt-1"> <carbon-campsite /> </span>
|
||||
<span v-if="icon === 'carbon-login'" class="mt-1"> <carbon-login /> </span>
|
||||
<span v-if="icon === 'carbon-dicom-overlay'" class="mt-1"> <carbon-dicom-overlay /> </span>
|
||||
<span v-if="icon === 'carbon-table'" class="mt-1"> <carbon-table class="dark:text-white" /> </span>
|
||||
<span v-if="icon === 'carbon-image'" class="mt-1"> <carbon-image /> </span>
|
||||
<span v-if="icon === 'carbon-storage-pool'" class="mt-1"> <carbon-storage-pool /> </span>
|
||||
<span v-if="icon === 'carbon-application'"> <carbon-application /> </span>
|
||||
<span v-if="icon === 'carbon-services'" class="mt-1"> <carbon-cloud-services /> </span>
|
||||
<span v-if="icon === 'carbon-event'"> <carbon-event /> </span>
|
||||
<span v-if="icon === 'carbon-operation'"> <carbon-operation /> </span>
|
||||
<span v-if="icon === 'carbon-api'"> <carbon-api /> </span>
|
||||
<span v-if="icon === 'carbon-cloud'" class="flex justify-center items-center"> <carbon-cloud /> </span>
|
||||
<span v-if="icon === 'carbon-flow'"> <carbon-flow /> </span>
|
||||
<span v-if="icon === 'carbon-nominal'"> <carbon-nominal /> </span>
|
||||
<span v-if="icon === 'carbon-language'"> <carbon-language /> </span>
|
||||
<span v-if="show_to && name && name !== 'items'" class="mt-1 ml-2 text-xs" :class="{ 'hidden': open }"> {{ t(`${name}`,name||'') || '' }}</span>
|
||||
<span v-if="mode !== 'icon'" class="ml-2" :class="{ 'lg:hidden': !open }"> {{ t(`${title}`,title||'') }}</span>
|
||||
<span v-if="show_to" :class="{ 'lg:hidden': !open }" class="ml-2 text-xs flex justify-center items-center "> {{ t(`${name}`,name || '') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NavItemType } from '~/typs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
typ: NavItemType
|
||||
icon: string | undefined
|
||||
pfx: string | undefined
|
||||
name: string | undefined
|
||||
show_to: boolean | false
|
||||
title: string | undefined
|
||||
mode: sring | undefined
|
||||
open: boolean | false
|
||||
}>()
|
||||
|
||||
</script>
|
110
src/components/NavMenu.vue
Normal file
110
src/components/NavMenu.vue
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
<template>
|
||||
<nav
|
||||
class="flex items-center justify-between flex-wrap p-1 fixed w-full z-10 top-0 left-0"
|
||||
:class="{ 'shadow-lg bg-indigo-900' : isOpen , 'bg-blue-900' : !isOpen}"
|
||||
@keydown.escape="isOpen = false"
|
||||
>
|
||||
<!--Logo etc-->
|
||||
<div class="flex items-center flex-shrink-0 text-white ml-2 mr-6">
|
||||
<a
|
||||
aria-current="page"
|
||||
href="/"
|
||||
class="router-link-active router-link-exact-active flex items-center text-white no-underline hover:text-white hover:no-underline"
|
||||
title="LibreCloud"
|
||||
>
|
||||
<span class="w-11 h-11 rounded-full mr-2">
|
||||
<img class="max-w-7xl w-32 h-rounded" src="/assets/images/logo.svg" alt="App">
|
||||
</span>
|
||||
<span class="hidden text-sm md:block mr-2">{{props.navtitle}}</span>
|
||||
<span
|
||||
v-if="usetitle"
|
||||
class="text-xl pl-2"
|
||||
><i class="em em-grinning"></i> {{ t(`nav.${props.title}`,props.title) }} </span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!--Toggle button (hidden on large screens)-->
|
||||
<button
|
||||
type="button"
|
||||
class="block lg:hidden px-2 text-gray-300 hover:text-white focus:outline-none focus:text-white"
|
||||
:class="{ 'transition transform-180': isOpen }"
|
||||
@click.prevent="isOpen = !isOpen"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
v-show="isOpen"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z"
|
||||
/>
|
||||
<path
|
||||
v-show="!isOpen"
|
||||
fill-rule="evenodd"
|
||||
d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!--Menu-->
|
||||
<div
|
||||
class="w-full flex-grow lg:flex lg:items-center lg:w-auto"
|
||||
:class="{ 'block shadow-3xl': isOpen, 'hidden': !isOpen }"
|
||||
>
|
||||
<ul
|
||||
class="pt-4 lg:pt-0 list-reset lg:flex justify-end flex-1 items-center"
|
||||
>
|
||||
<li v-for="item in items" :key="item.name_to" class="mr-3">
|
||||
<a
|
||||
class="inline-block py-2 px-4 text-white no-underline"
|
||||
:class="item.active ? '' : 'text-gray-400 no-underline hover:text-gray-200 hover:text-underline'"
|
||||
:href="item.href"
|
||||
@click.prevent="on_item(item)"
|
||||
> {{ t(`nav.${item.title}`,item.title) }}
|
||||
</a>
|
||||
</li>
|
||||
<slot name="opslist" />
|
||||
<li class="p-1">
|
||||
<div class="bg-white flex items-center rounded-full shadow-xl">
|
||||
<slot name="search" />
|
||||
</div>
|
||||
</li>
|
||||
<slot name="lastitems" />
|
||||
<li class="p-1" v-if="authData.auth !== ''">
|
||||
<router-link class="icon-btn text-2xl text-white no-underline" to="/logout" :title="t('button.logout')">
|
||||
<carbon-logout />
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="p-1" v-else>
|
||||
<router-link class="icon-btn text-2xl text-white no-underline" to="/login" :title="t('button.login')">
|
||||
<carbon-login />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
navtitle: string
|
||||
usetitle: boolean
|
||||
title: string
|
||||
items: Array<SideMenuItemType>
|
||||
}>()
|
||||
const authData = auth_data()
|
||||
|
||||
const emit = defineEmits(['onNavMenu'])
|
||||
const isOpen = ref(false)
|
||||
const on_item = (item: SideMenuItemType) => {
|
||||
emit('onNavMenu', item)
|
||||
}
|
||||
</script>
|
137
src/components/Profile.vue
Normal file
137
src/components/Profile.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import useState from '~/hooks/useState'
|
||||
const { t } = useI18n()
|
||||
|
||||
const profile = ref({
|
||||
username: 'jesusperez',
|
||||
fullname: 'Jesús Pérez Lorenzo',
|
||||
bgcolors: ['bg-emerald-500', 'bg-purple-500', 'bg-yellow-500', 'bg-gray-800', 'bg-pink-500'],
|
||||
bgcolor: 'bg-dark-800',
|
||||
photourl: 'https://avatars.githubusercontent.com/u/59666?v=4',
|
||||
})
|
||||
const profile_showsettings= ref(false)
|
||||
const profile_editing= ref('')
|
||||
const profile_saveedit = (name: string) => {
|
||||
profile_editing.value = ''
|
||||
}
|
||||
const profile_edit = (name: string) => {
|
||||
profile_editing.value = name
|
||||
// this.$nextTick(() => {
|
||||
// this.$refs[`${name}input`].focus()
|
||||
// })
|
||||
}
|
||||
const profile_discard = (name: string) => {
|
||||
// this.$refs[`${name}input`].value = this[name]
|
||||
profile_editing.value = ''
|
||||
}
|
||||
const profile_selectcolor = (color: string) => {
|
||||
profile.value.bgcolor = color
|
||||
}
|
||||
const onSettings = () => {
|
||||
profile_showsettings.value= true
|
||||
}
|
||||
const onCloseSettings = () => {
|
||||
profile_showsettings.value=false
|
||||
}
|
||||
const onLogout = () => {
|
||||
useState().show_profile.value = false
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="block flex flex-col w-full h-screen justify-center items-center bg-white">
|
||||
<div class="flex flex-col w-75 shadow-md max-w-full h-70 rounded-lg transition-colors ease-in" :class="profile.bgcolor">
|
||||
<div v-show="profile_showsettings" class="fixed z-10 flex flex-col w-75 max-w-full h-70 text-center text-white p-5 origin-top-left transform scale-0 transition-all" :class="{'scale-100': profile_showsettings}">
|
||||
<span class="text-2xl font-bold">{{ t('profile.Settings','Settings') }}</span>
|
||||
<div class="flex flex-col space-y-2 mt-5 flex-grow">
|
||||
<span class="text-md font-bold">{{ t('profile.backgroud_color','Background color') }}:</span>
|
||||
<div class="w-full flex justify-center space-x-2">
|
||||
<span v-for="c in profile.bgcolors" :key="c">
|
||||
<button class="flex border-4 border-white inline cursor-pointer w-8 h-8 rounded-full" :class="c" :disabled="profile.bgcolor == c" @click="profile_selectcolor(c)"></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center flex-shrink align-end">
|
||||
<button class="rounded bg-transparent font-bold text-lg fill-current text-white" @click="onCloseSettings">
|
||||
{{ t(`profile.Close`,'Close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!profile_showsettings">
|
||||
<div class="flex w-full justify-between p-2 mt-1">
|
||||
<button @click="onSettings" class="flex space-x-1 group font-semibold items-center bg-transparent cursor-pointer text-white fill-current">
|
||||
<svg class="w-7 h-7" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span class="origin-left transform scale-x-0 group-focus:scale-x-100 group-hover:scale-x-100 transition-transform ease-in select-none text-lg">{{ t('profile.Settings','Settings') }}</span>
|
||||
</button>
|
||||
<button @click="onLogout" class="flex space-x-1 group font-semibold items-center bg-transparent cursor-pointer text-white fill-current">
|
||||
<span class="origin-right transform scale-x-0 group-focus:scale-x-100 group-hover:scale-x-100 transition-transform ease-in select-none text-lg">{{ t('profile.Logout','Logout') }}</span>
|
||||
<svg class="w-7 h-7" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-full h-full text-center">
|
||||
<div class="flex flex-col space-y-2 items-center mb-3">
|
||||
<img :src="profile.photourl" alt="Profile Picture" class="rounded-full select-none w-22">
|
||||
<div class="text-center relative group">
|
||||
<span v-show="profile_editing !== 'fullname'" class="text-xl break-words font-semibold text-white font-sans m-0 p-0 px-2 select-none" title="Double click to edit" @dblclick="profile_edit('fullname')">{{ profile.fullname }}</span>
|
||||
<svg v-show="profile_editing != 'fullname'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-50 inline absolute right-2 -top-3 transform scale-0 group-hover:scale-100 transition-transform" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<input
|
||||
v-show="profile_editing == 'fullname'"
|
||||
id=""
|
||||
type="text"
|
||||
name=""
|
||||
class="bg-transparent text-xl font-sans w-full p-0 px-2 m-0 text-center text-white font-semibold focus:outline-none focus:animate-pulse"
|
||||
:value="profile.fullname"
|
||||
spellcheck="false"
|
||||
x-ref="fullnameinput"
|
||||
title="Enter to save, click outside to discard."
|
||||
maxlength="20"
|
||||
@keydown.enter="profile_saveedit('fullname')"
|
||||
@click="profile_discard('fullname')"
|
||||
@keydown.escape="profile_discard('fullname')"
|
||||
>
|
||||
</div>
|
||||
<div class="text-center relative group">
|
||||
<span v-show="profile_editing != 'username'" class="text-sm text-wrap font-semibold font-sans m-0 p-0 text-green-100 select-none" title="Double click to edit" @dblclick="profile_edit('username')">{{ profile.username }}</span>
|
||||
<svg v-show="profile_editing != 'username'" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-50 inline absolute ml-1 -top-1 transform scale-0 group-hover:scale-100 transition-transform" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" />
|
||||
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<input
|
||||
v-show="profile_editing == 'username'"
|
||||
id=""
|
||||
type="text"
|
||||
name=""
|
||||
class="inline-block bg-transparent text-sm font-sans p-0 m-0 w-auto text-center font-semibold text-green-100 focus:outline-none focus:animate-pulse"
|
||||
:value="profile.username"
|
||||
spellcheck="false"
|
||||
x-ref="usernameinput"
|
||||
title="Enter to save, click outside to discard."
|
||||
maxlength="15"
|
||||
@keydown.enter="profile_saveedit('username')"
|
||||
@click="profile_discard('username')"
|
||||
@keydown.escape="profile_discard('username')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden flex flex-row justify-evenly">
|
||||
<div class="flex flex-col cursor-pointer hover:opacity-80">
|
||||
<span class="text-lg font-bold text-white">11</span>
|
||||
<span class="text-green-100 text-sm">Followers</span>
|
||||
</div>
|
||||
<div class="flex flex-col cursor-pointer hover:opacity-80">
|
||||
<span class="text-lg font-bold text-white">52</span>
|
||||
<span class="text-green-100 text-sm">Following</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
9
src/components/README.md
Normal file
9
src/components/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
## Components
|
||||
|
||||
Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).
|
||||
|
||||
### Icons
|
||||
|
||||
You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/).
|
||||
|
||||
It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details.
|
5
src/components/icons/AppImg.vue
Normal file
5
src/components/icons/AppImg.vue
Normal file
File diff suppressed because one or more lines are too long
5
src/components/icons/AppLogoText.vue
Normal file
5
src/components/icons/AppLogoText.vue
Normal file
File diff suppressed because one or more lines are too long
7
src/components/icons/AppLogoV.vue
Normal file
7
src/components/icons/AppLogoV.vue
Normal file
File diff suppressed because one or more lines are too long
99
src/components/menus/MenuLocales.vue
Normal file
99
src/components/menus/MenuLocales.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<button class="icon-btn mx-2" @click="toggleShowLocales">
|
||||
<carbon-language class="inline-block text-gray-900 dark:text-white" />
|
||||
</button>
|
||||
<div v-if="showLocalesSelector" :class="`select !normal max-w-xs ${selWidth} float-right`">
|
||||
<select v-model="currentLocale" class="dark:text-white">
|
||||
<option
|
||||
v-for="itm in listLocales"
|
||||
:key="itm"
|
||||
:selected="itm === currentLocale"
|
||||
:value="itm"
|
||||
>
|
||||
{{ localeLabel(itm) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// import {
|
||||
// PropType,
|
||||
// } from 'vue'
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
// <vue:window on:keydown={cleanOverlay} />
|
||||
import {
|
||||
ref,
|
||||
onMounted,
|
||||
computed,
|
||||
PropType,
|
||||
} from 'vue'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
enum LocalesLabelModes {
|
||||
value = 'val',
|
||||
translation = 'trans',
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
labelMode: {
|
||||
type: Object as PropType<LocalesLabelModes>,
|
||||
//default: LocalesLabelModes.translation,
|
||||
required: false,
|
||||
},
|
||||
includeCurrent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
const i18n = useI18n()
|
||||
|
||||
const showLocalesSelector = ref(false)
|
||||
|
||||
const currentLocale = computed({
|
||||
get: (): string => i18n.locale.value,
|
||||
set: (val: string) => {
|
||||
if (i18n.availableLocales.includes(val)) {
|
||||
// console.log(`change to ${val} from ${i18n.locale.value}`)
|
||||
i18n.locale.value = val
|
||||
showLocalesSelector.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
const locales = computed(() => i18n.availableLocales)
|
||||
const listLocales = computed(() =>
|
||||
props.includeCurrent ? locales.value : locales.value.filter(lcl => lcl !== currentLocale.value))
|
||||
const toggleShowLocales = () => {
|
||||
showLocalesSelector.value = !showLocalesSelector.value
|
||||
// change to some real logic
|
||||
// i18n.locale.value = i18n.availableLocales[(i18n.availableLocales.indexOf(i18n.locale.value) + 1) % i18n.availableLocales.length]
|
||||
}
|
||||
const localeLabel = (value: string): string => {
|
||||
let label = ''
|
||||
switch (props.labelMode) {
|
||||
case LocalesLabelModes.translation:
|
||||
label = i18n.t(value, value)
|
||||
break
|
||||
case LocalesLabelModes.value:
|
||||
label = value
|
||||
break
|
||||
}
|
||||
return label
|
||||
}
|
||||
const selWidth = computed(() => {
|
||||
let width = ''
|
||||
switch (props.labelMode) {
|
||||
case LocalesLabelModes.translation:
|
||||
width = 'w-28'
|
||||
break
|
||||
case LocalesLabelModes.value:
|
||||
width = 'w-24'
|
||||
break
|
||||
}
|
||||
return width
|
||||
})
|
||||
onMounted(() => {
|
||||
})
|
||||
</script>
|
251
src/components/navbar/Navbar.vue
Normal file
251
src/components/navbar/Navbar.vue
Normal file
@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<nav class="flex items-center justify-between pt-1 dark:bg-cool-gray-800 dark:text-white">
|
||||
<!-- Navbar left -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase lg:hidden" @click="on_logo_app">
|
||||
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/app_w.svg" alt="App">
|
||||
</span>
|
||||
<!-- Toggle sidebar button -->
|
||||
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="toggleSidebar">
|
||||
<span class="sr-only">Toggle sidebar</span>
|
||||
<ChevronDoubleRight
|
||||
aria-hidden="true"
|
||||
class="w-4 h-4 text-gray-600 dark:text-white"
|
||||
:class="{ 'transform transition-transform -rotate-180': isSidebarOpen }"
|
||||
/>
|
||||
</button>
|
||||
<Breadcrump :bc-path="bcPath" @on-book-selec="bookSelec" />
|
||||
<span class="lg:ml-3 flex">
|
||||
<span
|
||||
class="hidden lg:flex flex-grow"
|
||||
:class="`${navTitle && navTitle.cmpnt === 'boxmenu' ? 'mt-2': ''} ${navTitle.textclick ? 'cursor-pointer': ''}`"
|
||||
@click="onNavTitleClick"
|
||||
>{{ navTitle && navTitle.text || '' }}
|
||||
</span>
|
||||
<BoxMenu
|
||||
v-if="navTitle && navTitle.text !== '' && navTitle.cmpnt === 'boxmenu'"
|
||||
class="-ml-4 lg:ml-0 flex-grow-0"
|
||||
:menu-options="navTitle.ops"
|
||||
:title="navTitle.title"
|
||||
:btn-type="navTitle.btntype"
|
||||
@on-menu-option="onNavTitleMenuOption"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Navbar right -->
|
||||
<nav aria-label="Secondary" class="flex items-center space-x-3">
|
||||
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase" @click="on_logo_app">
|
||||
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
|
||||
</span>
|
||||
<NavbarIconButton
|
||||
v-show="authData.auth === ''"
|
||||
label="Login"
|
||||
@click="router.push('/login')"
|
||||
>
|
||||
<carbon-login /> {{ t('button.login','Login') }}
|
||||
</NavbarIconButton>
|
||||
<NavbarIconButton
|
||||
v-show="useSettings"
|
||||
label="Open setting panel"
|
||||
@click="isSettingsPanelOpen = !isSettingsPanelOpen"
|
||||
>
|
||||
<carbon-settings />
|
||||
</NavbarIconButton>
|
||||
<!-- Search button -->
|
||||
<NavbarIconButton v-if="navbar_right.search" label="Open search panel" @click="isSearchPanelOpen = true">
|
||||
<!-- SearchIcon aria-hidden="true" class="w-6 h-6" -->
|
||||
<carbon-search />
|
||||
</NavbarIconButton>
|
||||
|
||||
<!-- Notification Button -->
|
||||
<NavbarIconButton v-if="navbar_right.notify" label="Open notifications panel" @click="isNotificationsPanelOpen = true">
|
||||
<!--BellIcon aria-hidden="true" class="w-6 h-6" /-->
|
||||
<carbon-notification />
|
||||
</NavbarIconButton>
|
||||
<!-- User menu -->
|
||||
<Menu v-slot="{ open }" as="div" class="relative pr-2">
|
||||
<MenuButton
|
||||
id="user-menu"
|
||||
aria-haspopup="true"
|
||||
:aria-expanded="open ? 'true' : 'false'"
|
||||
class="relative flex items-center overflow-hidden rounded-full group focus:outline-none focus:ring"
|
||||
v-if="authData.auth !== ''"
|
||||
>
|
||||
<span class="sr-only">{{ `${navbar_right.title || 'Open menu'}` }}</span>
|
||||
<img
|
||||
class="object-cover w-8 h-8 rounded-full group-hover:opacity-90"
|
||||
src="/assets/images/jesus.jpg"
|
||||
alt="User image"
|
||||
>
|
||||
<!-- <div class="absolute right-0 p-1 bg-green-400 rounded-full top-1 animate-ping"></div>
|
||||
<div class="absolute right-0 p-1 bg-green-400 border border-white rounded-full top-1"></div> -->
|
||||
</MenuButton>
|
||||
<transition
|
||||
enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-out"
|
||||
leave-from-class="transform scale-100 opacity-100"
|
||||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-999 w-48 py-1 mt-2 origin-top-righti dark:bg-gray-600 bg-white rounded-md shadow-lg focus:outline-none"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="user-menu"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="(itm,index) in navbar_right.items"
|
||||
v-slot="{ active }"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
>
|
||||
<span class="block text-sm dark:text-gray-300 text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-500">
|
||||
<a
|
||||
v-if="itm && itm.type === 'app_link'"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
class="px-4 py-2 flex items-center"
|
||||
role="menuitem"
|
||||
@click.prevent="onMenuItemClick(itm)"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</a>
|
||||
<router-link
|
||||
v-if="itm && itm.type === 'router_link'"
|
||||
:title="t(itm.title)"
|
||||
:to="{ name: itm.name_to }"
|
||||
class="px-4 py-2 flex items-center"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</nav>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/*
|
||||
// https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.your-profile') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.settings') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.sign-out') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
*/
|
||||
import {
|
||||
computed,
|
||||
} from 'vue'
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
import ChevronDoubleRight from '@/icons/ChevronDoubleRight.vue'
|
||||
// import SearchIcon from '@/icons/SearchIcon.vue'
|
||||
// import BellIcon from '@/icons/BellIcon.vue'
|
||||
import Breadcrump from '@/Breadcrump.vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import NavbarIconButton from './NavbarIconButton.vue'
|
||||
import BoxMenu from '~/views/BoxMenu.vue'
|
||||
import useState from '~/hooks/useState'
|
||||
import useComponent from '~/hooks/useComponent'
|
||||
// import { NavItemType } from '~/typs'
|
||||
// import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
const open_menu = ref(false)
|
||||
const { isSidebarOpen, isSearchPanelOpen, isSettingsPanelOpen, toggleSidebar, isNotificationsPanelOpen, bcPath, navTitle } = useState()
|
||||
const { t } = useI18n()
|
||||
|
||||
const authData = computed(() => auth_data())
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const user_image = computed(() => {
|
||||
const image = defs.value.profile && defs.value.profile.image ? defs.value.profile.image : ''
|
||||
if (image === '') {
|
||||
return '/assets/images/user.jpg'
|
||||
}
|
||||
if (image.charAt(0) === '/' || image.includes('http')) {
|
||||
return image
|
||||
} else {
|
||||
return `/assets/images/${image}`
|
||||
}
|
||||
})
|
||||
|
||||
const useSettings = computed(() => Object.entries(useComponent().settingsComponent.value).length !== 0)
|
||||
|
||||
const navbar_right = computed(() => {
|
||||
return defs.value && defs.value.header && defs.value.header.navbar && defs.value.header.navbar.menuright
|
||||
? defs.value.header.navbar.menuright
|
||||
: { title: '', notify: false, search: false, items: [] }
|
||||
})
|
||||
|
||||
const bookSelec = (target: string) => {
|
||||
switch (target) {
|
||||
case 'home':
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
router.push('/')
|
||||
break
|
||||
default:
|
||||
useState().bookSelec(target)
|
||||
}
|
||||
}
|
||||
const on_logo_app = () => {
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
useState().isSidebarOpen.value = false
|
||||
if (useState().app_home_click.value) {
|
||||
useState().app_home_click.value()
|
||||
}
|
||||
router.push('/')
|
||||
}
|
||||
const onMenuItemClick = (itm: SideMenuItemType ) => {
|
||||
const ustate: any = useState()
|
||||
const ky=`show_${itm.click}`
|
||||
if (ustate[ky])
|
||||
ustate[ky].value = !ustate[ky].value
|
||||
}
|
||||
const onNavTitleMenuOption = (data: any) => {
|
||||
if (navTitle.value.cllbck)
|
||||
navTitle.value.cllbck(data)
|
||||
}
|
||||
const onNavTitleClick = () => {
|
||||
if (navTitle.value.textclick)
|
||||
navTitle.value.textclick()
|
||||
}
|
||||
</script>
|
18
src/components/navbar/NavbarIconButton.vue
Normal file
18
src/components/navbar/NavbarIconButton.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center p-2 text-gray-400 transition-colors bg-gray-100 rounded-full focus:outline-none focus:ring hover:bg-gray-200 hover:text-gray-500 dark:bg-cool-gray-600 dark:text-white"
|
||||
>
|
||||
<span class="sr-only">{{ label }}</span>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
236
src/components/navbar/v.vue
Normal file
236
src/components/navbar/v.vue
Normal file
@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<nav class="flex items-center justify-between pt-1 dark:bg-cool-gray-800 dark:text-white">
|
||||
<!-- Navbar left -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase lg:hidden" @click="on_logo_app">
|
||||
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/app_w.svg" alt="App">
|
||||
</span>
|
||||
<!-- Toggle sidebar button -->
|
||||
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="toggleSidebar">
|
||||
<span class="sr-only">Toggle sidebar</span>
|
||||
<ChevronDoubleRight
|
||||
aria-hidden="true"
|
||||
class="w-4 h-4 text-gray-600 dark:text-white"
|
||||
:class="{ 'transform transition-transform -rotate-180': isSidebarOpen }"
|
||||
/>
|
||||
</button>
|
||||
<Breadcrump :bc-path="bcPath" @on-book-selec="bookSelec" />
|
||||
<span class="lg:ml-3 flex">
|
||||
<span
|
||||
class="hidden lg:flex flex-grow"
|
||||
:class="`${navTitle && navTitle.cmpnt === 'boxmenu' ? 'mt-2': ''} ${navTitle.textclick ? 'cursor-pointer': ''}`"
|
||||
@click="onNavTitleClick"
|
||||
>{{ navTitle && navTitle.text || '' }}
|
||||
</span>
|
||||
<BoxMenu
|
||||
v-if="navTitle && navTitle.text !== '' && navTitle.cmpnt === 'boxmenu'"
|
||||
class="-ml-4 lg:ml-0 flex-grow-0"
|
||||
:menu-options="navTitle.ops"
|
||||
:title="navTitle.title"
|
||||
:btn-type="navTitle.btntype"
|
||||
@on-menu-option="onNavTitleMenuOption"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Navbar right -->
|
||||
<nav aria-label="Secondary" class="flex items-center space-x-3">
|
||||
<span class="cursor-pointer p-2 text-xl font-semibold tracking-wider uppercase" @click="on_logo_app">
|
||||
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
|
||||
</span>
|
||||
<NavbarIconButton
|
||||
v-show="useSettings"
|
||||
label="Open setting panel"
|
||||
@click="isSettingsPanelOpen = !isSettingsPanelOpen"
|
||||
>
|
||||
<carbon-settings />
|
||||
</NavbarIconButton>
|
||||
<!-- Search button -->
|
||||
<NavbarIconButton v-if="navbar_right.search" label="Open search panel" @click="isSearchPanelOpen = true">
|
||||
<!-- SearchIcon aria-hidden="true" class="w-6 h-6" -->
|
||||
<carbon-search />
|
||||
</NavbarIconButton>
|
||||
|
||||
<!-- Notification Button -->
|
||||
<NavbarIconButton v-if="navbar_right.notify" label="Open notifications panel" @click="isNotificationsPanelOpen = true">
|
||||
<!--BellIcon aria-hidden="true" class="w-6 h-6" /-->
|
||||
<carbon-notification />
|
||||
</NavbarIconButton>
|
||||
<!-- User menu -->
|
||||
<Menu v-slot="{ open }" as="div" class="relative pr-2">
|
||||
<span> {{ navbar_right.items}} </span>
|
||||
<MenuButton
|
||||
id="user-menu"
|
||||
aria-haspopup="true"
|
||||
:aria-expanded="open ? 'true' : 'false'"
|
||||
class="relative flex items-center overflow-hidden rounded-full group focus:outline-none focus:ring"
|
||||
>
|
||||
<span class="sr-only">{{ `${navbar_right.title || 'Open menu'}` }}</span>
|
||||
<img
|
||||
class="object-cover w-8 h-8 rounded-full group-hover:opacity-90"
|
||||
src="/assets/images/jesus.jpg"
|
||||
alt="User image"
|
||||
>
|
||||
<!-- <div class="absolute right-0 p-1 bg-green-400 rounded-full top-1 animate-ping"></div>
|
||||
<div class="absolute right-0 p-1 bg-green-400 border border-white rounded-full top-1"></div> -->
|
||||
</MenuButton>
|
||||
<transition
|
||||
enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0"
|
||||
enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-out"
|
||||
leave-from-class="transform scale-100 opacity-100"
|
||||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<MenuItems
|
||||
class="absolute right-0 z-999 w-48 py-1 mt-2 origin-top-righti dark:bg-gray-600 bg-white rounded-md shadow-lg focus:outline-none"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby="user-menu"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="(itm,index) in navbar_right.items"
|
||||
v-slot="{ active }"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
>
|
||||
<span class="block text-sm dark:text-gray-300 text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-500">
|
||||
<a
|
||||
v-if="itm && itm.type === 'app_link'"
|
||||
:href="itm.link"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
class="px-4 py-2"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</a>
|
||||
<router-link
|
||||
v-if="itm && itm.type === 'router_link'"
|
||||
:title="t(itm.title)"
|
||||
:to="{ name: itm.name_to }"
|
||||
class="px-4 py-2 flex items-center"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</nav>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/*
|
||||
// https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.your-profile') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.settings') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
href="#"
|
||||
class="block px-4 py-2 text-sm text-gray-700"
|
||||
:class="{ 'bg-gray-100': active }"
|
||||
role="menuitem"
|
||||
>
|
||||
{{ t('menu.sign-out') }}
|
||||
</a>
|
||||
</MenuItem>
|
||||
*/
|
||||
import {
|
||||
computed,
|
||||
} from 'vue'
|
||||
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
|
||||
import ChevronDoubleRight from '@/icons/ChevronDoubleRight.vue'
|
||||
// import SearchIcon from '@/icons/SearchIcon.vue'
|
||||
// import BellIcon from '@/icons/BellIcon.vue'
|
||||
import Breadcrump from '@/Breadcrump.vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import NavbarIconButton from './NavbarIconButton.vue'
|
||||
import BoxMenu from '~/views/BoxMenu.vue'
|
||||
import useState from '~/hooks/useState'
|
||||
import useComponent from '~/hooks/useComponent'
|
||||
// import { NavItemType } from '~/typs'
|
||||
// import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
const { isSidebarOpen, isSearchPanelOpen, isSettingsPanelOpen, toggleSidebar, isNotificationsPanelOpen, bcPath, navTitle } = useState()
|
||||
const { t } = useI18n()
|
||||
|
||||
const authData = computed(() => auth_data())
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const user_image = computed(() => {
|
||||
const image = defs.value.profile && defs.value.profile.image ? defs.value.profile.image : ''
|
||||
if (image === '') {
|
||||
return '/assets/images/user.jpg'
|
||||
}
|
||||
if (image.charAt(0) === '/' || image.includes('http')) {
|
||||
return image
|
||||
} else {
|
||||
return `/assets/images/${image}`
|
||||
}
|
||||
})
|
||||
|
||||
const useSettings = computed(() => Object.entries(useComponent().settingsComponent.value).length !== 0)
|
||||
|
||||
const navbar_right = computed(() => {
|
||||
return defs.value && defs.value.header && defs.value.header.navbar && defs.value.header.navbar.menuright
|
||||
? defs.value.header.navbar.menuright
|
||||
: { title: '', notify: false, search: false, items: [] }
|
||||
})
|
||||
|
||||
const bookSelec = (target: string) => {
|
||||
switch (target) {
|
||||
case 'home':
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
router.push('/')
|
||||
break
|
||||
default:
|
||||
useState().bookSelec(target)
|
||||
}
|
||||
}
|
||||
const on_logo_app = () => {
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
useState().isSidebarOpen.value = false
|
||||
if (useState().app_home_click.value) {
|
||||
useState().app_home_click.value()
|
||||
}
|
||||
router.push('/')
|
||||
}
|
||||
const onNavTitleMenuOption = (data: any) => {
|
||||
if (navTitle.value.cllbck)
|
||||
navTitle.value.cllbck(data)
|
||||
}
|
||||
const onNavTitleClick = () => {
|
||||
if (navTitle.value.textclick)
|
||||
navTitle.value.textclick()
|
||||
}
|
||||
</script>
|
45
src/components/panels/AsidePanel.vue
Normal file
45
src/components/panels/AsidePanel.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<Panel :show="show" :title="title" id="aside" @close="close">
|
||||
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
|
||||
<keep-alive>
|
||||
<component :is="asideComponent" />
|
||||
</keep-alive>
|
||||
<slot />
|
||||
</div>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Panel from './Panel.vue'
|
||||
import useComponent, { DynComponent } from '~/hooks/useComponent'
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => '',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const cmpnt = useComponent()
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
emit('close')
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
const { t, locale } = useI18n()
|
||||
const asideComponent = computed(() => cmpnt.asideComponent.value)
|
||||
</script>
|
40
src/components/panels/NotificationsPanel.vue
Normal file
40
src/components/panels/NotificationsPanel.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<Panel :show="show" :title="t(title || 'notifications.notifications')" id="notifycation" @close="close">
|
||||
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
|
||||
<span>{{ t('notifications.content') }}</span>
|
||||
<!-- Notifications Panel Content ... -->
|
||||
</div>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Panel from './Panel.vue'
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => '',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
const { t, locale } = useI18n()
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
emit('close')
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
73
src/components/panels/Panel.vue
Normal file
73
src/components/panels/Panel.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<!-- Backdrop -->
|
||||
<Backdrop :show="show" :blur="backdrop_blur" @close="close" />
|
||||
|
||||
<!-- Panel -->
|
||||
<transition
|
||||
enter-active-class="transition duration-300 ease-in-out transform"
|
||||
:enter-from-class="left ? '-translate-x-full' : 'translate-x-full'"
|
||||
:enter-to-class="left ? '-translate-x-0' : 'translate-x-0'"
|
||||
leave-active-class="transition duration-300 ease-in-out transform"
|
||||
:leave-from-class="left ? '-translate-x-0' : 'translate-x-0'"
|
||||
:leave-to-class="left ? '-translate-x-full' : 'translate-x-full'"
|
||||
>
|
||||
<section
|
||||
v-if="show"
|
||||
:aria-labelledby="title"
|
||||
class="fixed z-999 border-left-2 border-gray-400 max-w-xs bg-white sm:max-w-md ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-cool-gray-800 dark:text-white"
|
||||
:class="`${left ? 'left-0' : 'right-0'} bg-opacity-${back_opacity} ${panel_style}`"
|
||||
>
|
||||
<div class="flex items-center justify-between flex-shrink-0 p-2">
|
||||
<h6 class="p-2 text-lg">
|
||||
{{ title }}
|
||||
</h6>
|
||||
<!-- Close button -->
|
||||
<button class="p-2 rounded-md focus:outline-none focus:ring dark:bg-cool-gray-600" @click="close">
|
||||
<CloseIcon class="w-6 h-6 text-gray-600 dark:text-white" />
|
||||
</button>
|
||||
</div>
|
||||
<!-- Panel content -->
|
||||
<slot />
|
||||
</section>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Backdrop from '../global/Backdrop.vue'
|
||||
import CloseIcon from '../icons/CloseIcon.vue'
|
||||
import useState from '~/hooks/useState'
|
||||
|
||||
const props = defineProps<{
|
||||
show: {
|
||||
type: boolean,
|
||||
required: true,
|
||||
},
|
||||
left: {
|
||||
type: boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: string,
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: string,
|
||||
required: true,
|
||||
},
|
||||
}>()
|
||||
|
||||
const backdrop_blur = useState().backdrop_blur
|
||||
const back_opacity = useState().back_opacity
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const panel_style = computed(() => {
|
||||
const key: any = props.id || ''
|
||||
return useState().panels.value[key] && useState().panels.value[key].style ? useState().panels.value[key].style : 'inset-y-0 w-full'
|
||||
})
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
51
src/components/panels/SearchPanel.vue
Normal file
51
src/components/panels/SearchPanel.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Panel :show="show" :title="t(title || 'search.search')" id="search" @close="close">
|
||||
<div class="flex-1 max-h-full p-4 space-y-4 overflow-hidden hover:overflow-y-auto dark:text-gray-600 dark:text-white">
|
||||
<form @submit.prevent="close" >
|
||||
<input
|
||||
type="text"
|
||||
v-model="search"
|
||||
:placeholder="`${t('search.search')}...`"
|
||||
class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring dark:text-gray-600 dark:text-white"
|
||||
>
|
||||
</form>
|
||||
<div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Panel from './Panel.vue'
|
||||
import useState from '~/hooks/useState'
|
||||
|
||||
const search = useState().search
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => '',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
const { t, locale } = useI18n()
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
emit('close')
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
47
src/components/panels/SettingsPanel.vue
Normal file
47
src/components/panels/SettingsPanel.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<Panel :show="show" :title="title || t('settings.settings')" id="setting" @close="close">
|
||||
<div class="flex-1 max-h-full p-4 overflow-hidden hover:overflow-y-auto">
|
||||
<keep-alive>
|
||||
<component :is="currentComponent" />
|
||||
</keep-alive>
|
||||
<slot />
|
||||
<!-- Settings Panel Content ... -->
|
||||
</div>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Panel from './Panel.vue'
|
||||
import useComponent, { DynComponent } from '~/hooks/useComponent'
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => '',
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const cmpnt = useComponent()
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
const { t, locale } = useI18n()
|
||||
const currentComponent = computed(() => cmpnt.settingsComponent.value)
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
emit('close')
|
||||
break
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
170
src/components/sidebar/Sidebar.vue
Normal file
170
src/components/sidebar/Sidebar.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<Backdrop :show="isSidebarOpen" :blur="backdrop_blur" class="lg:hidden" @close="isSidebarOpen = false" />
|
||||
<aside
|
||||
class="fixed inset-y-0 z-999 flex flex-col flex-shrink-0 w-64 max-h-screen transition-all transform border-r dark:border-gray-700 border-gray-200 bg-white shadow-lg lg:z-auto lg:static lg:shadow-none"
|
||||
:class="{ '-translate-x-full lg:translate-x-0 lg:w-20': !isSidebarOpen }"
|
||||
>
|
||||
<!-- sidebar header -->
|
||||
<SidebarHeader />
|
||||
<!-- Sidebar links -->
|
||||
<nav aria-label="Main" class="dark:bg-cool-gray-800 dark:text-white bg-white flex-1 overflow-hidden hover:overflow-y-auto">
|
||||
<!-- Sidebar Links... -->
|
||||
<ul class="p-2">
|
||||
<li
|
||||
v-for="(itm,index) in sidebarMenuItems"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
dark:border-gray-700
|
||||
border-gray-200
|
||||
>
|
||||
<hr v-if="itm.type === NavItemType.separator" class=" dark:border-gray-600 border-gray-300">
|
||||
<router-link
|
||||
v-if="itm.type === NavItemType.router_link"
|
||||
:title="t(itm.title)"
|
||||
:to="{ name: itm.name_to }"
|
||||
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
|
||||
:class="{ 'justify-center': !isSidebarOpen }"
|
||||
>
|
||||
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode||''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
|
||||
</router-link>
|
||||
<span
|
||||
v-if="itm.type === NavItemType.module_label"
|
||||
class="border-bottom-1 dark:border-gray-700 border-gray-200 flex text-xs color-gray-100"
|
||||
:class="isSidebarOpen ? 'justify-end' : 'justify-start'"
|
||||
>
|
||||
<span class="float-right text-gray-400" :class="{ 'lg:hidden': !isSidebarOpen }"> {{ t(`menu.${itm.label}`,'') }}</span>
|
||||
</span>
|
||||
<a
|
||||
v-if="itm.type == NavItemType.a_blank"
|
||||
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
|
||||
:class="{ 'justify-center': !isSidebarOpen }"
|
||||
@click="itemClick(itm.click || '')"
|
||||
>
|
||||
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode || ''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
|
||||
</a>
|
||||
<a
|
||||
v-if="itm.type == NavItemType.a_link"
|
||||
rel="noreferrer"
|
||||
:href="itm.href || '#'"
|
||||
target="_blank"
|
||||
:title="t(itm.title)"
|
||||
>
|
||||
<span v-if="itm.icon_on === 'carbon-launch'"> <carbon-launch /> </span>
|
||||
<span class="ml-2" :class="{ 'lg:hidden': !isSidebarOpen }"> {{ t(itm.title) }}</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="itm.type == NavItemType.app_link"
|
||||
:title="t(itm.title)"
|
||||
class="flex items-center p-2 overflow-hidden rounded-md hover:bg-gray-100 dark:hover:bg-gray-500"
|
||||
:class="{ 'justify-center': !isSidebarOpen, 'ml-5': itm.name_to && itm.name_to === 'items' }"
|
||||
@click.prevent="appItemClick(itm)"
|
||||
>
|
||||
<IconLink :typ="itm.type" :icon="itm.icon_on" :mode="itm.mode || ''" :open="isSidebarOpen" :pfx="itm.pfx" :title="itm.title" :show_to="itm.show_to" :name="itm.name_to" />
|
||||
</a>
|
||||
<a
|
||||
v-if="itm.type == NavItemType.cloud_link && !isSidebarOpen && itm.name_to && itm.name_to !== 'items'"
|
||||
:title="t(itm.title)"
|
||||
class="flex items-center justify-center p-2 overflow-hidden rounded-md hover:bg-gray-300 dark:hover:text-gray-100 dark:hover:bg-gray-500"
|
||||
@click.prevent="appItemClick(itm)"
|
||||
>
|
||||
<span v-if="itm.name_to && itm.name_to !== 'cloud'" class="flex -ml-2 text-gray-500 dark:text-gray-500 dark:hover:text-gray-100 dark:hover:bg-gray-500">
|
||||
<span v-if="isSidebarOpen && itm.pfx" class="-ml-2 mr-2 font-light">
|
||||
<small class="text-gray-400">{{ itm.pfx }}</small>
|
||||
</span>
|
||||
<span class="ml-2 text-xs font-light"> {{ itm.name || '' }}</span>
|
||||
</span>
|
||||
<span v-else class="pt-1 pb-1 border-b border-t border-gray-200 dark:border-gray-500">
|
||||
<span v-if="itm.icon_on === 'carbon-cloud'" class="flex justify-center items-center"> <carbon-cloud /> </span>
|
||||
<span class="text-xs flex justify-center items-center "> {{ itm.name || '' }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<span @click="on_logo_app" class="cursor-pointer text-xl font-semibold tracking-wider uppercase dark:bg-cool-gray-800 dark:text-white bg-white">
|
||||
<div class="flex">
|
||||
<img class="-mt-1 h-11 w-auto rounded" src="/assets/images/logo.svg" alt="App">
|
||||
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-4 mt-2 text-gray-400">{{ t('app.title','') }}</span>
|
||||
</div>
|
||||
</span>
|
||||
<SidebarFooter />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Backdrop from '@/global/Backdrop.vue'
|
||||
import IconLink from '@/IconLink.vue'
|
||||
import SidebarFooter from './SidebarFooter.vue'
|
||||
import SidebarHeader from './SidebarHeader.vue'
|
||||
import useState from '~/hooks/useState'
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import { NavItemType } from '~/typs'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const { isSidebarOpen } = useState()
|
||||
const { t } = useI18n()
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const backdrop_blur = useState().backdrop_blur
|
||||
|
||||
const sidebarMenuItems = computed((): SideMenuItemType[] => {
|
||||
const ctx = router.currentRoute.value.meta.ctx || ''
|
||||
const defsMenuItems = defs.value && defs.value.sidebar && defs.value.sidebar.menu_items ? defs.value.sidebar.menu_items : [] as SideMenuItemType[]
|
||||
const all_items = [].concat(defsMenuItems || []) // .concat(useState().sidebarMenuItems.value as SideMenuItemType[] | any)
|
||||
useState().sidebarMenuItems.value = all_items.filter((itm: SideMenuItemType) => {
|
||||
if (itm.ctx && itm.ctx === ctx) {
|
||||
return true
|
||||
} else if (itm.ctx && itm.ctx !== ctx) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return useState().sidebarMenuItems.value
|
||||
})
|
||||
|
||||
const itemClick = (target: string) => {
|
||||
switch (target) {
|
||||
case 'home':
|
||||
useState().bcPath.value = ''
|
||||
// useState().navTitle.value = ''
|
||||
router.push('/')
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
const appItemClick = (itm: SideMenuItemType) => {
|
||||
if (itm.cllbck) {
|
||||
itm.cllbck(itm)
|
||||
isSidebarOpen.value = false
|
||||
} else {
|
||||
if (useState().side_menu_click.value) {
|
||||
try {
|
||||
useState().side_menu_click.value(itm)
|
||||
isSidebarOpen.value.value = false
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
} else if (itm.click === 'tophome') {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
const on_logo_app = () => {
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
useState().isSidebarOpen.value = false
|
||||
if (useState().app_home_click.value) {
|
||||
useState().app_home_click.value()
|
||||
}
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
62
src/components/sidebar/SidebarFooter.vue
Normal file
62
src/components/sidebar/SidebarFooter.vue
Normal file
@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="flex-shrink-0 p-4 border-t dark:border-gray-700 border-gray-200 max-h-14 dark:bg-cool-gray-800 dark:text-white">
|
||||
<Button
|
||||
v-if="sidebarFooter.itm.type === NavItemType.a_link"
|
||||
class="flex w-full text-gray-700 bg-gray-100 hover:bg-gray-700 dark:bg-cool-gray-800 dark:text-gray-100 dark:hover:bg-gray-700"
|
||||
:class="{'justify-start': isSidebarOpen}"
|
||||
@click="itemClick(sidebarFooter.itm.click || '')"
|
||||
>
|
||||
<!-- LogoutIcon aria-hidden="true" class="w-6 h-6" -->
|
||||
<span v-if="sidebarFooter.itm.icon_on === 'carbon-logout'"> <carbon-logout /> </span>
|
||||
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-2"> {{ t(sidebarFooter.itm.title) }} </span>
|
||||
</Button>
|
||||
<router-link
|
||||
v-if="sidebarFooter.itm.type === NavItemType.router_link"
|
||||
:to="{ name: `${sidebarFooter.itm.name_to === 'Logout' && authData.auth === '' ? 'Login' : 'Logout'}` }"
|
||||
class="flex w-full text-gray-700 bg-gray-100 dark:bg-cool-gray-800 dark:text-gray-100 dark:hover:bg-gray-700"
|
||||
:class="{'justify-start': isSidebarOpen}"
|
||||
>
|
||||
<span v-if="sidebarFooter.itm.icon_on === 'carbon-logout' && authData.auth !== ''" class="mt-1"> <carbon-logout /> </span>
|
||||
<span v-else class="mt-1"> <carbon-login /> </span>
|
||||
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-2"> {{ t(sidebarFooter.itm.title) }} </span>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from '@/global/Button.vue'
|
||||
// import LogoutIcon from '@/icons/LogoutIcon.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useState from '~/hooks/useState'
|
||||
import { NavItemType } from '~/typs'
|
||||
import { auth_data } from '~/hooks/utils'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const { isSidebarOpen } = useState()
|
||||
const { t } = useI18n()
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const authData = computed(() => auth_data())
|
||||
|
||||
const sidebarFooter = computed(() => {
|
||||
return defs.value && defs.value.sidebar && defs.value.sidebar.footer ? defs.value.sidebar.footer : { itm: {} }
|
||||
})
|
||||
|
||||
const itemClick = (target: string) => {
|
||||
switch (target) {
|
||||
case 'home':
|
||||
useState().bcPath.value = ''
|
||||
useState().navTitle.value = {} as any
|
||||
router.push('/')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
58
src/components/sidebar/SidebarHeader.vue
Normal file
58
src/components/sidebar/SidebarHeader.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between flex-shrink-0 p-2 dark:bg-cool-gray-800 dark:text-white" :class="{ 'lg:justify-center': !isSidebarOpen }">
|
||||
<span
|
||||
class="cursor-pointer text-lg lg:text-xl font-semibold leading-8 tracking-wider uppercase whitespace-nowrap dark:bg-cool-gray-800 dark:text-white"
|
||||
@click="on_logo_app"
|
||||
>
|
||||
<div class="flex">
|
||||
<img class="h-11 w-auto rounded" :src="`${header.logo && header.logo !== '' ? header.logo : '/assets/images/app_w.svg'}`" alt="App">
|
||||
<span :class="{ 'lg:hidden': !isSidebarOpen }" class="ml-4 mt-2 text-gray-400">{{ header.title || '' }}</span>
|
||||
</div>
|
||||
</span>
|
||||
<button class="rounded-md lg:hidden" @click="isSidebarOpen = false">
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useState from '~/hooks/useState'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
const { isSidebarOpen } = useState()
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const header = computed(() => {
|
||||
return defs.value && defs.value.header ? defs.value.header : {}
|
||||
})
|
||||
|
||||
const on_logo_app = () => {
|
||||
useState().bcPath.value = ''
|
||||
useState().dfltNavTitle()
|
||||
useState().isSidebarOpen.value = false
|
||||
if (useState().app_home_click.value) {
|
||||
useState().app_home_click.value()
|
||||
}
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
</script>
|
97
src/hooks/useComponent.ts
Normal file
97
src/hooks/useComponent.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
// import WysiwygEditor from '@/WysiwygEditor.vue'
|
||||
// // import CodeEditor from '@/CodeEditor.vue'
|
||||
// import GridSettings from '~/views/GridSettings.vue'
|
||||
// import GridView from '~/views/GridView.vue'
|
||||
// import TableView from '~/views/TableView.vue'
|
||||
// import ListView from '~/views/ListView.vue'
|
||||
// import FormView from '~/views/FormView.vue'
|
||||
|
||||
// import TaFormView from '/app_modules/bm/ta/views/ta_form.vue'
|
||||
// import TaTableView from '/app_modules/bm/ta/views/ta_table.vue'
|
||||
// import TaListView from '/app_modules/bm/ta/views/ta_list.vue'
|
||||
|
||||
export enum DynComponent {
|
||||
// GridSettings,
|
||||
// GridJs,
|
||||
// TableView,
|
||||
// ListView,
|
||||
// FormView,
|
||||
// WysiwygEditor,
|
||||
// CodeEditor,
|
||||
}
|
||||
|
||||
const asideComponent = ref({})
|
||||
|
||||
const settingsComponent = ref({})
|
||||
|
||||
const fullSliderComponent = ref({})
|
||||
|
||||
const formViewComponent = ref({})
|
||||
|
||||
const dataViewComponent = ref({})
|
||||
|
||||
const topPaneComponent = ref({})
|
||||
|
||||
const bottomPaneComponent = ref({})
|
||||
|
||||
const moduleComponent = ref({})
|
||||
|
||||
const getModuleComponent = (key: string, target: string): any => {
|
||||
// switch (key) {
|
||||
// case 'ta':
|
||||
// switch (target) {
|
||||
// case 'form':
|
||||
// return TaFormView
|
||||
// break
|
||||
// case 'table':
|
||||
// return TaTableView
|
||||
// break
|
||||
// case 'list':
|
||||
// return TaListView
|
||||
// break
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
}
|
||||
const getComponent = (cmpnt: DynComponent): any => {
|
||||
// switch (cmpnt) {
|
||||
// case DynComponent.WysiwygEditor:
|
||||
// return WysiwygEditor
|
||||
// break
|
||||
// // case DynComponent.CodeEditor:
|
||||
// // return CodeEditor
|
||||
// // break
|
||||
// case DynComponent.GridSettings:
|
||||
// return GridSettings
|
||||
// break
|
||||
// case DynComponent.GridJs:
|
||||
// return GridView
|
||||
// break
|
||||
// case DynComponent.TableView:
|
||||
// return TableView
|
||||
// break
|
||||
// case DynComponent.ListView:
|
||||
// return ListView
|
||||
// break
|
||||
// case DynComponent.FormView:
|
||||
// return FormView
|
||||
// break
|
||||
// }
|
||||
}
|
||||
|
||||
export default function useComponent() {
|
||||
return {
|
||||
getModuleComponent,
|
||||
asideComponent,
|
||||
settingsComponent,
|
||||
fullSliderComponent,
|
||||
dataViewComponent,
|
||||
formViewComponent,
|
||||
getComponent,
|
||||
topPaneComponent,
|
||||
bottomPaneComponent,
|
||||
moduleComponent,
|
||||
}
|
||||
}
|
80
src/hooks/useData.js
Normal file
80
src/hooks/useData.js
Normal file
@ -0,0 +1,80 @@
|
||||
const roles = ['Admin', 'Editor', 'User']
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: 'c27eb2bd-13d3-4231-aa60-e392b5f60d2e',
|
||||
name: 'Agnes Bogisich',
|
||||
email: 'Drake61@hotmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 'baca6dbf-4efe-4135-b584-5123dc6e1efa',
|
||||
name: 'Damaris Huels',
|
||||
email: 'Alayna.Rohan@yahoo.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 'ac991968-7f22-41e4-84ba-e62a335607c7',
|
||||
name: 'Monique Kozey',
|
||||
email: 'Louisa_Emard@gmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: '2b353eda-d96a-47b0-aaed-1871b257abd1',
|
||||
name: 'Kayden Collier',
|
||||
email: 'Rosina71@yahoo.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: '697fbe33-fe86-45fc-93e3-bd4335fe4063',
|
||||
name: 'Kiera Baumbach',
|
||||
email: 'Ashleigh10@hotmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: '0dffdd8d-84f4-4b87-8832-c6f560ebe850',
|
||||
name: 'Sage Dietrich',
|
||||
email: 'Ramona70@gmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
id: '46d0a8b6-b2c2-468e-b6d9-89c8520dccfe',
|
||||
name: 'Jodie Jones',
|
||||
email: 'Lempi89@gmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 'c8d54e36-f6e8-4c8f-b43a-4228ab11d7a9',
|
||||
name: 'Blaze Reilly',
|
||||
email: 'Dakota_Casper@yahoo.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: '47e143af-9e03-4c7c-9fe1-99f3374841cb',
|
||||
name: 'Margie Douglas',
|
||||
email: 'Jadyn.Ernser@yahoo.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
id: 'd40ff313-4a69-4a70-9fc3-398d51e53114',
|
||||
name: 'Arianna Kilback',
|
||||
email: 'Fritz.Tremblay@gmail.com',
|
||||
role: roles[Math.round(Math.random() * (roles.length - 1))],
|
||||
active: false,
|
||||
},
|
||||
]
|
||||
|
||||
export default function useData() {
|
||||
return {
|
||||
users,
|
||||
}
|
||||
}
|
127
src/hooks/useState.ts
Normal file
127
src/hooks/useState.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import type { SideMenuItemType, UiPanelsType } from '~/typs/cmpnts'
|
||||
|
||||
const reqError = ref({ defs: '', lang: '', data: '', gql: '', api: '' })
|
||||
|
||||
const currentMapKey = ref('')
|
||||
const isSidebarOpen = ref(false)
|
||||
const toggleSidebar = () => {
|
||||
isSidebarOpen.value = !isSidebarOpen.value
|
||||
}
|
||||
const showModal = ref(false)
|
||||
|
||||
const isAsidePanelOpen = ref(false)
|
||||
|
||||
const sideSettingButton = ref(false)
|
||||
|
||||
const isSettingsPanelOpen = ref(false)
|
||||
|
||||
const isSearchPanelOpen = ref(false)
|
||||
const usideSettingButton = ref(false)
|
||||
|
||||
const isNotificationsPanelOpen = ref(false)
|
||||
|
||||
const useSettings = ref(false)
|
||||
|
||||
const useSearch = ref(false)
|
||||
|
||||
const search = ref('')
|
||||
|
||||
const bcPath = ref('')
|
||||
|
||||
const navTitle = ref({
|
||||
text: '',
|
||||
textclick: null as null | Function,
|
||||
title: '',
|
||||
cmpnt: '',
|
||||
ops: [] as any[],
|
||||
btntype: '',
|
||||
cllbck: null as null | Function,
|
||||
})
|
||||
const dfltNavTitle = () => {
|
||||
navTitle.value = {
|
||||
text: '',
|
||||
textclick: null as null | Function,
|
||||
title: '',
|
||||
cmpnt: '',
|
||||
ops: [] as any[],
|
||||
btntype: '',
|
||||
cllbck: null as null | Function,
|
||||
}
|
||||
}
|
||||
// '#fa-book/biomagnetismo/microorganismos/virus'
|
||||
|
||||
const bookCllbck = ref()
|
||||
const bookSelec = (data: string) => {
|
||||
if (data === '#') {
|
||||
bcPath.value = ''
|
||||
dfltNavTitle()
|
||||
}
|
||||
else {
|
||||
if (bookCllbck.value)
|
||||
bookCllbck.value(data)
|
||||
// else ...
|
||||
}
|
||||
}
|
||||
const sidebarMenuItems = ref([] as SideMenuItemType[])
|
||||
|
||||
const checkin = ref(false)
|
||||
const connection = ref({
|
||||
state: '',
|
||||
})
|
||||
|
||||
const tsksrvcs = 'tsksrvcs'
|
||||
const appsrvcs = 'appsrvcs'
|
||||
const srvcstatus = 'srvcstatus'
|
||||
|
||||
// const menu_items = ref([
|
||||
// { id: tsksrvcs, title: 'Tasks', active: false, link: '#' },
|
||||
// { id: appsrvcs, title: 'Applications', active: false, link: '#' },
|
||||
// { id: srvcstatus, title: 'Informations', active: false, link: '#' },
|
||||
// ])
|
||||
|
||||
const side_menu_click = ref()
|
||||
const app_home_click = ref()
|
||||
|
||||
const backdrop_blur = ref(14)
|
||||
const back_opacity = ref(60)
|
||||
const panels = ref({} as UiPanelsType)
|
||||
|
||||
const show_profile = ref(false)
|
||||
|
||||
export default function useState() {
|
||||
return {
|
||||
reqError,
|
||||
currentMapKey,
|
||||
isSidebarOpen,
|
||||
toggleSidebar,
|
||||
isAsidePanelOpen,
|
||||
isSettingsPanelOpen,
|
||||
isSearchPanelOpen,
|
||||
isNotificationsPanelOpen,
|
||||
useSettings,
|
||||
usideSettingButton,
|
||||
sideSettingButton,
|
||||
bcPath,
|
||||
bookSelec,
|
||||
bookCllbck,
|
||||
navTitle,
|
||||
dfltNavTitle,
|
||||
checkin,
|
||||
connection,
|
||||
sidebarMenuItems,
|
||||
showModal,
|
||||
tsksrvcs,
|
||||
appsrvcs,
|
||||
srvcstatus,
|
||||
// menu_items,
|
||||
side_menu_click,
|
||||
app_home_click,
|
||||
useSearch,
|
||||
search,
|
||||
back_opacity,
|
||||
backdrop_blur,
|
||||
panels,
|
||||
show_profile,
|
||||
}
|
||||
}
|
339
src/hooks/utils.ts
Normal file
339
src/hooks/utils.ts
Normal file
@ -0,0 +1,339 @@
|
||||
// import {
|
||||
// FixedAdjust,
|
||||
// DenseFixedAdjust,
|
||||
// ProminentFixedAdjust,
|
||||
// ShortFixedAdjust,
|
||||
// } from "@smui/top-app-bar";
|
||||
|
||||
import Toastify from 'toastify-js'
|
||||
import store from '~/store'
|
||||
import useState from '~/hooks/useState'
|
||||
import { AppDefsAction } from '~/store/types'
|
||||
import { MessageType } from '~/typs'
|
||||
import 'toastify-js/src/toastify.css'
|
||||
const { connection } = useState()
|
||||
|
||||
// import { postData, sendData } from './api.js'
|
||||
|
||||
// https://www.carlrippon.com/fetch-with-async-await-and-typescript/
|
||||
|
||||
/*
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-Id': uuidv4(),
|
||||
'Authorization': `Bearer <API_TOKEN>`
|
||||
}
|
||||
*/
|
||||
// https://github.com/Maronato/vue-toastification#installation
|
||||
|
||||
export const show_message = (typ: MessageType, msg: string, timeout?: number): void => {
|
||||
// switch (typ) {
|
||||
// case MessageType.Show:
|
||||
// toast.show(msg)
|
||||
// break
|
||||
// case MessageType.Success:
|
||||
// toast.success(msg)
|
||||
// break
|
||||
// case MessageType.Error:
|
||||
// toast.error(msg)
|
||||
// break
|
||||
// case MessageType.Warning:
|
||||
// toast.warning(msg)
|
||||
// break
|
||||
// case MessageType.Info:
|
||||
// toast.info(msg)
|
||||
// break
|
||||
// }
|
||||
// if (timeout && timeout > 0)
|
||||
// setTimeout(toast.clear, timeout)
|
||||
// https://github.com/apvarun/toastify-js
|
||||
Toastify({
|
||||
text: msg,
|
||||
duration: timeout || 3000,
|
||||
destination: 'https://github.com/apvarun/toastify-js',
|
||||
newWindow: true,
|
||||
className: '',
|
||||
offset: {
|
||||
x: 10, // horizontal axis - can be a number or a string indicating unity. eg: '2em'
|
||||
y: 50, // vertical axis - can be a number or a string indicating unity. eg: '2em
|
||||
},
|
||||
close: true,
|
||||
gravity: 'top', // `top` or `bottom`
|
||||
position: 'left', // `left`, `center` or `right`
|
||||
backgroundColor: 'linear-gradient(to right, #00b09b, #96c93d)',
|
||||
stopOnFocus: true, // Prevents dismissing of toast on hover
|
||||
onClick() {}, // Callback after click
|
||||
}).showToast()
|
||||
}
|
||||
|
||||
export const translate = (store: any, map: string, ky: string, ctx: string, dflt?: string): string => {
|
||||
const val = dflt || ky
|
||||
const lang = store.state.app_lang.lang.get(map)
|
||||
// switch (ctx) {
|
||||
// case 'main':
|
||||
// val = lang.value.main && lang.value.main[ky] ? lang.value.main[ky] : val
|
||||
// break
|
||||
// case 'data':
|
||||
// val = lang.value.main && lang.value.main[ky] ? lang.value.main[ky] : val
|
||||
// val = lang.value.data[ky] || val
|
||||
// break
|
||||
// case 'forms':
|
||||
// val = lang.value.forms && lang.value.forms[ky] ? lang.value.forms[ky] : val
|
||||
// break
|
||||
// }
|
||||
if (lang && lang.value)
|
||||
return lang.value[ctx] && lang.value[ctx][ky] ? lang.value[ctx][ky] : val
|
||||
else if (lang && lang[ctx])
|
||||
return lang[ctx][ky] ? lang[ctx][ky] : val
|
||||
else
|
||||
return val
|
||||
}
|
||||
|
||||
export const senddata = async<T>(url: RequestInfo, data: BlobPart, file_name?: string, cllbck?: any): Promise<any> => {
|
||||
const formData = new FormData()
|
||||
// formData.append('blob', new Blob([JSON.stringify(rowData)]), 'test')
|
||||
// let response: Ref < T | undefined > = ref()
|
||||
|
||||
formData.append('blob', new Blob([data]), file_name)
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
mode: 'no-cors', // 'cors' by default
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
return response
|
||||
}
|
||||
catch (err) {
|
||||
if (cllbck)
|
||||
cllbck(err)
|
||||
else
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
export const postdata = (url: string, data: any, file_name: string, cllbck?: any): void => {
|
||||
if (data && file_name) {
|
||||
senddata(url, data, file_name, cllbck).then((result: any) => {
|
||||
if (result.status !== 0) {
|
||||
console.log(
|
||||
// throw Error(
|
||||
`Data sent to ${url} (${file_name}) - Network response NOT OK`,
|
||||
)
|
||||
}
|
||||
else {
|
||||
console.log(
|
||||
`Save data to ${url} (${file_name}) status: ${result.status}`,
|
||||
)
|
||||
}
|
||||
if (cllbck)
|
||||
cllbck(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
export const fetch_text = async(path: RequestInfo): Promise<any> => {
|
||||
try {
|
||||
const response = await fetch(path)
|
||||
return !response.ok ? response.text() : new Error('No items found')
|
||||
}
|
||||
catch (err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
export const fetch_json = async(path: RequestInfo, timeout = 2000): Promise<any> => {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const response = await self.fetch(path)
|
||||
setTimeout(async() => {
|
||||
resolve(await response.json())
|
||||
}, timeout)
|
||||
}
|
||||
catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const load_data = async(data_path: RequestInfo, ato: number, timeout = 2000, cllbck?: any) => {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
// return await new Promise(async(resolve, reject) => {
|
||||
useState().connection.value.state = ''
|
||||
const token = localStorage.getItem('auth') || ''
|
||||
// if (token === '')
|
||||
// return `error no auth for ${data_path}`
|
||||
|
||||
try {
|
||||
const headers = token !== "" ? { Authorization: `Bearer ${token}`} : {}
|
||||
// 'content-type': 'application/json',
|
||||
const res = await self.fetch(data_path, {
|
||||
headers
|
||||
})
|
||||
// setTimeout(async() => {
|
||||
let dataloaded = {}
|
||||
try {
|
||||
let strData = await res.text()
|
||||
for (let i = 0; i < ato; i++)
|
||||
strData = atob(strData)
|
||||
dataloaded = JSON.parse(strData)
|
||||
}
|
||||
catch (e) {
|
||||
return `error ${e} with ${data_path}`
|
||||
}
|
||||
if (Object.keys(dataloaded).length) {
|
||||
if (cllbck)
|
||||
cllbck(dataloaded)
|
||||
return ''
|
||||
}
|
||||
else {
|
||||
return new Error('No definitions found')
|
||||
}
|
||||
// }, timeout)
|
||||
}
|
||||
catch (err) {
|
||||
show_message(MessageType.Error, `'Data Load' -> ${err}`)
|
||||
useState().connection.value.state = 'connection.error'
|
||||
return err
|
||||
}
|
||||
// })
|
||||
}
|
||||
export const post_data = async(url: string, formData: any, with_auth = true) => {
|
||||
connection.value.state = ''
|
||||
let headers = {}
|
||||
if (with_auth) {
|
||||
const token = localStorage.getItem('auth') || ''
|
||||
if (token === '')
|
||||
return `error no auth for ${url}`
|
||||
headers = {
|
||||
'content-type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
// 'Accept': 'application/json',
|
||||
}
|
||||
}
|
||||
else {
|
||||
headers = { 'content-type': 'application/json' }
|
||||
}
|
||||
let formDataJsonString = ''
|
||||
try {
|
||||
formDataJsonString = JSON.stringify(formData)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formDataJsonString,
|
||||
})
|
||||
if (!response.ok && response.status !== 200) {
|
||||
const errorMessage = await response.text()
|
||||
console.log(errorMessage)
|
||||
// throw new Error(errorMessage)
|
||||
}
|
||||
if (response.ok && response.status === 200)
|
||||
return response.json()
|
||||
}
|
||||
catch (e) {
|
||||
connection.value.state = 'connection.error'
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
export const check_credentials = async(url: string, data: any): Promise<any> => {
|
||||
let dataJsonString = ''
|
||||
try {
|
||||
dataJsonString = JSON.stringify(data)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: dataJsonString,
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorMessage = await response.text()
|
||||
// throw new Error(errorMessage)
|
||||
console.log(errorMessage)
|
||||
return
|
||||
}
|
||||
if (response.ok && response.status === 200)
|
||||
return response.json()
|
||||
}
|
||||
catch (e) {
|
||||
useState().connection.value.state = 'connection.error'
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
export const run_task = (val: number, task: Function) => {
|
||||
const now = new Date().getTime()
|
||||
const timePassed = now % val
|
||||
const run_at = val - timePassed
|
||||
setTimeout(task, run_at)
|
||||
}
|
||||
|
||||
export const verify_auth = async(mapkey: string, check_url: string, login_url: string) => {
|
||||
const auth = localStorage.getItem('auth') || ''
|
||||
const res = await check_credentials(check_url, { data: auth, mapkey })
|
||||
if (res && res.token === auth) {
|
||||
if (res.defs)
|
||||
store.dispatch(AppDefsAction.addDefs, { key: mapkey, defs: res.defs })
|
||||
if (res.defs.checkin && res.defs.checkin > 0) {
|
||||
run_task(res.defs.checkin, async() => {
|
||||
// console.log('run_task')
|
||||
const res_task = await verify_auth(mapkey, check_url, login_url)
|
||||
if (res_task)
|
||||
return
|
||||
if (useState().connection.value.state === '')
|
||||
location.href = login_url
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const auth_data = () => {
|
||||
const auth = localStorage.getItem('auth') || ''
|
||||
let uidefs = {}
|
||||
if (store && store.state && store.state.app_defs && store.state.app_defs.main) {
|
||||
uidefs = store.state.app_defs.main.get('ui')
|
||||
}
|
||||
return {auth, uidefs}
|
||||
}
|
||||
|
||||
export const parse_str_json_data = (src: string, dflt: object|any) => {
|
||||
let data = dflt
|
||||
try {
|
||||
data = JSON.parse(src)
|
||||
}
|
||||
catch (e) {
|
||||
data = dflt
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export default {
|
||||
fetch_text,
|
||||
fetch_json,
|
||||
postdata,
|
||||
senddata,
|
||||
load_data,
|
||||
post_data,
|
||||
// store_data,
|
||||
show_message,
|
||||
translate,
|
||||
check_credentials,
|
||||
run_task,
|
||||
verify_auth,
|
||||
auth_data,
|
||||
parse_str_json_data,
|
||||
}
|
224
src/layouts/AppLayout.vue
Normal file
224
src/layouts/AppLayout.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div v-if="isCheckin">
|
||||
<div class="spinner" style="margin: 100px auto; width: 80px; height: 80px;">
|
||||
<div class="dot1" />
|
||||
<div class="dot2" />
|
||||
</div>
|
||||
<div class="flex justify-center text-2xl">
|
||||
{{ t('message.loadding') }} ...
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>
|
||||
<div class="antialiased text-gray-900 bg-white dark:bg-gray-800 dark:text-white">
|
||||
<div class="flex h-screen overflow-y-hidden bg-white">
|
||||
<!-- Sidebar -->
|
||||
<Sidebar />
|
||||
|
||||
<div class="flex flex-col flex-1 h-full overflow-hidden">
|
||||
<!-- Navbar -->
|
||||
<header class="flex-shrink-0 border-b dark:border-gray-700 border-gray-200">
|
||||
<Navbar @on-nav-menu="onNavMenu" />
|
||||
</header>
|
||||
<!-- Main content -->
|
||||
<main class="flex-1 max-h-full overflow-hidden overflow-y-scroll dark:bg-cool-gray-800 dark:text-white">
|
||||
<slot />
|
||||
</main>
|
||||
<!-- Main footer -->
|
||||
<footer class="flex items-center justify-between flex-shrink-0 p-4 border-t dark:border-gray-700 border-gray-200 max-h-14 dark:bg-cool-gray-800 dark:text-white">
|
||||
<div class="flex">
|
||||
<span
|
||||
v-for="(itm,index) in left_items"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
>
|
||||
<a
|
||||
v-if="itm && itm.type === NavItemType.app_link"
|
||||
:href="itm.href"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</a>
|
||||
<router-link
|
||||
v-if="itm && itm.type === 'router_link'"
|
||||
:title="t(itm.title)"
|
||||
:to="{ name: itm.name_to }"
|
||||
class="small-text flex items-center text-shadow-xs"
|
||||
>
|
||||
{{ t(itm.title) }}
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<Footer />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span
|
||||
v-for="(itm,index) in right_items"
|
||||
:key="`${String(index)}-${itm.title}`"
|
||||
>
|
||||
<a
|
||||
v-if="itm && itm.type === NavItemType.app_link"
|
||||
:href="itm.href"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span class="hidden text-sm md:block mr-2"> {{ t(itm.title) }} </span>
|
||||
<span v-if="itm.img" class="w-11 h-11 mr-2">
|
||||
<app-img v-if="itm.img === 'librecloudd'" />
|
||||
<img v-else class="h-11 w-auto rounded" :src="`/assets/images/${itm.img}.svg`" alt="App">
|
||||
</span>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="itm && itm.type === 'router_link'"
|
||||
:title="t(itm.title)"
|
||||
:to="{ name: itm.name_to }"
|
||||
class="flex items-center"
|
||||
>
|
||||
<span class="hidden text-sm md:block mr-2"> {{ t(itm.title) }} </span>
|
||||
<span v-if="itm.img" class="w-11 h-11 mr-2">
|
||||
<app-img v-if="itm.img === 'librecloudd'" />
|
||||
<img v-else class="h-11 w-auto" :src="`/assets/images/${itm.img}.svg`" alt="App">
|
||||
</span>
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<!-- Setting panel button -->
|
||||
<div v-if="sideSettingButton" v-show="useSettings" class="fixed right-0 transform rotate-90 translate-x-8 top-1/2">
|
||||
<Button class="text-sm font-medium z-999 text-white uppercase bg-gray-600" @click="isSettingsPanelOpen = true">
|
||||
Settings
|
||||
</Button>
|
||||
</div>
|
||||
<SettingsPanel :show="isSettingsPanelOpen" @close="isSettingsPanelOpen = false" />
|
||||
<SearchPanel left :show="isSearchPanelOpen" @close="isSearchPanelOpen = false" />
|
||||
<NotificationsPanel left :show="isNotificationsPanelOpen" @close="isNotificationsPanelOpen = false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/*
|
||||
<suspense v-else>
|
||||
<template #default>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="space-up">
|
||||
<div class="spinner" style="margin: 100px auto; width: 80px; height: 80px;">
|
||||
<div class="dot1" />
|
||||
<div class="dot2" />
|
||||
</div>
|
||||
<div class="flex justify-center text-2xl">
|
||||
Check-in ...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</suspense>
|
||||
|
||||
<div>
|
||||
<a
|
||||
href="https://karyo.dev"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-1"
|
||||
>
|
||||
|
||||
<span class="w-11 h-11 dark:bg-gray-200 rounded-full mr-2">
|
||||
<dsc-logo />
|
||||
</span>
|
||||
<span class="hidden text-sm md:block">Z-Terton</span>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
*/
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
import Sidebar from '@/sidebar/Sidebar.vue'
|
||||
import Navbar from '@/navbar/Navbar.vue'
|
||||
import SettingsPanel from '@/panels/SettingsPanel.vue'
|
||||
import SearchPanel from '@/panels/SearchPanel.vue'
|
||||
import NotificationsPanel from '@/panels/NotificationsPanel.vue'
|
||||
import Button from '@/global/Button.vue'
|
||||
import Footer from '@/Footer.vue'
|
||||
import AppImg from '@/icons/AppImg.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from 'vuex'
|
||||
import useState from '~/hooks/useState'
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import { NavItemType } from '~/typs'
|
||||
|
||||
// import { AppDefsAction } from '~/store/types'
|
||||
// import useComponent, { DynComponent } from '~/hooks/useComponent'
|
||||
// import { check_credentials } from '~/hooks/utils'
|
||||
|
||||
//const { reqError, isSidebarOpen, isAsidePanelOpen, isSettingsPanelOpen, isSearchPanelOpen, isNotificationsPanelOpen, sideSettingButton } = useState()
|
||||
const { isSidebarOpen, isSettingsPanelOpen, isSearchPanelOpen, isNotificationsPanelOpen, sideSettingButton, useSettings } = useState()
|
||||
|
||||
const checkScreen = () => {
|
||||
if (window.innerWidth <= 1024)
|
||||
isSidebarOpen.value = false
|
||||
}
|
||||
|
||||
// const props = defineProps<{
|
||||
// appdefs: any
|
||||
// }>()
|
||||
// const { appdefs } = toRefs(props)
|
||||
|
||||
// const use_panels = true
|
||||
// const show_top_pane = ref(false)
|
||||
// const show_bottom_pane = ref(false)
|
||||
|
||||
const isCheckin = ref(true)
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const left_items = computed((): SideMenuItemType[] => {
|
||||
return defs.value && defs.value.footer && defs.value.footer.left_items ? defs.value.footer.left_items : [] as SideMenuItemType[]
|
||||
})
|
||||
const right_items = computed((): SideMenuItemType[] => {
|
||||
return defs.value && defs.value.footer && defs.value.footer.right_items ? defs.value.footer.right_items : [] as SideMenuItemType[]
|
||||
})
|
||||
|
||||
const onNavMenu = (itm: SideMenuItemType) => {
|
||||
useState().show_profile.value = true
|
||||
}
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', checkScreen)
|
||||
isCheckin.value = false
|
||||
})
|
||||
|
||||
// const topPaneComponent = computed(() => useComponent().topPaneComponent.value)
|
||||
// const bottomPaneComponent = computed(() => useComponent().bottomPaneComponent.value)
|
||||
// const root_url = router.currentRoute.value.meta.rooturl || ''
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkScreen)
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.small-text {
|
||||
font-size: 75%;
|
||||
}
|
||||
.expandable {
|
||||
background: #dadada;
|
||||
overflow: hidden;
|
||||
|
||||
transition: all .5s ease-in-out;
|
||||
line-height: 0;
|
||||
padding: 0 1em;
|
||||
color: transparent;
|
||||
}
|
||||
.expandable:target {
|
||||
line-height: 1.5;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
7
src/layouts/GuestLayout.vue
Normal file
7
src/layouts/GuestLayout.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center min-h-screen p-4 space-y-4 antialiased text-gray-900 bg-white">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
27
src/layouts/Page404.vue
Normal file
27
src/layouts/Page404.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-4 py-10 text-center text-teal-700 dark:text-gray-200">
|
||||
<div>
|
||||
<p class="text-4xl">
|
||||
<carbon-warning class="inline-block" />
|
||||
</p>
|
||||
</div>
|
||||
<router-view />
|
||||
<slot />
|
||||
<div>
|
||||
<button
|
||||
class="btn m-3 text-sm mt-8"
|
||||
@click="router.back()"
|
||||
>
|
||||
{{ t('button.back') }}
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
7
src/layouts/SimpleLayout.vue
Normal file
7
src/layouts/SimpleLayout.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="antialiased text-gray-900 bg-white">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
9
src/layouts/default.vue
Normal file
9
src/layouts/default.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<main class="px-4 py-10 text-center text-gray-700 dark:text-gray-200">
|
||||
<router-view />
|
||||
<Footer />
|
||||
<div class="mt-5 mx-auto text-center opacity-25 text-sm">
|
||||
[Default Layout]
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
30
src/layouts/home.vue
Normal file
30
src/layouts/home.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<main class="px-4 py-10 text-center text-gray-700 dark:text-gray-200">
|
||||
<router-view />
|
||||
<Footer />
|
||||
<div class="mt-5 mx-auto text-center opacity-25 text-sm">
|
||||
[Home Layout]
|
||||
</div>
|
||||
<router-link to="/datalist">
|
||||
Data List
|
||||
</router-link>
|
||||
</main>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
onBeforeRouteUpdate((to: any, from: any) => {
|
||||
})
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
// console.log(to);
|
||||
// const answer = window.confirm(
|
||||
// 'Do you really want to leave? you have unsaved changes!'
|
||||
// )
|
||||
// // cancel the navigation and stay on the same page
|
||||
// if (!answer) return false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
2
src/logic/dark.ts
Normal file
2
src/logic/dark.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const isDark = useDark()
|
||||
export const toggleDark = useToggle(isDark)
|
1
src/logic/index.ts
Normal file
1
src/logic/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './dark'
|
59
src/main.ts
Normal file
59
src/main.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// windicss layers
|
||||
import 'virtual:windi-base.css'
|
||||
import 'virtual:windi-components.css'
|
||||
// your custom styles here
|
||||
import './styles/main.css'
|
||||
// windicss utilities should be the last style import
|
||||
import 'virtual:windi-utilities.css'
|
||||
// windicss devtools support (dev only)
|
||||
import 'virtual:windi-devtools'
|
||||
|
||||
import VueAnimXYZ from '@animxyz/vue' // import AnimXZY vue package
|
||||
import '@animxyz/core'
|
||||
|
||||
// register vue composition api globally
|
||||
import { createApp } from 'vue'
|
||||
// import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { createHead, useHead } from '@vueuse/head'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
import routes from './router'
|
||||
import store from './store'
|
||||
|
||||
import 'a17t'
|
||||
|
||||
import { messages } from './modules/i18n'
|
||||
// import GuestLayout from '~/layouts/GuestLayout.vue'
|
||||
import AppLayout from '~/layouts/AppLayout.vue'
|
||||
import SimpleLayout from '~/layouts/SimpleLayout.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'es',
|
||||
legacy: false,
|
||||
fallbackLocale: ['es'],
|
||||
fallbackWarn: false,
|
||||
missing: (locale, key, instance) => {
|
||||
console.warn(`detect '${key}' key missing in '${locale}'`)
|
||||
},
|
||||
messages,
|
||||
})
|
||||
|
||||
const head = createHead()
|
||||
// const router = createRouter({
|
||||
// history: createWebHistory(),
|
||||
// routes,
|
||||
// })
|
||||
app.use(VueAnimXYZ)
|
||||
app.use(store)
|
||||
app.use(i18n)
|
||||
app.use(routes)
|
||||
// app.use(router)
|
||||
app.use(head)
|
||||
app.component('AppLayout', AppLayout)
|
||||
app.component('SimpleLayout', SimpleLayout)
|
||||
// app.component('GuestLayout', GuestLayout)
|
||||
app.mount('#app')
|
11
src/modules/README.md
Normal file
11
src/modules/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
## Modules
|
||||
|
||||
A custom user module system. Place a `.ts` file with the following template, it will be installed automatically.
|
||||
|
||||
```ts
|
||||
import { UserModule } from '~/types'
|
||||
|
||||
export const install: UserModule = ({ app, router, isClient }) => {
|
||||
// do something
|
||||
}
|
||||
```
|
25
src/modules/i18n.ts
Normal file
25
src/modules/i18n.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
// import { UserModule } from '~/types'
|
||||
|
||||
// import i18n resources
|
||||
// https://vitejs.dev/guide/features.html#glob-import
|
||||
export const messages = Object.fromEntries(
|
||||
Object.entries(
|
||||
import.meta.globEager('../../locales/*.y(a)?ml'))
|
||||
.map(([key, value]) => {
|
||||
const yaml = key.endsWith('.yaml')
|
||||
return [key.slice(14, yaml ? -5 : -4), value.default]
|
||||
}),
|
||||
)
|
||||
/*
|
||||
export const install: UserModule = ({ app }) => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages,
|
||||
})
|
||||
|
||||
app.use(i18n)
|
||||
}
|
||||
|
||||
*/
|
9
src/modules/nprogress.ts
Normal file
9
src/modules/nprogress.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import NProgress from 'nprogress'
|
||||
import { UserModule } from '~/types'
|
||||
|
||||
export const install: UserModule = ({ isClient, router }) => {
|
||||
if (isClient) {
|
||||
router.beforeEach(() => { NProgress.start() })
|
||||
router.afterEach(() => { NProgress.done() })
|
||||
}
|
||||
}
|
12
src/modules/sw.ts
Normal file
12
src/modules/sw.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { UserModule } from '~/types'
|
||||
|
||||
export const install: UserModule = ({ isClient, router }) => {
|
||||
if (isClient) {
|
||||
router.isReady().then(async() => {
|
||||
if (isClient) {
|
||||
const { registerSW } = await import('virtual:pwa-register')
|
||||
registerSW({ immediate: true })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
20
src/pages/README.md
Normal file
20
src/pages/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
## File-based Routing
|
||||
|
||||
Routes will be auto-generated for Vue files in this dir with the same file structure.
|
||||
Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details.
|
||||
|
||||
### Path Aliasing
|
||||
|
||||
`~/` is aliased to `./src/` folder.
|
||||
|
||||
For example, instead of having
|
||||
|
||||
```ts
|
||||
import { isDark } from '../../../../logic'
|
||||
```
|
||||
|
||||
now, you can use
|
||||
|
||||
```ts
|
||||
import { isDark } from '~/logic'
|
||||
```
|
5
src/pages/[...all].vue
Executable file
5
src/pages/[...all].vue
Executable file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
Not Found
|
||||
</div>
|
||||
</template>
|
52
src/pages/base.vue
Normal file
52
src/pages/base.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
const name = ref('')
|
||||
|
||||
const router = useRouter()
|
||||
const go = () => {
|
||||
if (name.value)
|
||||
router.push(`/hi/${encodeURIComponent(name.value)}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="text-4xl">
|
||||
<carbon-campsite class="inline-block" />
|
||||
</p>
|
||||
<p>
|
||||
<a rel="noreferrer" href="https://github.com/antfu/vitesse-lite" target="_blank">
|
||||
Vitesse Lite
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<em class="text-sm opacity-75">Opinionated Vite Starter Template</em>
|
||||
</p>
|
||||
|
||||
<div class="py-4" />
|
||||
|
||||
<input
|
||||
id="input"
|
||||
v-model="name"
|
||||
placeholder="What's your name?"
|
||||
type="text"
|
||||
autocomplete="false"
|
||||
p="x-4 y-2"
|
||||
w="250px"
|
||||
text="center"
|
||||
bg="transparent"
|
||||
border="~ rounded gray-200 dark:gray-700"
|
||||
outline="none active:none"
|
||||
@keydown.enter="go"
|
||||
>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="m-3 text-sm btn"
|
||||
:disabled="!name"
|
||||
@click="go"
|
||||
>
|
||||
Go
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
27
src/pages/hi/[name].vue
Normal file
27
src/pages/hi/[name].vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{ name: string }>()
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="text-4xl">
|
||||
<carbon-pedestrian class="inline-block" />
|
||||
</p>
|
||||
<p>
|
||||
Hi, {{ props.name }}
|
||||
</p>
|
||||
<p class="text-sm opacity-50">
|
||||
<em>Dynamic route!</em>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn m-3 text-sm mt-8"
|
||||
@click="router.back()"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
235
src/router.ts
Normal file
235
src/router.ts
Normal file
@ -0,0 +1,235 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
|
||||
// import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { setupLayouts } from 'virtual:generated-layouts'
|
||||
import generatedRoutes from 'virtual:generated-pages'
|
||||
// import generatedRoutes from 'pages-generated'
|
||||
// import { setupLayouts } from 'layouts-generated'
|
||||
|
||||
import useState from '~/hooks/useState'
|
||||
|
||||
// const Login = defineAsyncComponent(() => import('./views/Login.vue'))
|
||||
import store from '~/store'
|
||||
|
||||
import Home from '~/views/Home.vue'
|
||||
import About from '~/views/about.md'
|
||||
// import Register from '~/views/auth/Register.vue'
|
||||
import Login from '~/views/Login.vue'
|
||||
import Logout from '~/views/Logout.vue'
|
||||
import Profile from '~/components/Profile.vue'
|
||||
|
||||
import { verify_auth, check_credentials } from '~/hooks/utils'
|
||||
// import {
|
||||
// lang_url,
|
||||
// req_lang,
|
||||
// } from '~/defs/ta_defs'
|
||||
// const DataGrid = () => import('~/views/DataGrid.vue')
|
||||
//const SambaModel = () => import('~/views/SambaModel.vue')
|
||||
|
||||
const layoutsRoutes = setupLayouts(generatedRoutes)
|
||||
/*
|
||||
// import routes generated by Voie
|
||||
import pgRoutes from 'voie-pages'
|
||||
|
||||
const UIElements = defineAsyncComponent(() => import('./views/UIElements.vue'))
|
||||
const Tables = defineAsyncComponent(() => import('./views/Tables.vue'))
|
||||
const Dashboard = defineAsyncComponent(() => import('./views/Dashboard.vue'))
|
||||
const Forms = defineAsyncComponent(() => import('./views/Forms.vue'))
|
||||
const Input = defineAsyncComponent(() => import('./views/Input.vue'))
|
||||
const Home = defineAsyncComponent(() => import('./views/Home.vue'))
|
||||
const Modal = defineAsyncComponent(() => import('./views/Modal.vue'))
|
||||
const Blank = defineAsyncComponent(() => import('./views/Blank.vue'))
|
||||
const OLogin = defineAsyncComponent(() => import('./views/OLogin.vue'))
|
||||
const Entry = defineAsyncComponent(() => import('./views/Entry.vue'))
|
||||
*/
|
||||
|
||||
// https://medium.com/swlh/adding-meta-fields-and-transitions-to-vue-router-routes-f5cb78a7ed97
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// if (to.meta.requireAuth) {
|
||||
// if (!localStorage.getItem("auth")) {
|
||||
// next('/notfound')
|
||||
// } else {
|
||||
// next();
|
||||
// }
|
||||
// } else {
|
||||
// next();
|
||||
// }
|
||||
// });
|
||||
|
||||
// const get_lang = (_to: any, _from: any, next: any) => {
|
||||
// req_lang(lang_url, (lang: any) => { lng: lang })
|
||||
// // req_lang(lang_url, () => next())
|
||||
// }
|
||||
|
||||
const rooturl = window.ROOT_LOCATION || location.origin
|
||||
|
||||
const check_auth = async(to: any, _from: any, next: any) => {
|
||||
useState().bcPath.value = ''
|
||||
const auth = localStorage.getItem('auth') || ''
|
||||
const check_url = `${window.ROOT_LOCATION || location.origin}/checkin`
|
||||
const login_url = `${location.origin}/ui/login`
|
||||
const login_route = '/login'
|
||||
const mapkey = to.meta.uimapkey || 'ui'
|
||||
const map_route_to = () => {
|
||||
if (to.meta.requireAuth) {
|
||||
return login_route
|
||||
}
|
||||
if (to.meta.pubLayout) {
|
||||
to.meta.layout = to.meta.pubLayout
|
||||
to.meta.useNav = !to.meta.useNav
|
||||
}
|
||||
return
|
||||
}
|
||||
if (auth !== '') {
|
||||
const defs = store.state.app_defs.main.get(mapkey)
|
||||
if (defs && defs.checkin && useState().checkin.value) {
|
||||
next()
|
||||
}
|
||||
else {
|
||||
useState().checkin.value = true
|
||||
const res = await verify_auth(mapkey, check_url, login_url)
|
||||
// if (useState().connection.value.state === '')
|
||||
// TODO redirect to a connection error page or consider PWA offline mode
|
||||
if (res && res.defs)
|
||||
next()
|
||||
else {
|
||||
const route=map_route_to()
|
||||
next(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const route=map_route_to()
|
||||
next(route)
|
||||
}
|
||||
}
|
||||
|
||||
const get_defs = async(mapkey: string) => {
|
||||
const auth = localStorage.getItem('auth') || ''
|
||||
const url = `${window.ROOT_LOCATION || location.origin}/checkin`
|
||||
if (auth !== '') {
|
||||
const res = await check_credentials(url, { data: auth, mapkey })
|
||||
if (res.token === auth)
|
||||
return res.defs || {}
|
||||
else
|
||||
return {}
|
||||
}
|
||||
else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
/*
|
||||
{
|
||||
path: '/home',
|
||||
alias: '/',
|
||||
name: 'Home',
|
||||
component: Home,
|
||||
meta: { layout: 'empty' },
|
||||
},
|
||||
{
|
||||
path: '/ui/samba',
|
||||
name: 'Samba',
|
||||
component: SambaModel,
|
||||
meta: { layout: 'AppLayout' },
|
||||
},
|
||||
*/
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: Home,
|
||||
meta: {
|
||||
requireAuth: false,
|
||||
layout: 'AppLayout',
|
||||
ctx: 'home',
|
||||
// useNav: false,
|
||||
// pubLayout: 'SimpleLayout',
|
||||
mapkey: 'kloud',
|
||||
uiMapkey: 'ui',
|
||||
rooturl,
|
||||
},
|
||||
beforeEnter: check_auth,
|
||||
// (_to: any, _from: any, next: any) => {
|
||||
// req_lang(lang_url, () => next())
|
||||
// },
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login,
|
||||
meta: { layout: 'empty', rooturl, use_logo: true, allow_register: false, allow_reset: false },
|
||||
},
|
||||
{
|
||||
path: '/logout',
|
||||
name: 'Logout',
|
||||
component: Logout,
|
||||
meta: { layout: 'empty', rooturl, use_logo: true },
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
meta: {
|
||||
layout: 'AppLayout',
|
||||
ctx: 'about',
|
||||
rooturl,
|
||||
use_loog: true,
|
||||
},
|
||||
component: About,
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
meta: {
|
||||
layout: 'AppLayout',
|
||||
ctx: 'profile',
|
||||
rooturl,
|
||||
use_loog: true,
|
||||
},
|
||||
component: Profile,
|
||||
},
|
||||
// {
|
||||
// path: '/auth/register',
|
||||
// name: 'Register',
|
||||
// component: Register,
|
||||
// meta: { layout: 'GuestLayout' },
|
||||
// },
|
||||
/*
|
||||
{
|
||||
path: '/datalist',
|
||||
name: 'DataGrid',
|
||||
component: DataGrid,
|
||||
meta: {
|
||||
requireAuth: true,
|
||||
layout: 'AppLayout',
|
||||
uiMapkey: 'ui',
|
||||
mapkey: 'kloud',
|
||||
rooturl,
|
||||
bcpath: '#fa-book/librecloud/klouds',
|
||||
},
|
||||
beforeEnter: check_auth,
|
||||
// props: { lang: 'es' }, // get_lang,
|
||||
// beforeEnter: get_lang,
|
||||
// (_to: any, _from: any, next: any) => {
|
||||
// req_lang(lang_url, () => next())
|
||||
// },
|
||||
},
|
||||
*/
|
||||
{
|
||||
path: '/:catchAll(.*)',
|
||||
name: '404',
|
||||
meta: { layout: 'Page404' },
|
||||
component: () => import('./views/404.vue'),
|
||||
},
|
||||
...layoutsRoutes,
|
||||
]
|
||||
|
||||
// const routes: RouteRecordRaw[] = pgRoutes
|
||||
const router: any = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
// routes,
|
||||
export default router
|
57
src/shims.d.ts
vendored
Normal file
57
src/shims.d.ts
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/* eslint-disable import/no-duplicates */
|
||||
|
||||
declare interface Window {
|
||||
// extend the window
|
||||
ROOT_LOCATION: string
|
||||
}
|
||||
|
||||
// with vite-plugin-md, markdowns can be treat as Vue components
|
||||
declare module '*.md' {
|
||||
import { ComponentOptions } from 'vue'
|
||||
const component: ComponentOptions
|
||||
export default component
|
||||
}
|
||||
/*
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue'
|
||||
export default Vue
|
||||
}
|
||||
*/
|
||||
declare const _APP_VERSION: string
|
||||
/*
|
||||
* Keep states in the global scope to be reusable across Vue instances.
|
||||
*
|
||||
* @see {@link /createGlobalState}
|
||||
* @param stateFactory A factory function to create the state
|
||||
*/
|
||||
/*
|
||||
declare function createGlobalState<T extends object>(
|
||||
stateFactory: () => T
|
||||
): () => T
|
||||
*/
|
||||
interface EventTarget {
|
||||
value: EventTarget|null
|
||||
name: string| null
|
||||
/**
|
||||
* Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
|
||||
*
|
||||
* The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.
|
||||
*
|
||||
* When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.
|
||||
*
|
||||
* When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
|
||||
*
|
||||
* When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.
|
||||
*
|
||||
* The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.
|
||||
*/
|
||||
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void
|
||||
/**
|
||||
* Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
|
||||
*/
|
||||
dispatchEvent(event: Event): boolean
|
||||
/**
|
||||
* Removes the event listener in target's event listener list with the same type, callback, and options.
|
||||
*/
|
||||
removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void
|
||||
}
|
61
src/store/index.ts
Normal file
61
src/store/index.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { InjectionKey } from 'vue'
|
||||
import {
|
||||
createStore,
|
||||
createLogger,
|
||||
Store as VuexStore,
|
||||
useStore as baseUseStore,
|
||||
} from 'vuex'
|
||||
|
||||
import {
|
||||
app_data,
|
||||
AppDataState,
|
||||
app_check,
|
||||
AppCheckState,
|
||||
app_defs,
|
||||
AppDefsState,
|
||||
app_profile,
|
||||
AppProfileState,
|
||||
AppLangState,
|
||||
app_lang,
|
||||
} from './modules'
|
||||
|
||||
import { Store as AppDataStore } from './modules/app/data'
|
||||
import { Store as AppCheckStore } from './modules/app/check'
|
||||
import { Store as AppDefsStore } from './modules/app/defs'
|
||||
import { Store as AppProfileStore } from './modules/app/profile'
|
||||
import { Store as AppLangStore } from './modules/app/lang'
|
||||
|
||||
export interface RootState {
|
||||
data: AppDataState
|
||||
check: AppCheckState
|
||||
defs: AppDefsState
|
||||
profile: AppProfileState
|
||||
lang: AppLangState
|
||||
}
|
||||
|
||||
export type RootStore = AppDataStore<Pick<RootState, 'data'>>
|
||||
& AppCheckStore<Pick<RootState, 'check'>>
|
||||
& AppDefsStore<Pick<RootState, 'defs'>>
|
||||
& AppProfileStore<Pick<RootState, 'profile'>>
|
||||
& AppLangStore<Pick<RootState, 'lang'>>
|
||||
|
||||
// eslint-disable-next-line symbol-description
|
||||
export const key: InjectionKey<VuexStore<RootState>> = Symbol()
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production'
|
||||
|
||||
export default createStore<RootState>({
|
||||
modules: {
|
||||
app_data,
|
||||
app_check,
|
||||
app_defs,
|
||||
app_profile,
|
||||
app_lang,
|
||||
},
|
||||
strict: debug,
|
||||
plugins: debug ? [createLogger()] : [],
|
||||
})
|
||||
|
||||
// export const useStore = (): RootStore => {
|
||||
// return baseUseStore(key)
|
||||
// }
|
44
src/store/modules/app/check/actions.ts
Normal file
44
src/store/modules/app/check/actions.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
import { Mutations, MutationTypes } from './mutations'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export enum ActionTypes {
|
||||
addCheck = 'addCheck',
|
||||
removeCheck = 'removeCheck',
|
||||
editCheck = 'editCheck'
|
||||
}
|
||||
|
||||
type AppCheckActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
key: K,
|
||||
payload: Parameters<Mutations[K]>[1]
|
||||
): ReturnType<Mutations[K]>
|
||||
} & Omit<ActionContext<State, RootState>, 'commit'>
|
||||
|
||||
export interface Actions {
|
||||
[ActionTypes.addCheck](
|
||||
{ commit }: AppCheckActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.removeCheck](
|
||||
{ commit }: AppCheckActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.editCheck](
|
||||
{ commit }: AppCheckActionContext,
|
||||
payload: any
|
||||
): void
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, RootState> & Actions = {
|
||||
[ActionTypes.addCheck]({ commit }, payload) {
|
||||
commit(MutationTypes.addCheck, payload)
|
||||
},
|
||||
[ActionTypes.removeCheck]({ commit }, payload) {
|
||||
commit(MutationTypes.removeCheck, payload)
|
||||
},
|
||||
[ActionTypes.editCheck]({ commit }, payload) {
|
||||
commit(MutationTypes.editCheck, payload)
|
||||
},
|
||||
}
|
13
src/store/modules/app/check/getters.ts
Normal file
13
src/store/modules/app/check/getters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export type Getters<S = State> = {
|
||||
check(state: S, key: string): any
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, RootState> & Getters = {
|
||||
check: (state, key: string) => {
|
||||
return state.check.get(key)
|
||||
},
|
||||
}
|
49
src/store/modules/app/check/index.ts
Normal file
49
src/store/modules/app/check/index.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Store as VuexStore,
|
||||
Module,
|
||||
CommitOptions,
|
||||
DispatchOptions,
|
||||
} from 'vuex'
|
||||
|
||||
import { getters, Getters } from './getters'
|
||||
import { mutations, Mutations, MutationTypes } from './mutations'
|
||||
import { actions, Actions, ActionTypes } from './actions'
|
||||
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
interface State {
|
||||
check: Map<string, any>
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
check: new Map(),
|
||||
}
|
||||
|
||||
const data_module: Module<State, RootState> = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
||||
|
||||
export default data_module
|
||||
|
||||
type Store<S = State> = Omit<VuexStore<S>, 'commit' | 'getters' | 'dispatch' > & {
|
||||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
|
||||
key: K,
|
||||
payload: P,
|
||||
options?: CommitOptions
|
||||
): ReturnType<Mutations[K]>
|
||||
} & {
|
||||
getters: {
|
||||
[K in keyof Getters]: ReturnType<Getters[K]>
|
||||
}
|
||||
} & {
|
||||
dispatch<K extends keyof Actions>(
|
||||
key: K,
|
||||
payload: Parameters<Actions[K]>[1],
|
||||
options?: DispatchOptions
|
||||
): ReturnType<Actions[K]>
|
||||
}
|
||||
|
||||
export { State, ActionTypes, MutationTypes, Store }
|
48
src/store/modules/app/check/mutations.ts
Normal file
48
src/store/modules/app/check/mutations.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { AppCheck } from '~/store/types'
|
||||
|
||||
export enum MutationTypes {
|
||||
addCheck = 'addCheck',
|
||||
removeCheck = 'removeCheck',
|
||||
editCheck = 'editCheck'
|
||||
}
|
||||
|
||||
interface CheckMap {
|
||||
key: string
|
||||
check: any
|
||||
}
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.addCheck](state: S, check_map: CheckMap): void
|
||||
[MutationTypes.removeCheck](state: S, check_map: CheckMap): void
|
||||
[MutationTypes.editCheck](state: S, check_map: CheckMap, key: string): void
|
||||
}
|
||||
|
||||
export const mutations: MutationTree<State> & Mutations = {
|
||||
[MutationTypes.addCheck](state: State, check_map: CheckMap) {
|
||||
if (check_map.key && check_map.check)
|
||||
state.check.set(check_map.key, check_map.check)
|
||||
},
|
||||
[MutationTypes.removeCheck](state: State, check_map: CheckMap) {
|
||||
if (check_map.key && check_map.check) {
|
||||
const check = state.check.get(check_map.key)
|
||||
check.splice(check.indexOf(check_map.check), 1)
|
||||
state.check.set(check_map.key, check)
|
||||
}
|
||||
},
|
||||
[MutationTypes.editCheck](state: State, check_map: CheckMap) {
|
||||
if (check_map.key && check_map.check) {
|
||||
const new_check = [] as unknown as any
|
||||
const check = state.check.get(check_map.key)
|
||||
const key = check_map.check.id
|
||||
check.forEach((it: any) => {
|
||||
if (it.id === key)
|
||||
new_check.push(check)
|
||||
else
|
||||
new_check.push(it)
|
||||
})
|
||||
state.check.set(check_map.key, new_check)
|
||||
}
|
||||
},
|
||||
}
|
44
src/store/modules/app/data/actions.ts
Normal file
44
src/store/modules/app/data/actions.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
import { Mutations, MutationTypes } from './mutations'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export enum ActionTypes {
|
||||
addData = 'addData',
|
||||
removeData = 'removeData',
|
||||
editData = 'editData'
|
||||
}
|
||||
|
||||
type AppDataActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
key: K,
|
||||
payload: Parameters<Mutations[K]>[1]
|
||||
): ReturnType<Mutations[K]>
|
||||
} & Omit<ActionContext<State, RootState>, 'commit'>
|
||||
|
||||
export interface Actions {
|
||||
[ActionTypes.addData](
|
||||
{ commit }: AppDataActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.removeData](
|
||||
{ commit }: AppDataActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.editData](
|
||||
{ commit }: AppDataActionContext,
|
||||
payload: any
|
||||
): void
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, RootState> & Actions = {
|
||||
[ActionTypes.addData]({ commit }, payload) {
|
||||
commit(MutationTypes.addData, payload)
|
||||
},
|
||||
[ActionTypes.removeData]({ commit }, payload) {
|
||||
commit(MutationTypes.removeData, payload)
|
||||
},
|
||||
[ActionTypes.editData]({ commit }, payload) {
|
||||
commit(MutationTypes.editData, payload)
|
||||
},
|
||||
}
|
13
src/store/modules/app/data/getters.ts
Normal file
13
src/store/modules/app/data/getters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export type Getters<S = State> = {
|
||||
data(state: S, key: string): any
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, RootState> & Getters = {
|
||||
data: (state, key: string) => {
|
||||
return state.data.get(key)
|
||||
},
|
||||
}
|
49
src/store/modules/app/data/index.ts
Normal file
49
src/store/modules/app/data/index.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Store as VuexStore,
|
||||
Module,
|
||||
CommitOptions,
|
||||
DispatchOptions,
|
||||
} from 'vuex'
|
||||
|
||||
import { getters, Getters } from './getters'
|
||||
import { mutations, Mutations, MutationTypes } from './mutations'
|
||||
import { actions, Actions, ActionTypes } from './actions'
|
||||
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
interface State {
|
||||
data: Map<string, any>
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
data: new Map(),
|
||||
}
|
||||
|
||||
const data_module: Module<State, RootState> = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
||||
|
||||
export default data_module
|
||||
|
||||
type Store<S = State> = Omit<VuexStore<S>, 'commit' | 'getters' | 'dispatch' > & {
|
||||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
|
||||
key: K,
|
||||
payload: P,
|
||||
options?: CommitOptions
|
||||
): ReturnType<Mutations[K]>
|
||||
} & {
|
||||
getters: {
|
||||
[K in keyof Getters]: ReturnType<Getters[K]>
|
||||
}
|
||||
} & {
|
||||
dispatch<K extends keyof Actions>(
|
||||
key: K,
|
||||
payload: Parameters<Actions[K]>[1],
|
||||
options?: DispatchOptions
|
||||
): ReturnType<Actions[K]>
|
||||
}
|
||||
|
||||
export { State, ActionTypes, MutationTypes, Store }
|
48
src/store/modules/app/data/mutations.ts
Normal file
48
src/store/modules/app/data/mutations.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { AppData } from '~/store/types'
|
||||
|
||||
export enum MutationTypes {
|
||||
addData = 'addData',
|
||||
removeData = 'removeData',
|
||||
editData = 'editData'
|
||||
}
|
||||
|
||||
interface DataMap {
|
||||
key: string
|
||||
data: any
|
||||
}
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.addData](state: S, data_map: DataMap): void
|
||||
[MutationTypes.removeData](state: S, data_map: DataMap): void
|
||||
[MutationTypes.editData](state: S, data_map: DataMap, key: string): void
|
||||
}
|
||||
|
||||
export const mutations: MutationTree<State> & Mutations = {
|
||||
[MutationTypes.addData](state: State, data_map: DataMap) {
|
||||
if (data_map.key && data_map.data)
|
||||
state.data.set(data_map.key, data_map.data)
|
||||
},
|
||||
[MutationTypes.removeData](state: State, data_map: DataMap) {
|
||||
if (data_map.key && data_map.data) {
|
||||
const data = state.data.get(data_map.key)
|
||||
data.splice(data.indexOf(data_map.data), 1)
|
||||
state.data.set(data_map.key, data)
|
||||
}
|
||||
},
|
||||
[MutationTypes.editData](state: State, data_map: DataMap) {
|
||||
if (data_map.key && data_map.data) {
|
||||
const new_data = [] as unknown as any
|
||||
const data = state.data.get(data_map.key)
|
||||
const key = data_map.data.id
|
||||
data.forEach((it: any) => {
|
||||
if (it.id === key)
|
||||
new_data.push(data)
|
||||
else
|
||||
new_data.push(it)
|
||||
})
|
||||
state.data.set(data_map.key, new_data)
|
||||
}
|
||||
},
|
||||
}
|
44
src/store/modules/app/defs/actions.ts
Normal file
44
src/store/modules/app/defs/actions.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
import { Mutations, MutationTypes } from './mutations'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export enum ActionTypes {
|
||||
addDefs = 'addDefs',
|
||||
removeDefs = 'removeDefs',
|
||||
editDefs = 'editDefs'
|
||||
}
|
||||
|
||||
type AppDefsActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
key: K,
|
||||
payload: Parameters<Mutations[K]>[1]
|
||||
): ReturnType<Mutations[K]>
|
||||
} & Omit<ActionContext<State, RootState>, 'commit'>
|
||||
|
||||
export interface Actions {
|
||||
[ActionTypes.addDefs](
|
||||
{ commit }: AppDefsActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.removeDefs](
|
||||
{ commit }: AppDefsActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.editDefs](
|
||||
{ commit }: AppDefsActionContext,
|
||||
payload: any
|
||||
): void
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, RootState> & Actions = {
|
||||
[ActionTypes.addDefs]({ commit }, payload) {
|
||||
commit(MutationTypes.addDefs, payload)
|
||||
},
|
||||
[ActionTypes.removeDefs]({ commit }, payload) {
|
||||
commit(MutationTypes.removeDefs, payload)
|
||||
},
|
||||
[ActionTypes.editDefs]({ commit }, payload) {
|
||||
commit(MutationTypes.editDefs, payload)
|
||||
},
|
||||
}
|
13
src/store/modules/app/defs/getters.ts
Normal file
13
src/store/modules/app/defs/getters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export type Getters<S = State> = {
|
||||
defs(state: S, key: string): any
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, RootState> & Getters = {
|
||||
defs: (state, key: string) => {
|
||||
return state.main.get(key)
|
||||
},
|
||||
}
|
49
src/store/modules/app/defs/index.ts
Normal file
49
src/store/modules/app/defs/index.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Store as VuexStore,
|
||||
Module,
|
||||
CommitOptions,
|
||||
DispatchOptions,
|
||||
} from 'vuex'
|
||||
|
||||
import { getters, Getters } from './getters'
|
||||
import { mutations, Mutations, MutationTypes } from './mutations'
|
||||
import { actions, Actions, ActionTypes } from './actions'
|
||||
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
interface State {
|
||||
main: Map<string, any>
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
main: new Map(),
|
||||
}
|
||||
|
||||
const data_module: Module<State, RootState> = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
||||
|
||||
export default data_module
|
||||
|
||||
type Store<S = State> = Omit<VuexStore<S>, 'commit' | 'getters' | 'dispatch' > & {
|
||||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
|
||||
key: K,
|
||||
payload: P,
|
||||
options?: CommitOptions
|
||||
): ReturnType<Mutations[K]>
|
||||
} & {
|
||||
getters: {
|
||||
[K in keyof Getters]: ReturnType<Getters[K]>
|
||||
}
|
||||
} & {
|
||||
dispatch<K extends keyof Actions>(
|
||||
key: K,
|
||||
payload: Parameters<Actions[K]>[1],
|
||||
options?: DispatchOptions
|
||||
): ReturnType<Actions[K]>
|
||||
}
|
||||
|
||||
export { State, ActionTypes, MutationTypes, Store }
|
47
src/store/modules/app/defs/mutations.ts
Normal file
47
src/store/modules/app/defs/mutations.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
|
||||
export enum MutationTypes {
|
||||
addDefs = 'addDefs',
|
||||
removeDefs = 'removeDefs',
|
||||
editDefs = 'editDefs'
|
||||
}
|
||||
|
||||
interface DefsMap {
|
||||
key: string
|
||||
defs: any
|
||||
}
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.addDefs](state: S, defs_map: DefsMap): void
|
||||
[MutationTypes.removeDefs](state: S, defs_map: DefsMap): void
|
||||
[MutationTypes.editDefs](state: S, defs_map: DefsMap, key: string): void
|
||||
}
|
||||
|
||||
export const mutations: MutationTree<State> & Mutations = {
|
||||
[MutationTypes.addDefs](state: State, defs_map: DefsMap) {
|
||||
if (defs_map.key && defs_map.defs)
|
||||
state.main.set(defs_map.key, defs_map.defs)
|
||||
},
|
||||
[MutationTypes.removeDefs](state: State, defs_map: DefsMap) {
|
||||
if (defs_map.key && defs_map.defs) {
|
||||
const defs = state.main.get(defs_map.key)
|
||||
defs.splice(defs.indexOf(defs_map.defs), 1)
|
||||
state.main.set(defs_map.key, defs)
|
||||
}
|
||||
},
|
||||
[MutationTypes.editDefs](state: State, defs_map: DefsMap) {
|
||||
if (defs_map.key && defs_map.defs) {
|
||||
const new_defs = [] as unknown as any
|
||||
const defs = state.main.get(defs_map.key)
|
||||
const key = defs_map.defs.id
|
||||
defs.forEach((it: any) => {
|
||||
if (it.id === key)
|
||||
new_defs.push(defs)
|
||||
else
|
||||
new_defs.push(it)
|
||||
})
|
||||
state.main.set(defs_map.key, new_defs)
|
||||
}
|
||||
},
|
||||
}
|
35
src/store/modules/app/index.ts
Normal file
35
src/store/modules/app/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import data_module, { State as DataState, MutationTypes as DataMutationTypes, ActionTypes as DataActionTypes, Store as DataStore } from './data'
|
||||
import check_module, { State as CheckState, MutationTypes as CheckMutationTypes, ActionTypes as CheckActionTypes, Store as CheckStore } from './check'
|
||||
import defs_module, { State as DefsState, MutationTypes as DefsMutationTypes, ActionTypes as DefsActionTypes, Store as DefsStore } from './defs'
|
||||
import profile_module, { State as ProfileState, MutationTypes as ProfileMutationTypes, ActionTypes as ProfileActionTypes, Store as ProfileStore } from './profile'
|
||||
import lang_module, { State as LangState, MutationTypes as LangMutationTypes, ActionTypes as LangActionTypes, Store as LangStore } from './lang'
|
||||
|
||||
export const AppDataMutationTypes = DataMutationTypes
|
||||
export const AppCheckMutationTypes = CheckMutationTypes
|
||||
export const AppDefsMutationTypes = DefsMutationTypes
|
||||
export const AppProfileMutationTypes = ProfileMutationTypes
|
||||
export const AppLangMutationTypes = LangMutationTypes
|
||||
|
||||
export const AppDataActionTypes = DataActionTypes
|
||||
export const AppCheckActionTypes = CheckActionTypes
|
||||
export const AppDefsActionTypes = DefsActionTypes
|
||||
export const AppProfileActionTypes = ProfileActionTypes
|
||||
export const AppLangActionTypes = LangActionTypes
|
||||
|
||||
export type AppDataState= DataState
|
||||
export type AppCheckState= CheckState
|
||||
export type AppDefsState= DefsState
|
||||
export type AppProfileState= ProfileState
|
||||
export type AppLangState= LangState
|
||||
|
||||
export type AppDataStore = DataStore
|
||||
export type AppCheckStore = CheckStore
|
||||
export type AppDefsStore = DefsStore
|
||||
export type AppProfileStore = ProfileStore
|
||||
export type AppLangStore = LangStore
|
||||
|
||||
export const app_data = data_module
|
||||
export const app_check = check_module
|
||||
export const app_defs = defs_module
|
||||
export const app_profile = profile_module
|
||||
export const app_lang = lang_module
|
68
src/store/modules/app/lang/actions.ts
Normal file
68
src/store/modules/app/lang/actions.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
import { Mutations, MutationTypes } from './mutations'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export enum ActionTypes {
|
||||
addLang = 'addLang',
|
||||
addLangMain = 'addLangMain',
|
||||
addLangData = 'addLangData',
|
||||
addLangForms = 'addLangForms',
|
||||
removeLang = 'removeLang',
|
||||
editLang = 'editLang'
|
||||
}
|
||||
|
||||
type AppLangActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
key: K,
|
||||
payload: Parameters<Mutations[K]>[1]
|
||||
): ReturnType<Mutations[K]>
|
||||
} & Omit<ActionContext<State, RootState>, 'commit'>
|
||||
|
||||
export interface Actions {
|
||||
[ActionTypes.addLang](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.addLangMain](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.addLangData](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.addLangForms](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.removeLang](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.editLang](
|
||||
{ commit }: AppLangActionContext,
|
||||
payload: any
|
||||
): void
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, RootState> & Actions = {
|
||||
[ActionTypes.addLang]({ commit }, payload) {
|
||||
commit(MutationTypes.addLang, payload)
|
||||
},
|
||||
[ActionTypes.addLangMain]({ commit }, payload) {
|
||||
commit(MutationTypes.addLangMain, payload)
|
||||
},
|
||||
[ActionTypes.addLangData]({ commit }, payload) {
|
||||
commit(MutationTypes.addLangData, payload)
|
||||
},
|
||||
[ActionTypes.addLangForms]({ commit }, payload) {
|
||||
commit(MutationTypes.addLangForms, payload)
|
||||
},
|
||||
[ActionTypes.removeLang]({ commit }, payload) {
|
||||
commit(MutationTypes.removeLang, payload)
|
||||
},
|
||||
[ActionTypes.editLang]({ commit }, payload) {
|
||||
commit(MutationTypes.editLang, payload)
|
||||
},
|
||||
}
|
25
src/store/modules/app/lang/getters.ts
Normal file
25
src/store/modules/app/lang/getters.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export type Getters<S = State> = {
|
||||
lang(state: S, key: string): any
|
||||
langMain(state: S, key: string): any
|
||||
langData(state: S, key: string): any
|
||||
langForms(state: S, key: string): any
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, RootState> & Getters = {
|
||||
lang: (state: State, key: string) => {
|
||||
return state.lang.get(key)
|
||||
},
|
||||
langMain: (state: State, key: string) => {
|
||||
return state.lang.get(key)?.main || null
|
||||
},
|
||||
langData: (state: State, key: string) => {
|
||||
return state.lang.get(key)?.data || null
|
||||
},
|
||||
langForms: (state: State, key: string) => {
|
||||
return state.lang.get(key)?.forms || null
|
||||
},
|
||||
}
|
51
src/store/modules/app/lang/index.ts
Normal file
51
src/store/modules/app/lang/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
Store as VuexStore,
|
||||
Module,
|
||||
CommitOptions,
|
||||
DispatchOptions,
|
||||
} from 'vuex'
|
||||
|
||||
import { getters, Getters } from './getters'
|
||||
import { mutations, Mutations, MutationTypes } from './mutations'
|
||||
import { actions, Actions, ActionTypes } from './actions'
|
||||
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
import { AppLang } from '~/store/types'
|
||||
|
||||
interface State {
|
||||
lang: Map<string, AppLang>
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
lang: new Map(),
|
||||
}
|
||||
|
||||
const data_module: Module<State, RootState> = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
||||
|
||||
export default data_module
|
||||
|
||||
type Store<S = State> = Omit<VuexStore<S>, 'commit' | 'getters' | 'dispatch' > & {
|
||||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
|
||||
key: K,
|
||||
payload: P,
|
||||
options?: CommitOptions
|
||||
): ReturnType<Mutations[K]>
|
||||
} & {
|
||||
getters: {
|
||||
[K in keyof Getters]: ReturnType<Getters[K]>
|
||||
}
|
||||
} & {
|
||||
dispatch<K extends keyof Actions>(
|
||||
key: K,
|
||||
payload: Parameters<Actions[K]>[1],
|
||||
options?: DispatchOptions
|
||||
): ReturnType<Actions[K]>
|
||||
}
|
||||
|
||||
export { State, ActionTypes, MutationTypes, Store }
|
108
src/store/modules/app/lang/mutations.ts
Normal file
108
src/store/modules/app/lang/mutations.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { AppLang, AppCtxLang, LangMain, LangData, LangForms } from '~/store/types'
|
||||
|
||||
export enum MutationTypes {
|
||||
addLang = 'addLang',
|
||||
addLangMain = 'addLangMain',
|
||||
addLangData = 'addLangData',
|
||||
addLangForms = 'addLangForms',
|
||||
removeLang = 'removeLang',
|
||||
editLang = 'editLang'
|
||||
}
|
||||
interface LangMap {
|
||||
key: string
|
||||
lang: AppLang
|
||||
}
|
||||
interface LangSection {
|
||||
key: string
|
||||
section: any
|
||||
}
|
||||
interface CtxLangMap {
|
||||
key: string
|
||||
lang: AppCtxLang
|
||||
}
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.addLang](state: S, lang_map: LangMap): void
|
||||
[MutationTypes.addLangMain](state: S, lang_map: LangSection): void
|
||||
[MutationTypes.addLangData](state: S, lang_map: LangSection): void
|
||||
[MutationTypes.addLangForms](state: S, lang_map: LangSection): void
|
||||
[MutationTypes.removeLang](state: S, lang_map: CtxLangMap): void
|
||||
[MutationTypes.editLang](state: S, lang_map: CtxLangMap): void
|
||||
}
|
||||
|
||||
const get_lang_map = (state: State, mapkey: string): AppLang => {
|
||||
const lng = state.lang.get(mapkey)
|
||||
return lng || { main: {}, data: {}, forms: {} }
|
||||
}
|
||||
|
||||
export const mutations: MutationTree<State> & Mutations = {
|
||||
[MutationTypes.addLang](state: State, lang_map: LangMap) {
|
||||
state.lang.set(lang_map.key, lang_map.lang)
|
||||
},
|
||||
[MutationTypes.addLangMain](state, lang_map: LangSection) {
|
||||
const lng = get_lang_map(state, lang_map.key)
|
||||
lng.main = lang_map.section
|
||||
state.lang.set(lang_map.key, lng)
|
||||
},
|
||||
[MutationTypes.addLangData](state, lang_map: LangSection) {
|
||||
const lng = get_lang_map(state, lang_map.key)
|
||||
lng.data = lang_map.section
|
||||
state.lang.set(lang_map.key, lng)
|
||||
},
|
||||
[MutationTypes.addLangForms](state, lang_map: LangSection) {
|
||||
const lng = get_lang_map(state, lang_map.key)
|
||||
lng.data = lang_map.section
|
||||
state.lang.set(lang_map.key, lng)
|
||||
},
|
||||
[MutationTypes.removeLang](state: State, lang_map: CtxLangMap) {
|
||||
const lang = get_lang_map(state, lang_map.key)
|
||||
const key = lang_map.lang.lng.key
|
||||
switch (lang_map.lang.ctx) {
|
||||
case 'main':
|
||||
if (lang.main[key]) {
|
||||
delete lang.main[key]
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
case 'data':
|
||||
if (lang.data[key]) {
|
||||
delete lang.data[key]
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
case 'forms':
|
||||
if (lang.forms[key]) {
|
||||
delete lang.forms[key]
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
[MutationTypes.editLang](state: State, lang_map: CtxLangMap) {
|
||||
const lang = get_lang_map(state, lang_map.key)
|
||||
const key = lang_map.lang.lng.key
|
||||
const val = lang_map.lang.lng.text
|
||||
switch (lang_map.lang.ctx) {
|
||||
case 'main':
|
||||
if (lang.main[key]) {
|
||||
lang.main[key] = val
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
case 'data':
|
||||
if (lang.data[key]) {
|
||||
lang.data[key] = val
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
case 'forms':
|
||||
if (lang.forms[key]) {
|
||||
lang.forms[key] = val
|
||||
state.lang.set(lang_map.key, lang)
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
44
src/store/modules/app/profile/actions.ts
Normal file
44
src/store/modules/app/profile/actions.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ActionContext, ActionTree } from 'vuex'
|
||||
import { Mutations, MutationTypes } from './mutations'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export enum ActionTypes {
|
||||
addProfile = 'addProfile',
|
||||
removeProfile = 'removeProfile',
|
||||
editProfile = 'editProfile'
|
||||
}
|
||||
|
||||
type AppProfileActionContext = {
|
||||
commit<K extends keyof Mutations>(
|
||||
key: K,
|
||||
payload: Parameters<Mutations[K]>[1]
|
||||
): ReturnType<Mutations[K]>
|
||||
} & Omit<ActionContext<State, RootState>, 'commit'>
|
||||
|
||||
export interface Actions {
|
||||
[ActionTypes.addProfile](
|
||||
{ commit }: AppProfileActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.removeProfile](
|
||||
{ commit }: AppProfileActionContext,
|
||||
payload: any
|
||||
): void
|
||||
[ActionTypes.editProfile](
|
||||
{ commit }: AppProfileActionContext,
|
||||
payload: any
|
||||
): void
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, RootState> & Actions = {
|
||||
[ActionTypes.addProfile]({ commit }, payload) {
|
||||
commit(MutationTypes.addProfile, payload)
|
||||
},
|
||||
[ActionTypes.removeProfile]({ commit }, payload) {
|
||||
commit(MutationTypes.removeProfile, payload)
|
||||
},
|
||||
[ActionTypes.editProfile]({ commit }, payload) {
|
||||
commit(MutationTypes.editProfile, payload)
|
||||
},
|
||||
}
|
13
src/store/modules/app/profile/getters.ts
Normal file
13
src/store/modules/app/profile/getters.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
export type Getters<S = State> = {
|
||||
profile(state: S): any
|
||||
}
|
||||
|
||||
export const getters: GetterTree<State, RootState> & Getters = {
|
||||
profile: (state: State) => {
|
||||
return state.profile
|
||||
},
|
||||
}
|
49
src/store/modules/app/profile/index.ts
Normal file
49
src/store/modules/app/profile/index.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
Store as VuexStore,
|
||||
Module,
|
||||
CommitOptions,
|
||||
DispatchOptions,
|
||||
} from 'vuex'
|
||||
|
||||
import { getters, Getters } from './getters'
|
||||
import { mutations, Mutations, MutationTypes } from './mutations'
|
||||
import { actions, Actions, ActionTypes } from './actions'
|
||||
|
||||
import { RootState } from '~/store/index'
|
||||
|
||||
interface State {
|
||||
profile: any
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
profile: {},
|
||||
}
|
||||
|
||||
const data_module: Module<State, RootState> = {
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
getters,
|
||||
}
|
||||
|
||||
export default data_module
|
||||
|
||||
type Store<S = State> = Omit<VuexStore<S>, 'commit' | 'getters' | 'dispatch' > & {
|
||||
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
|
||||
key: K,
|
||||
payload: P,
|
||||
options?: CommitOptions
|
||||
): ReturnType<Mutations[K]>
|
||||
} & {
|
||||
getters: {
|
||||
[K in keyof Getters]: ReturnType<Getters[K]>
|
||||
}
|
||||
} & {
|
||||
dispatch<K extends keyof Actions>(
|
||||
key: K,
|
||||
payload: Parameters<Actions[K]>[1],
|
||||
options?: DispatchOptions
|
||||
): ReturnType<Actions[K]>
|
||||
}
|
||||
|
||||
export { State, ActionTypes, MutationTypes, Store }
|
37
src/store/modules/app/profile/mutations.ts
Normal file
37
src/store/modules/app/profile/mutations.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
import { State } from './index'
|
||||
|
||||
export enum MutationTypes {
|
||||
addProfile = 'addProfile',
|
||||
removeProfile = 'removeProfile',
|
||||
editProfile = 'editProfile'
|
||||
}
|
||||
|
||||
export type Mutations<S = State> = {
|
||||
[MutationTypes.addProfile](state: S, profile: any): void
|
||||
[MutationTypes.removeProfile](state: S, target: any): void
|
||||
[MutationTypes.editProfile](state: S, target: any, key: string): void
|
||||
}
|
||||
|
||||
export const mutations: MutationTree<State> & Mutations = {
|
||||
[MutationTypes.addProfile](state: State, profile: any) {
|
||||
state.profile = profile
|
||||
},
|
||||
[MutationTypes.removeProfile](state: State, target: any) {
|
||||
const profile = state.profile as any
|
||||
const key = target.key
|
||||
if (profile[key]) {
|
||||
delete profile[key]
|
||||
state.profile = profile
|
||||
}
|
||||
},
|
||||
[MutationTypes.editProfile](state: State, target: any) {
|
||||
const profile = state.profile as any
|
||||
const key = target.key
|
||||
const data = target.data
|
||||
if (profile[key]) {
|
||||
profile[key] = data
|
||||
state.profile = profile
|
||||
}
|
||||
},
|
||||
}
|
1
src/store/modules/index.ts
Normal file
1
src/store/modules/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './app'
|
97
src/store/store_module.ts
Normal file
97
src/store/store_module.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { useStore, Store } from 'vuex'
|
||||
|
||||
interface InternalModule<S, A, M, G> {
|
||||
state: S
|
||||
actions: A
|
||||
mutations: M
|
||||
getters: G
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows us to access the internal vuex properties and
|
||||
* maps them in a way which removes the module prefix.
|
||||
*/
|
||||
function getFromStoreByType<T>(
|
||||
moduleName: string,
|
||||
type: unknown,
|
||||
isNamespaced: boolean,
|
||||
) {
|
||||
if (isNamespaced) {
|
||||
return Object.keys(type)
|
||||
.filter(t => t.startsWith(`${moduleName}/`))
|
||||
.reduce((acc, curr) => {
|
||||
const typeName = curr.split('/').pop()
|
||||
const typeValue = type[curr][0]
|
||||
|
||||
return { [typeName]: typeValue, ...acc }
|
||||
}, {}) as T
|
||||
}
|
||||
|
||||
return Object.keys(type).reduce((acc, curr) => {
|
||||
const typeValue = type[curr][0]
|
||||
|
||||
return { [curr]: typeValue, ...acc }
|
||||
}, {}) as T
|
||||
}
|
||||
|
||||
/*
|
||||
* We have to wrap the getters in a Proxy because we only want to
|
||||
* "access" the getter if it actually being accessed.
|
||||
*
|
||||
* We could technically use the getFromStoreByType function but
|
||||
* the getter would be invoked multiple types on store instantiation.
|
||||
*
|
||||
* This is just a little cheeky workaround. Proxy <3
|
||||
*/
|
||||
function wrapGettersInProxy<G>(
|
||||
moduleName: string,
|
||||
getters: G,
|
||||
isNamespaced: boolean,
|
||||
) {
|
||||
return new Proxy(getters as Object, {
|
||||
get(_, prop: string) {
|
||||
if (isNamespaced)
|
||||
return getters[`${moduleName}/${prop}`]
|
||||
|
||||
return getters[prop]
|
||||
},
|
||||
}) as G
|
||||
}
|
||||
|
||||
function isModuleNamespaced<S>(moduleName: string, store: Store<S>): boolean {
|
||||
// @ts-ignore internal Vuex object that isn't typed.
|
||||
return Boolean(store._modulesNamespaceMap[`${moduleName}/`])
|
||||
}
|
||||
|
||||
export default function useStoreModule<S = any, A = any, M = any, G = any>(
|
||||
moduleName: string,
|
||||
storeName?: string,
|
||||
): InternalModule<S, A, M, G> {
|
||||
// @ts-ignore useStore doesn't yet accept a key as arg
|
||||
const store = storeName ? useStore(storeName) : useStore()
|
||||
const state = store.state[moduleName]
|
||||
const isNamespaced = isModuleNamespaced(moduleName, store)
|
||||
|
||||
const actions = getFromStoreByType<A>(
|
||||
moduleName,
|
||||
// @ts-ignore internal Vuex object that isn't typed.
|
||||
store._actions,
|
||||
isNamespaced,
|
||||
)
|
||||
|
||||
const mutations = getFromStoreByType<M>(
|
||||
moduleName,
|
||||
// @ts-ignore internal Vuex object that isn't typed.
|
||||
store._mutations,
|
||||
isNamespaced,
|
||||
)
|
||||
|
||||
const getters = wrapGettersInProxy<G>(moduleName, store.getters, isNamespaced)
|
||||
|
||||
return {
|
||||
actions,
|
||||
mutations,
|
||||
state,
|
||||
getters,
|
||||
}
|
||||
}
|
78
src/store/types.ts
Normal file
78
src/store/types.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
AppDataMutationTypes,
|
||||
AppDataActionTypes,
|
||||
AppCheckMutationTypes,
|
||||
AppCheckActionTypes,
|
||||
AppDefsMutationTypes,
|
||||
AppDefsActionTypes,
|
||||
AppProfileMutationTypes,
|
||||
AppProfileActionTypes,
|
||||
AppLangMutationTypes,
|
||||
AppLangActionTypes,
|
||||
} from './modules/app/index'
|
||||
|
||||
export const AppDataMutation = AppDataMutationTypes
|
||||
export const AppDataAction = AppDataActionTypes
|
||||
export const AppCheckMutation = AppCheckMutationTypes
|
||||
export const AppCheckAction = AppCheckActionTypes
|
||||
export const AppDefsMutation = AppDefsMutationTypes
|
||||
export const AppDefsAction = AppDefsActionTypes
|
||||
export const AppProfileMutation = AppProfileMutationTypes
|
||||
export const AppProfileAction = AppProfileActionTypes
|
||||
export const AppLangMutation = AppLangMutationTypes
|
||||
export const AppLangAction = AppLangActionTypes
|
||||
|
||||
export interface LangCtxKey {
|
||||
ctx: string
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface LangItem {
|
||||
key: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface LangMain {
|
||||
[key: string]: string
|
||||
}
|
||||
export interface LangData {
|
||||
[key: string]: string
|
||||
}
|
||||
export interface LangForms {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AppLang {
|
||||
main: LangMain
|
||||
data: LangData
|
||||
forms: LangForms
|
||||
}
|
||||
|
||||
export interface AppCtxLang {
|
||||
ctx: string
|
||||
lng: LangItem
|
||||
}
|
||||
export interface AppDefs {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AppProfile {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AppData {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AppCheck {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
/*
|
||||
export interface AppStore {
|
||||
defs: AppDefs
|
||||
profile: AppProfile
|
||||
data: [AppData]
|
||||
lang: AppLang
|
||||
}
|
||||
*/
|
43
src/styles/main.css
Executable file
43
src/styles/main.css
Executable file
@ -0,0 +1,43 @@
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background: #121212;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
margin: 2em;
|
||||
}
|
||||
.markdown-body p {
|
||||
margin-top: 1em;;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
padding: 2em;
|
||||
background: rgba(243,244,246);
|
||||
}
|
||||
.dark .markdown-body pre {
|
||||
background: rgba(75, 85, 99,var(--tw-bg-opacity))
|
||||
}
|
||||
.markdown-body a {
|
||||
color: rgba(37, 99, 235);
|
||||
}
|
||||
.btn {
|
||||
@apply px-4 py-1 rounded inline-block
|
||||
bg-teal-600 text-white cursor-pointer
|
||||
hover:bg-teal-700
|
||||
disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
@apply inline-block cursor-pointer select-none
|
||||
opacity-75 transition duration-200 ease-in-out
|
||||
hover:opacity-100 hover:text-teal-600;
|
||||
font-size: 0.9em;
|
||||
}
|
86
src/typs/clouds/index.ts
Normal file
86
src/typs/clouds/index.ts
Normal file
@ -0,0 +1,86 @@
|
||||
export const enum LanguageType {
|
||||
en = 'en',
|
||||
es = 'es',
|
||||
None = 'None',
|
||||
}
|
||||
export interface StatusItemType {
|
||||
title: string
|
||||
content: string
|
||||
lang: LanguageType
|
||||
datetime: string
|
||||
isOpen: boolean
|
||||
}
|
||||
export interface StatusItemDataType {
|
||||
[key: string]: any
|
||||
title: string
|
||||
content: string
|
||||
lang: LanguageType
|
||||
datetime: string
|
||||
}
|
||||
export const enum ReqType {
|
||||
tcp = 'tcp',
|
||||
https = 'https',
|
||||
NotSet = 'NotSet',
|
||||
}
|
||||
export const enum CriticalType {
|
||||
yes = 'yes',
|
||||
cloud = 'cloud',
|
||||
group = 'group',
|
||||
ifresized = 'ifresized',
|
||||
no = 'no',
|
||||
}
|
||||
export interface SrvcType {
|
||||
name: string
|
||||
path: string
|
||||
req: ReqType
|
||||
target: string
|
||||
liveness: string
|
||||
critical: CriticalType
|
||||
}
|
||||
export interface SrvcInfoType {
|
||||
[key: string]: any
|
||||
name: string
|
||||
info: string
|
||||
srvc: SrvcType
|
||||
}
|
||||
export interface CloudGroupItemType {
|
||||
[key: string]: any
|
||||
hostname: string
|
||||
tsksrvcs: SrvcType[]
|
||||
appsrvcs: SrvcType[]
|
||||
}
|
||||
export interface CloudGroupServcType {
|
||||
[key: string]: any
|
||||
hostname: string
|
||||
name: string
|
||||
tsksrvcs: SrvcInfoElemType[]
|
||||
appsrvcs: SrvcInfoElemType[]
|
||||
}
|
||||
|
||||
export interface CloudDataCheck {
|
||||
[key: string]: any
|
||||
name: string
|
||||
apps: Map<string, Map<string, CloudGroupServcType>>
|
||||
cloud: Map<string, Map<string, CloudGroupServcType>>
|
||||
infos: StatusItemType[]
|
||||
}
|
||||
export interface CloudOptionType {
|
||||
name: string
|
||||
option: number
|
||||
}
|
||||
export interface CloudGroupDataType {
|
||||
[key: string]: CloudGroupItemType[] | any
|
||||
}
|
||||
export interface ResCloudDataCheck {
|
||||
[key: string]: any
|
||||
name: string
|
||||
cloud: CloudGroupDataType
|
||||
apps: CloudGroupDataType
|
||||
// cloud: CloudGroupSrvcType
|
||||
// statusentries: StatusItemDataType[]
|
||||
}
|
||||
export interface ResCloudDataCheckDefs {
|
||||
[key: string]: any
|
||||
check: ResCloudDataCheck[]
|
||||
defs: any
|
||||
}
|
23
src/typs/index.ts
Normal file
23
src/typs/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export enum MessageType {
|
||||
Show,
|
||||
Success,
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
}
|
||||
export interface MenuItemType {
|
||||
id: string
|
||||
title: string
|
||||
active: boolean
|
||||
link: string
|
||||
}
|
||||
|
||||
export enum NavItemType {
|
||||
router_link = 'router_link',
|
||||
app_link = 'app_link',
|
||||
a_blank = 'a_blank',
|
||||
a_link = 'a_link',
|
||||
cloud_link = 'cloud_link',
|
||||
module_label = 'module_label',
|
||||
separator = 'separator',
|
||||
}
|
26
src/views/404.vue
Normal file
26
src/views/404.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="px-4 py-10 text-center text-teal-700 dark:text-gray-200">
|
||||
<div>
|
||||
<p class="text-4xl">
|
||||
<carbon-warning class="inline-block" />
|
||||
</p>
|
||||
</div>
|
||||
<router-view />
|
||||
<div>
|
||||
<button
|
||||
class="btn m-3 text-sm mt-8"
|
||||
@click="router.back()"
|
||||
>
|
||||
{{ t('button.back') }}
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
21
src/views/About.md
Normal file
21
src/views/About.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: About
|
||||
---
|
||||
|
||||
<div class="text-center">
|
||||
<!-- You can use Vue components inside markdown -->
|
||||
<carbon-dicom-overlay class="text-4xl mb-6 m-auto" />
|
||||
<h3>About</h3>
|
||||
</div>
|
||||
|
||||
[Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **Tailwind** v2 for UI.
|
||||
|
||||
```js
|
||||
// syntax highlighting example
|
||||
function vitesse() {
|
||||
const foo = 'bar'
|
||||
console.log(foo)
|
||||
}
|
||||
```
|
||||
|
||||
heck out the [GitHub repo](https://github.com/antfu/vitesse) for more details.
|
329
src/views/Home.vue
Normal file
329
src/views/Home.vue
Normal file
@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<NavMenu v-if="use_nav" :usetitle="false" :title="title" navtitle="" :items="navMenuItems" @on-nav-menu="onNavMenu">
|
||||
<template #opslist v-if="clds_groups.length > 0 ">
|
||||
<li class="mr-3">
|
||||
<select id="cld-select" class="rounded-xl bg-light-300 form-select p-2 mt-1 block w-full ddark:hover:bg-gray-800 ddark:bg-gray-500 ddark:text-gray-200 bg-gray-200 text-gray-800" @change="update_cloud_selection">
|
||||
<option
|
||||
v-for="(cld, index) in clouds_options"
|
||||
:key="index"
|
||||
class="bg-gray-200 text-gray-800 appearance-none border-none inline-block py-3 pl-3 pr-8 rounded leading-tight ddark:hover:bg-gray-800 ddark:bg-gray-500 ddark:text-gray-200"
|
||||
:selected="cld_sel_idx === index"
|
||||
>
|
||||
{{ cld.name }}
|
||||
</option>
|
||||
</select>
|
||||
</li>
|
||||
</template>
|
||||
<template #search v-if="(cld_selected.cloud && cld_selected.cloud.size > 0 ) || cld_selected.apps && cld_selected.apps.size > 0">
|
||||
<input id="search" v-model="search" class="rounded-l-full w-full py-2 px-2 text-gray-700 leading-tight focus:outline-none" type="text" :placeholder="t('nav.Search','Search')">
|
||||
<div class="p-1">
|
||||
<button
|
||||
class="bg-blue-500 text-white rounded-full p-2 hover:bg-blue-400 focus:outline-none flex items-center justify-center"
|
||||
@click="on_search"
|
||||
>
|
||||
<span class="text-xs">
|
||||
<carbon-search class="align-bottom" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #lastitems>
|
||||
<li class="mr-3">
|
||||
<MenuLocales :label-mode="localesLabelMode" :include-current="includeCurrLocale" />
|
||||
</li>
|
||||
</template>
|
||||
</NavMenu>
|
||||
<Profile v-if="show_profile" />
|
||||
<div id="tophome">
|
||||
<div v-if="cld_selected.cloud && cld_selected.cloud.size > 0" class="-mt-2 text-gray-800 lg:p-1">
|
||||
<CloudGroups
|
||||
:title="t('nav.Tasks','Services')"
|
||||
target="tsksrvcs"
|
||||
:groups="cld_selected.cloud"
|
||||
:search="search"
|
||||
:hide="tasks_hide"
|
||||
@on-cloud-group="onCloudGroup"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="cld_selected.apps && cld_selected.apps.size > 0" class="mt-1 text-gray-800 p-2 lg:p-4">
|
||||
<CloudGroups
|
||||
:title="t('nav.Apps','Apps')"
|
||||
target="appsrvcs"
|
||||
:groups="cld_selected.apps"
|
||||
:search="search"
|
||||
:hide="apps_hide"
|
||||
@on-cloud-group="onCloudGroup"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="cld_selected.infos && cld_selected.infos.length > 0" class="shadow-lg mx-auto bg-gray-100 text-gray-800 mt-4 md:mt-2 bg-light-800 dark:bg-gray-600 w-full p-2 rounded-l">
|
||||
<Infos :items="cld_selected.infos" :title="t('nav.Informations','Informations')"
|
||||
@on-cloud-info-item="onCloudInfoItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import CloudGroups from '~/components/CloudGroups.vue'
|
||||
// import ProfileView from '~/components/ProfileView.vue'
|
||||
import NavMenu from '~/components/NavMenu.vue'
|
||||
import Infos from '~/views/Infos.vue'
|
||||
import Profile from '~/components/Profile.vue'
|
||||
import { load_data } from '~/hooks/utils'
|
||||
// import { translate, show_message } from '~/hooks/utils'
|
||||
import useState from '~/hooks/useState'
|
||||
import { StatusItemType, StatusItemDataType, ResCloudDataCheckDefs, CloudGroupServcType, CloudDataCheck,CloudOptionType } from '~/typs/clouds'
|
||||
import { SideMenuItemType } from '~/typs/cmpnts'
|
||||
import MenuLocales from '@/menus/MenuLocales.vue'
|
||||
import { AppDefsAction } from '~/store/types'
|
||||
// import { AppDataAction, AppCheckAction, AppDefsAction } from '~/store/types'
|
||||
|
||||
enum LocalesLabelModes {
|
||||
value = 'val',
|
||||
translation = 'trans',
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const map_key = router.currentRoute.value.meta.mapkey as string || 'ui'
|
||||
const use_nav = router.currentRoute.value.meta.useNav as boolean
|
||||
const ctx = router.currentRoute.value.meta.ctx || ''
|
||||
const localesLabelMode = ref(LocalesLabelModes.translation)
|
||||
const includeCurrLocale = true
|
||||
|
||||
const store = useStore()
|
||||
const { t } = useI18n()
|
||||
const title = ref('Services Status')
|
||||
const search = useState().search
|
||||
// const router = useRouter()
|
||||
// const go = () => {
|
||||
// if (name.value)
|
||||
// router.push(`/hi/${encodeURIComponent(name.value)}`)
|
||||
// }
|
||||
const show_profile = computed(() => useState().show_profile.value)
|
||||
const defs = computed(() => store.state.app_defs.main.get(map_key) || {})
|
||||
|
||||
const menu_items = computed((): SideMenuItemType[] => {
|
||||
const defsMenuItems = defs.value && defs.value.sidebar && defs.value.sidebar.menu_items ? defs.value.sidebar.menu_items : [] as SideMenuItemType[]
|
||||
const all_items = [].concat(defsMenuItems || []) // .concat(useState().sidebarMenuItems.value as SideMenuItemType[] | any)
|
||||
useState().sidebarMenuItems.value = all_items.filter((itm: SideMenuItemType) => {
|
||||
if (itm.ctx && itm.ctx === ctx) {
|
||||
return true
|
||||
} else if (itm.ctx && itm.ctx !== ctx) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return useState().sidebarMenuItems.value
|
||||
})
|
||||
const navMenuItems = computed(() => {
|
||||
const menu_items_active = menu_items.value.filter(menuitem => {
|
||||
switch (menuitem.click) {
|
||||
case 'tsksrvcs':
|
||||
return cld_selected.value.cloud && cld_selected.value.cloud.size > 0
|
||||
break
|
||||
case 'appsrvcs':
|
||||
return cld_selected.value.apps && cld_selected.value.apps.size > 0
|
||||
break
|
||||
case 'srvcstatus':
|
||||
return cld_selected.value.infos && cld_selected.value.infos.length > 0
|
||||
break
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
return menu_items_active
|
||||
})
|
||||
const on_search = () => {
|
||||
}
|
||||
const tasks_hide = ref(false)
|
||||
const apps_hide = ref(false)
|
||||
const onCloudGroup = (target: string) => {
|
||||
if (target) {
|
||||
switch (target) {
|
||||
case useState().tsksrvcs:
|
||||
tasks_hide.value = !tasks_hide.value
|
||||
break
|
||||
case useState().appsrvcs:
|
||||
apps_hide.value = !apps_hide.value
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
const onAppHome = () => {
|
||||
const dom_id = document.getElementById('tophome')
|
||||
if (dom_id) {
|
||||
dom_id.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
|
||||
setTimeout(() => window.scrollBy(0, -40), 1000)
|
||||
}
|
||||
}
|
||||
useState().app_home_click.value = onAppHome
|
||||
useState().backdrop_blur.value = 2
|
||||
useState().back_opacity.value = 100
|
||||
useState().panels.value.search = { style: 'h-auto'}
|
||||
|
||||
const onNavMenu = (item: SideMenuItemType) => {
|
||||
if (item) {
|
||||
const dom_id = document.getElementById(item.click as string)
|
||||
if (dom_id) {
|
||||
dom_id.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
|
||||
setTimeout(() => window.scrollBy(0, -40), 1000)
|
||||
}
|
||||
// const new_defs = JSON.parse(JSON.stringify(store.state.app_defs.main.get(map_key)))
|
||||
// new_defs.sidebar.menu_items = menu_items.value.map(i => ({ ...i, active: i.click !== item.click? false : !i.active }))
|
||||
// store.dispatch(AppDefsAction.addDefs, new_defs)
|
||||
switch (item.click) {
|
||||
case useState().tsksrvcs:
|
||||
tasks_hide.value = false
|
||||
break
|
||||
case useState().appsrvcs:
|
||||
apps_hide.value = false
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
useState().side_menu_click.value = onNavMenu
|
||||
const cld_default_name = 'ma'
|
||||
const clds_groups = ref([] as CloudDataCheck[])
|
||||
const cld_sel_idx= ref(0)
|
||||
const cld_selected = ref({} as CloudDataCheck)
|
||||
|
||||
const clouds_options = computed(() => {
|
||||
const options = [] as CloudOptionType[]
|
||||
clds_groups.value.forEach((it,idx) => {
|
||||
options.push({
|
||||
name: it.name,
|
||||
option: idx,
|
||||
})
|
||||
})
|
||||
return options
|
||||
})
|
||||
const update_cloud_selection = (e: any) => {
|
||||
if (clds_groups.value[e.target.selectedIndex]) {
|
||||
cld_sel_idx.value=e.target.selectedIndex
|
||||
cld_selected.value = clds_groups.value[cld_sel_idx.value]
|
||||
}
|
||||
}
|
||||
const onCloudInfoItem = (item: StatusItemType) => {
|
||||
if (item) {
|
||||
clds_groups.value[cld_sel_idx.value].infos = clds_groups.value[cld_sel_idx.value].infos.map((i: StatusItemType) => ({
|
||||
...i, isOpen: i.title === item.title && i.datetime === item.datetime ? !i.isOpen : false
|
||||
}))
|
||||
cld_selected.value = clds_groups.value[cld_sel_idx.value]
|
||||
}
|
||||
}
|
||||
const sortInfos = (data: StatusItemType[], rev: boolean): StatusItemType[] => {
|
||||
const mapped = data.map(function(el, i) {
|
||||
return { index: i, value: parseInt(el.datetime.replaceAll(' ','').replaceAll('/','').replaceAll(':',''),0) || 0 };
|
||||
})
|
||||
mapped.sort(function(a, b) {
|
||||
if (a.value > b.value) {
|
||||
return rev ? -1 : 1;
|
||||
}
|
||||
if (a.value < b.value) {
|
||||
return rev ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
const result = mapped.map(function(el){
|
||||
return data[el.index];
|
||||
});
|
||||
return result
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
useState().sidebarMenuItems.value = useState().sidebarMenuItems.value.filter((itm: SideMenuItemType) => {
|
||||
if (itm.ctx && itm.ctx === ctx) {
|
||||
return false
|
||||
} else if (itm.ctx && itm.ctx !== ctx) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
useState().side_menu_click.value = () => router.push('/')
|
||||
useState().app_home_click.value = () => router.push('/')
|
||||
})
|
||||
onMounted(async() => {
|
||||
// await nextTick()
|
||||
const use_local = false
|
||||
let url = 'groups.json'
|
||||
const rooturl = window.ROOT_LOCATION || location.origin
|
||||
if (!use_local)
|
||||
url = `${rooturl}/info`
|
||||
|
||||
clds_groups.value = [] as CloudDataCheck[]
|
||||
await load_data(url, 0, 2000, (res: ResCloudDataCheckDefs) => {
|
||||
// use Map to keep objects order
|
||||
// use Object.keys + sort to map same result order
|
||||
const clouds_groups = [] as CloudDataCheck[]
|
||||
res.check.forEach((kld,kldidx) => {
|
||||
if (kld.name === cld_default_name)
|
||||
cld_sel_idx.value=kldidx
|
||||
const check_data: Map<string, Map<string, CloudGroupServcType>> = new Map()
|
||||
clouds_groups[kldidx] = {} as CloudDataCheck
|
||||
Object.keys(kld.apps).sort().forEach((grp_key) => {
|
||||
const data: Map<string, CloudGroupServcType> = new Map()
|
||||
Object.keys(kld.apps[grp_key]).sort().forEach((key) => {
|
||||
const grp_res: CloudGroupServcType[] = kld.apps[grp_key][key].filter((it:CloudGroupServcType) => it.tsksrvcs.length > 0 || it.appsrvcs.length > 0)
|
||||
if (grp_res.length > 0)
|
||||
data.set(key, kld.apps[grp_key][key])
|
||||
})
|
||||
if (data.size > 0)
|
||||
check_data.set(grp_key, data)
|
||||
})
|
||||
clouds_groups[kldidx] = {
|
||||
name: kld.name,
|
||||
apps: check_data,
|
||||
cloud: kld.cloud ? new Map() : check_data,
|
||||
infos: [] as StatusItemType[],
|
||||
}
|
||||
if (kld.cloud) {
|
||||
Object.keys(kld.cloud).sort().forEach((grp_key) => {
|
||||
Object.keys(kld.cloud[grp_key]).sort().forEach((key) => {
|
||||
const data: CloudGroupServcType[] = kld.cloud[grp_key][key]
|
||||
data.forEach((it, index) => {
|
||||
if (clouds_groups[kldidx].apps.get(grp_key)) {
|
||||
const m = clouds_groups[kldidx].apps.get(grp_key)
|
||||
if (m && m.size > 0 && m.get(key) && it.tsksrvcs) {
|
||||
const m_data = m.get(key)
|
||||
if (m_data && m_data[index]) {
|
||||
m_data[index].tsksrvcs = it.tsksrvcs
|
||||
const new_data: Map<string, CloudGroupServcType> = new Map()
|
||||
new_data.set(key, m_data)
|
||||
clouds_groups[kldidx].cloud.set(grp_key, new_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
let infos: StatusItemType[] = []
|
||||
if (kld.infos) {
|
||||
kld.infos.forEach((it: StatusItemDataType) => {
|
||||
infos.push({ ...it, isOpen: false })
|
||||
})
|
||||
infos = sortInfos(infos,true)
|
||||
}
|
||||
clouds_groups[kldidx].infos = infos
|
||||
})
|
||||
clds_groups.value = clouds_groups
|
||||
cld_selected.value = clds_groups.value[cld_sel_idx.value]
|
||||
if (res.defs) {
|
||||
store.dispatch(AppDefsAction.addDefs, { key: map_key, defs: res.defs })
|
||||
}
|
||||
useState().sidebarMenuItems.value = res.defs.sidebar.menu_items.filter((itm: SideMenuItemType) => {
|
||||
if (itm.ctx && itm.ctx === ctx) {
|
||||
return true
|
||||
} else if (itm.ctx && itm.ctx !== ctx) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user