chore: add src

This commit is contained in:
Jesús Pérez Lorenzo 2021-10-14 15:42:52 +01:00
parent d3e880d1d4
commit 49e9edae14
65 changed files with 3855 additions and 0 deletions

26
src/App.vue Normal file
View File

@ -0,0 +1,26 @@
<template>
<component :is="layout" class="px-4 py-10 text-center 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 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
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}`
})
</script>

View 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>

View File

@ -0,0 +1,63 @@
<script setup lang="ts">
import { CloudGroupServcType } 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)
}
</script>
<template>
<div :id="props.target" class="border-t-gray-800 border-opacity-90 bg-light-600 w-full p-4 mt-4 md:mt-2 rounded-l">
<div class="flex flex justify-between text-2xl bg-light-800 p-3 rounded-xl" @click="on_group_item(props.target,$event)">
<div class=""> {{ 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
v-if="props.target === 'tsksrvcs' && srvr.tsksrvcs && srvr.tsksrvcs.length >0"
:source="srvr.tsksrvcs"
:target="props.target"
:filter="props.search"
:hostname="srvr.hostname"
/>
<CloudServices
v-if="props.target === 'appsrvcs' && srvr.appsrvcs && srvr.appsrvcs.length >0"
:source="srvr.appsrvcs"
:target="props.target"
:filter="props.search"
:hostname="srvr.hostname"
/>
</div>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,99 @@
<script setup lang="ts">
import {
SrvcInfoType,
CriticalType,
} from '~/typs/clouds'
import { auth_data } from '~/hooks/utils'
const { t } = useI18n()
const props = defineProps<{
source: SrvcInfoType[]
hostname: string
target: string
filter: string
}>()
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 ? t(`srvc.${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)
}
</script>
<template>
<div
v-for="(item ,srvcindex) in props.source"
:key="`grp_${srvcindex}`"
class="w-1/2 md:w-1/4 lg:w-1/6 xl:w-1/7 font-light"
:class="{hidden: !filter_item(item)}"
>
<div
class="flex bg-white rounded-lg shadow-md m-2 border-l-4 border-white 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>
</template>

16
src/components/Footer.vue Normal file
View File

@ -0,0 +1,16 @@
<script setup lang="ts">
import { isDark, toggleDark } from '~/logic'
</script>
<template>
<nav class="text-xl mt-6">
<button class="icon-btn mx-2 !outline-none" @click="toggleDark">
<carbon-moon v-if="isDark" />
<carbon-sun v-else />
</button>
<a class="icon-btn mx-2" rel="noreferrer" href="https://github.com/antfu/vitesse-lite" target="_blank" title="GitHub">
<carbon-logo-github />
</a>
</nav>
</template>

107
src/components/NavMenu.vue Normal file
View File

@ -0,0 +1,107 @@
<script setup lang="ts">
import { MenuItemType } from '~/typs'
import { auth_data } from '~/hooks/utils'
const { t } = useI18n()
const props = defineProps<{
title: string
items: Array<MenuItemType>
}>()
const authData = auth_data()
const emit = defineEmits(['onNavMenu'])
const isOpen = ref(false)
const on_item = (item: MenuItemType, event: Event) => {
event.preventDefault()
event.stopImmediatePropagation()
event.stopPropagation()
emit('onNavMenu', item)
}
</script>
<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="hidden text-sm md:block mr-2">LibreCloud</span>
<span class="w-11 h-11 rounded-full mr-2">
<img class="h-11 w-auto rounded" src="/assets/images/app_w.svg" alt="App">
</span>
<span
class="text-xl pl-2"
><i class="em em-grinning"></i> {{ 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="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.id" 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.link"
@click="on_item(item,$event)"
>{{ item.title }}
</a>
</li>
<li class="p-1">
<div class="bg-white flex items-center rounded-full shadow-xl">
<slot name="search" />
</div>
</li>
<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>

View File

@ -0,0 +1,125 @@
<script setup lang="ts">
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',
showsettings: false,
photourl: 'https://avatars.githubusercontent.com/u/59666?v=4',
editing: '',
})
const profile_saveedit = (name: string) => {
profile.value.editing = ''
}
const profile_edit = (name: string) => {
profile.value.editing = name
// this.$nextTick(() => {
// this.$refs[`${name}input`].focus()
// })
}
const profile_discard = (name: string) => {
// this.$refs[`${name}input`].value = this[name]
profile.value.editing = ''
}
const profile_selectcolor = (color: string) => {
profile.value.bgcolor = color
}
</script>
<template>
<div class="hidden block flex flex-col w-full h-screen justify-center items-center bg-white">
<div class="flex flex-col w-70 shadow-md max-w-full h-70 rounded-lg transition-colors ease-in" :class="profile.bgcolor" @click="profile.showsettings=!profile.showsettings">
<div class="fixed z-10 flex flex-col w-70 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">Settings</span>
<div class="flex flex-col space-y-2 mt-5 flex-grow">
<span class="text-md font-bold">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="profile.showsettings = false">
Close
</button>
</div>
</div>
<div v-show="!profile.showsettings">
<div class="flex w-full justify-between p-2 mt-1">
<button class="flex space-x-1 group font-semibold items-center bg-transparent cursor-pointer text-white fill-current" @click="profile.showsettings=true">
<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">settings</span>
</button>
<button 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">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="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
View 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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

46
src/hooks/useState.ts Normal file
View File

@ -0,0 +1,46 @@
import { ref, computed } from 'vue'
// import type { ListCloudBoxesType } from '~/typs/clouds'
// import type { SideMenuItemType } 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 bcPath = ref('')
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: 'Services Status', active: false, link: '#' },
])
const showModal = ref(false)
const checkin = ref(false)
const connection = ref({
state: '',
})
export default function useState() {
return {
tsksrvcs,
appsrvcs,
srvcstatus,
menu_items,
reqError,
currentMapKey,
isSidebarOpen,
toggleSidebar,
bcPath,
showModal,
checkin,
connection,
}
}

341
src/hooks/utils.ts Normal file
View File

@ -0,0 +1,341 @@
// 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 res = await self.fetch(data_path, {
headers: {
Authorization: `Bearer ${token}`,
// 'content-type': 'application/json',
},
})
// 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,
}

View File

@ -0,0 +1,7 @@
<template>
<div class="antialiased text-gray-900 bg-white">
<slot />
</div>
</template>
<script setup></script>

View 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
View 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>

300
src/layouts/_AppLayout.vue Normal file
View File

@ -0,0 +1,300 @@
<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
v-show="useDragHeader && show_top_pane"
id="pane-top"
:style="`height: ${head_data.h}px`"
class="eexpandable p-3 z-999 bg-gray-200 text-gray-100 dark:bg-gray-800 dark:text-white absolute "
>
<Backdrop :show="show_top_pane" :blur="14" :css="`height: ${head_data.h}px;`" @close="close_top_pane" />
<div class="bg-gray-100 text-gray-900 w-auto p-8 absolute top-2" :class="show_top_pane ? 'z-9999' : ''">
<keep-alive>
<component :is="topPaneComponent" />
</keep-alive>
</div>
</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 -->
<draggable
v-if="useDragHeader"
@start="start_header"
@end="end_header"
>
<header class="flex-shrink-0 border-b dark:border-gray-700 border-gray-200">
<Navbar />
</header>
</draggable>
<div v-else>
<header class="flex-shrink-0 border-b dark:border-gray-700 border-gray-200">
<Navbar />
</header>
</div>
<!-- Main content -->
<main class="flex-1 max-h-full p-1 overflow-hidden overflow-y-scroll dark:bg-cool-gray-800 dark:text-white">
<slot />
</main>
<!-- Main footer -->
<draggable
@start="start_footer"
@end="end_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 === 'a-click'"
:href="itm.link"
class=""
>
{{ t(itm.title) }}
</a>
<router-link
v-if="itm && itm.type === 'router-link'"
:title="t(itm.title)"
:to="{ name: itm.name_to }"
class="flex items-center"
>
{{ 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 === 'a-click'"
:href="itm.link"
class=""
>
<span class="hidden text-sm md:block mr-2"> {{ t(itm.title) }} </span>
<span v-if="itm.img" class="w-11 h-11 dark:bg-gray-200 rounded-full mr-2">
<dsc-logo v-if="itm.img === 'dsc'" />
<img v-else :src="itm.img">
</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 rounded-full 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>
</router-link>
</span>
</div>
</footer>
</draggable>
</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
v-show="useDragFooter && show_bottom_pane"
id="pane-bottom"
:style="`height: ${bottom_data.h}px; top: ${bottom_data.y}px;`"
class="eexpandable p-3 z-999 bg-gray-200 text-gray-100 dark:bg-gray-800 dark:text-white absolute"
>
<Backdrop :show="show_bottom_pane" :blur="14" :css="`height: 100%$; top: ${bottom_data.y}px;`" @close="close_bottom_pane" />
<div class="bg-gray-100 w-screen text-gray-900 my-5 pr-5 z-999 absolute top-2">
<keep-alive>
<component :is="bottomPaneComponent" />
</keep-alive>
</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 { toRefs, onMounted, onUnmounted, ref, computed } from 'vue'
import { VueDraggableNext as draggable } from 'vue-draggable-next'
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 Backdrop from '@/global/Backdrop.vue'
import ReqErrorView from '@/ReqErrorView.vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import useState from '~/hooks/useState'
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, useDragHeader, useDragFooter } = 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, locale } = 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(() => {
return defs.value && defs.value.footer && defs.value.footer.left_items ? defs.value.footer.left_items : []
})
const right_items = computed(() => {
return defs.value && defs.value.footer && defs.value.footer.right_items ? defs.value.footer.right_items : []
})
onMounted(() => {
window.addEventListener('resize', checkScreen)
isCheckin.value = false
})
const head_data = ref({ x: 0, y: 0, h: 0 })
const start_header = (ev: any) => {
head_data.value = { x: ev.originalEvent.pageX, y: ev.originalEvent.pageY, h: 0 }
// console.log(`start header: ${ev.originalEvent.clientX} (${ev.originalEvent.screenX}) ${ev.originalEvent.pageX} / ${ev.originalEvent.clientY} (${ev.originalEvent.screenY}) ${ev.originalEvent.pageY} `)
}
const end_header = (ev: any) => {
// console.log(`end header: ${ev.originalEvent.clientX} (${ev.originalEvent.screenX}) ${ev.originalEvent.pageX} / ${ev.originalEvent.clientY} (${ev.originalEvent.screenY}) ${ev.originalEvent.pageY} `)
// console.log(`target: ${head_data.value.x - ev.originalEvent.pageX} ${ev.originalEvent.pageY - head_data.value.y} `)
head_data.value.h = ev.originalEvent.pageY - head_data.value.y
// console.log(head_data.value.h)
show_top_pane.value = true
}
const close_top_pane = () => {
show_top_pane.value = false
}
const bottom_data = ref({ x: 0, y: 0, h: 0 })
const start_footer = (ev: any) => {
if (useDragFooter.value)
bottom_data.value = { x: ev.originalEvent.pageX, y: ev.originalEvent.pageY, h: 0 }
}
const end_footer = (ev: any) => {
if (useDragFooter.value) {
// console.log(`end footer: ${ev.originalEvent.clientX} (${ev.originalEvent.screenX}) ${ev.originalEvent.pageX} / ${ev.originalEvent.clientY} (${ev.originalEvent.screenY}) ${ev.originalEvent.pageY} `)
// console.log(`target: ${bottom_data.value.x - ev.originalEvent.pageX} ${ev.originalEvent.pageY - bottom_data.value.y} `)
bottom_data.value.h = (ev.originalEvent.pageY - bottom_data.value.y) * -1
bottom_data.value.y = ev.originalEvent.pageY
// console.log(bottom_data.value.h)
// console.log(bottom_data.value.y)
show_bottom_pane.value = true
}
}
const close_bottom_pane = () => {
show_bottom_pane.value = false
}
const topPaneComponent = computed(() => useComponent().topPaneComponent.value)
const bottomPaneComponent = computed(() => useComponent().bottomPaneComponent.value)
const root_url = router.currentRoute.value.meta.rooturl || ''
onMounted(() => {
})
onUnmounted(() => {
window.removeEventListener('resize', checkScreen)
})
</script>
<style scoped>
.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>

9
src/layouts/default.vue Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
export const isDark = useDark()
export const toggleDark = useToggle(isDark)

1
src/logic/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './dark'

53
src/main.ts Normal file
View File

@ -0,0 +1,53 @@
// 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'
// 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'
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(store)
app.use(i18n)
app.use(routes)
// app.use(router)
app.use(head)
app.component('AppLayout', AppLayout)
// app.component('GuestLayout', GuestLayout)
app.mount('#app')

11
src/modules/README.md Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
<template>
<div>
Not Found
</div>
</template>

52
src/pages/base.vue Normal file
View 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
View 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>

191
src/router.ts Normal file
View File

@ -0,0 +1,191 @@
// 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 Register from '~/views/auth/Register.vue'
import Login from '~/views/Login.vue'
import Logout from '~/views/Logout.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 mapkey = to.meta.uimapkey || 'ui'
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 next('/login')
}
}
else { next('/login') }
}
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: true,
layout: 'AppLayout',
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: '/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
View 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
View 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)
// }

View 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)
},
}

View 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)
},
}

View 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 }

View 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)
}
},
}

View 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)
},
}

View 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)
},
}

View 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 }

View 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)
}
},
}

View 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)
},
}

View 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)
},
}

View 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 }

View 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)
}
},
}

View 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

View 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)
},
}

View 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
},
}

View 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 }

View 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
}
},
}

View 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)
},
}

View 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
},
}

View 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 }

View 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
}
},
}

View File

@ -0,0 +1 @@
export * from './app'

97
src/store/store_module.ts Normal file
View 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
View 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
}
*/

25
src/styles/main.css Executable file
View File

@ -0,0 +1,25 @@
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
html.dark {
background: #121212;
}
.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;
}

56
src/typs/clouds/index.ts Normal file
View File

@ -0,0 +1,56 @@
export interface StatusItemType {
[key: string]: any
title: string
text: string
isOpen: boolean
}
export interface StatusItemDataType {
[key: string]: any
title: string
text: 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 ResCloudDataCheck {
group_cloud: CloudGroupSrvcType
group_apps: CloudGroupSrvcType
statusentries: StatusItemDataType[]
}

13
src/typs/index.ts Normal file
View File

@ -0,0 +1,13 @@
export enum MessageType {
Show,
Success,
Error,
Warning,
Info,
}
export interface MenuItemType {
id: string
title: string
active: boolean
link: string
}

26
src/views/404.vue Normal file
View 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>

168
src/views/Home.vue Normal file
View File

@ -0,0 +1,168 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import CloudGroups from '~/components/CloudGroups.vue'
// import ProfileView from '~/components/ProfileView.vue'
import NavMenu from '~/components/NavMenu.vue'
import AccordionView from '~/components/AccordionView.vue'
import { load_data } from '~/hooks/utils'
// import { translate, show_message } from '~/hooks/utils'
import useState from '~/hooks/useState'
import { StatusItemType, StatusItemDataType, ResCloudDataCheck, CloudGroupServcType } from '~/typs/clouds'
import { MenuItemType } from '~/typs'
const title = ref('Services Status')
const search = ref('')
// const router = useRouter()
// const go = () => {
// if (name.value)
// router.push(`/hi/${encodeURIComponent(name.value)}`)
// }
const menu_items = useState().menu_items
const on_search = () => {
}
const tasks_hide = ref(false)
const apps_hide = ref(false)
const on_cloud_group = (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 on_nav_menu = (item: MenuItemType) => {
if (item) {
const dom_id = document.getElementById(item.id)
if (dom_id) {
dom_id.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
setTimeout(() => window.scrollBy(0, -40), 1000)
}
menu_items.value = menu_items.value.map(i => ({ ...i, active: i.id !== item.id ? false : !i.active }))
switch (item.id) {
case useState().tsksrvcs:
tasks_hide.value = false
break
case useState().appsrvcs:
apps_hide.value = false
break
default:
}
}
}
const status_entries = ref([] as StatusItemType[])
const statusitems: StatusItemType[] = [
{
title: 'Why do I need Rust?',
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores iure quas laudantium dicta impedit, est id delectus molestiae deleniti enim nobis rem et nihil.',
isOpen: true,
},
{
title: 'Why am I so awesome?',
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi cumque, nulla harum aspernatur veniam ullam provident neque temporibus autem itaque odit.',
isOpen: false,
},
{
title: 'Why learn on THIS?',
text: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eligendi cumque, nulla harum aspernatur veniam ullam provident neque temporibus autem itaque odit.',
isOpen: false,
},
]
const groups = ref(new Map() as Map<string, Map<string, CloudGroupServcType>>)
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`
await load_data(url, 0, 2000, (res: ResCloudDataCheck) => {
// use Map to keep objects order
// use Object.keys + sort to map same result order
if (res.group_apps) {
Object.keys(res.group_apps).sort().forEach((grp_key) => {
const data: Map<string, CloudGroupServcType> = new Map()
Object.keys(res.group_apps[grp_key]).sort().forEach((key) => {
data.set(key, res.group_apps[grp_key][key])
})
groups.value.set(grp_key, data)
})
}
if (res.group_cloud) {
Object.keys(res.group_cloud).forEach((grp_key) => {
Object.keys(res.group_cloud[grp_key]).sort().forEach((key) => {
const data: CloudGroupServcType[] = res.group_cloud[grp_key][key]
data.forEach((it, index) => {
if (groups.value.get(grp_key)) {
const m = groups.value.get(grp_key)
if (m && 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)
groups.value.set(grp_key, new_data)
}
}
}
})
})
})
}
if (res.statusentries) {
const statusentries: StatusItemType[] = []
res.statusentries.forEach((it: StatusItemDataType) => {
statusentries.push({ ...it, isOpen: false })
})
status_entries.value = statusentries
}
else {
status_entries.value = statusitems
}
})
})
</script>
<template>
<NavMenu :title="title" :items="menu_items" @on-nav-menu="on_nav_menu">
<template #search>
<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="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>
</NavMenu>
<div class="mt-1 text-gray-800 p-2 lg:p-4">
<CloudGroups
title="Tasks"
target="tsksrvcs"
:groups="groups"
:search="search"
:hide="tasks_hide"
@on-cloud-group="on_cloud_group"
/>
<CloudGroups
title="Apps"
target="appsrvcs"
:groups="groups"
:search="search"
:hide="apps_hide"
@on-cloud-group="on_cloud_group"
/>
</div>
<div class="shadow-lg mx-auto bg-gray-200 text-gray-800 mt-4 md:mt-2 bg-light-800 w-full p-2 rounded-l">
<AccordionView :items="statusitems" title="Services Status" />
</div>
</template>

470
src/views/Login.vue Normal file

File diff suppressed because one or more lines are too long

130
src/views/Logout.vue Normal file
View File

@ -0,0 +1,130 @@
<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>
<section class="page-w bg-gray-200 h-full flex content-center flex-wrap ">
<figure class="p-1 login w-[calc(100%+1rem)]">
<div class="card ~neutral !low p-0 max-w-sm dark:bg-gray-900 ">
<div class="flex flex-row justify-center flex-none">
<div class="order-last flex-shrink-0">
<MenuLocales :label-mode="localesLabelMode" :include-current="includeCurrLocale" />
</div>
<div class="p-1 ml-8 flex flex-shrink-0 justify-center flex-grow bg-transparent">
<router-link class="text-white no-underline" to="/" >
<span v-if="use_logo" class="text-lg rounded-full">
<app-logo-img class="w-35 h-35 " />
</span>
<h2 v-else class="text-center mb-1 text-lg heading ~info">
{{ t('login.wellcome') }}
</h2>
</router-link>
</div>
</div>
<div v-if="use_logo" class="-mt-2 flex flex-row justify-center flex-none">
<span class="mb-1 text-lg ">
<app-logo-text class="h-12" />
</span>
</div>
<div class="p-2">
<p class="text-center mb-3 text-base support ~info dark:text-gray-100">
{{ t('login.subtitle') }}
</p>
<div class="container">
<h5 class="text-center subheading mb-4 dark:text-gray-200">
{{ t('login.end_session') }}
</h5>
<div class="messages global">
</div>
<div class="alternative-actions border-t border-b flex gap-4 py-2 section ~info justify-center dark:bg-gray-600 ">
<!-- <a href="auth/registration">Recover password</a> -->
<h3 class=" ~critical text-xl p-4 dark:text-gray-100">
{{ t('login.see_you_soon','') }}
</h3>
</div>
<div class="mt-8 flex justify-center">
<button class="text-lg button ~info " type="submit" @click.prevent="signIn">
{{ t('login.signin') }}
</button>
</div>
</div>
</div>
</div>
</figure>
</section>
</div>
</template>
<script setup lang="ts">
import {
ref,
onMounted,
} from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import MenuLocales from '@/menus/MenuLocales.vue'
import AppLogoImg from '@/icons/AppImg.vue'
import AppLogoText from '@/icons/AppLogoText.vue'
import { AppDefsAction } from '~/store/types'
// import useState from '~/hooks/useState'
enum LocalesLabelModes {
value = 'val',
translation = 'trans',
}
const router = useRouter()
const root_url = router.currentRoute.value.meta.rooturl || ''
const map_key = router.currentRoute.value.meta.uiMapkey || 'ui'
const use_logo = router.currentRoute.value.meta.use_logo
const { t, } = useI18n()
const store = useStore()
// const lang = computed(() => store.state.app_lang.lang)
const includeCurrLocale = true // computed(() => appDefs.value.includeCurrLocale || false)
const localesLabelMode = ref(LocalesLabelModes.translation as LocalesLabelModes)
//const errorMessage = ref('')
const isCheckin = ref(false)
const signIn = async() => {
router.push('/login')
}
onMounted(() => {
localStorage.removeItem('auth')
if (store && store.state && store.state.app_defs && store.state.app_defs.main) {
const uidefs = store.state.app_defs.main.get(map_key)
if (uidefs)
store.dispatch(AppDefsAction.removeDefs, uidefs)
}
// location.reload()
})
</script>
<style scoped>
.page-w {
min-width: 100vw;
/* position: absolute;
top: 0;
*/
min-height: 100vh;
color: var(--primary-light);
display: flex;
flex-direction: column;
justify-content: center;
}
.login {
/* position: absolute;*/
/* top: 45%;*/
/* left: 50%;*/
/* margin-right: -50%; */
/* transform: translate(-50%, -50%) */
/* margin: 0 auto; */
}
</style>