feat(admin): migrate locale + login + storage views to vue 3 composable

parent 0c522c17
# Based of https://github.com/microsoft/vscode-dev-containers/blob/main/containers/javascript-node/.devcontainer/base.Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster
ARG VARIANT=16-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
ARG VARIANT=18-bullseye
FROM node:${VARIANT}
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
ENV DEBIAN_FRONTEND=noninteractive
# [Optional] Uncomment if you want to install an additional version of node using nvm
# ARG EXTRA_NODE_VERSION=10
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
# Copy library scripts to execute
ADD https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/common-debian.sh /tmp/library-scripts/
ADD https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/node-debian.sh /tmp/library-scripts/
ADD https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/meta.env /tmp/library-scripts/
# [Optional] Uncomment if you want to install more global node modules
# RUN su node -c "npm install -g <your-package-list-here>"
# [Option] Install zsh
ARG INSTALL_ZSH="true"
# [Option] Upgrade OS packages to their latest versions
ARG UPGRADE_PACKAGES="true"
EXPOSE 3000
# Install needed packages, yarn, nvm and setup non-root user. Use a separate RUN statement to add your own dependencies.
ARG USERNAME=node
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ARG NPM_GLOBAL=/usr/local/share/npm-global
ENV NVM_DIR=/usr/local/share/nvm
ENV NVM_SYMLINK_CURRENT=true \
PATH=${NPM_GLOBAL}/bin:${NVM_DIR}/current/bin:${PATH}
RUN apt-get update \
# Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
&& apt-get purge -y imagemagick imagemagick-6-common \
# Install common packages, non-root user, update yarn and install nvm
&& bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" \
# Install yarn, nvm
&& rm -rf /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
&& bash /tmp/library-scripts/node-debian.sh "${NVM_DIR}" "none" "${USERNAME}" \
# Configure global npm install location, use group to adapt to UID/GID changes
&& if ! cat /etc/group | grep -e "^npm:" > /dev/null 2>&1; then groupadd -r npm; fi \
&& usermod -a -G npm ${USERNAME} \
&& umask 0002 \
&& mkdir -p ${NPM_GLOBAL} \
&& touch /usr/local/etc/npmrc \
&& chown ${USERNAME}:npm ${NPM_GLOBAL} /usr/local/etc/npmrc \
&& chmod g+s ${NPM_GLOBAL} \
&& npm config -g set prefix ${NPM_GLOBAL} \
&& sudo -u ${USERNAME} npm config -g set prefix ${NPM_GLOBAL} \
# Install eslint
&& su ${USERNAME} -c "umask 0002 && npm install -g eslint" \
&& npm cache clean --force > /dev/null 2>&1 \
# Install python-is-python3 on bullseye to prevent node-gyp regressions
&& . /etc/os-release \
&& if [ "${VERSION_CODENAME}" = "bullseye" ]; then apt-get -y install --no-install-recommends python-is-python3; fi \
# Clean up
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /root/.gnupg /tmp/library-scripts
ENV DEBIAN_FRONTEND=noninteractive
EXPOSE 3000
# Add Docker Source
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
......@@ -33,6 +70,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -
git \
gnupg2 \
nano \
netcat \
pandoc \
unzip \
wget
......@@ -48,7 +86,7 @@ ENV npm_config_fund false
RUN sed -i 's/#force_color_prompt=/force_color_prompt=/' /root/.bashrc
# Fetch wait-for utility
ADD https://raw.githubusercontent.com/eficode/wait-for/v2.1.3/wait-for /usr/local/bin/
ADD https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for /usr/local/bin/
RUN chmod +rx /usr/local/bin/wait-for
# Copy the startup file
......
......@@ -2,7 +2,11 @@
cd /workspace
echo "Disabling git info in terminal..."
git config codespaces-theme.hide-status 1
git config oh-my-zsh.hide-info 1
echo "Waiting for DB container to come online..."
/usr/local/bin/wait-for localhost:5432 -- echo "DB ready"
echo "Ready!"
......@@ -32,10 +32,10 @@
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"eamodio.gitlens",
"johnsoncodehk.volar",
"Vue.volar",
"oderwat.indent-rainbow",
"redhat.vscode-yaml",
"visualstudioexptteam.vscodeintellicode",
"VisualStudioExptTeam.vscodeintellicode",
"editorconfig.editorconfig",
"lokalise.i18n-ally",
"mrmlnc.vscode-duplicate",
......
......@@ -6,10 +6,10 @@ services:
context: .
dockerfile: Dockerfile
args:
# Update 'VARIANT' to pick an LTS version of Node.js: 16, 14, 12.
# Update 'VARIANT' to pick an LTS version of Node.js: 18, 16, 14, 12.
# Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: 16-bullseye
VARIANT: 18-bullseye
volumes:
- ..:/workspace
......
const _ = require('lodash')
const cfgHelper = require('../helpers/config')
const Promise = require('bluebird')
const fs = require('fs-extra')
const path = require('path')
......@@ -11,71 +8,13 @@ module.exports = {
channel: 'BETA',
version: WIKI.version,
releaseDate: WIKI.releaseDate,
minimumVersionRequired: '2.0.0-beta.0',
minimumNodeRequired: '10.12.0'
minimumVersionRequired: '3.0.0-beta.0',
minimumNodeRequired: '18.0.0'
},
init() {
// Clear content cache
fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
return this
},
/**
* Upgrade from WIKI.js 1.x - MongoDB database
*
* @param {Object} opts Options object
*/
async upgradeFromMongo (opts) {
WIKI.logger.info('Upgrading from MongoDB...')
let mongo = require('mongodb').MongoClient
let parsedMongoConStr = cfgHelper.parseConfigValue(opts.mongoCnStr)
return new Promise((resolve, reject) => {
// Connect to MongoDB
mongo.connect(parsedMongoConStr, {
autoReconnect: false,
reconnectTries: 2,
reconnectInterval: 1000,
connectTimeoutMS: 5000,
socketTimeoutMS: 5000
}, async (err, db) => {
try {
if (err !== null) { throw err }
let users = db.collection('users')
// Check if users table is populated
let userCount = await users.count()
if (userCount < 2) {
throw new Error('MongoDB Upgrade: Users table is empty!')
}
// Import all users
let userData = await users.find({
email: {
$not: 'guest'
}
}).toArray()
await WIKI.models.User.bulkCreate(_.map(userData, usr => {
return {
email: usr.email,
name: usr.name || 'Imported User',
password: usr.password || '',
provider: usr.provider || 'local',
providerId: usr.providerId || '',
role: 'user',
createdAt: usr.createdAt
}
}))
resolve(true)
} catch (errc) {
reject(errc)
}
db.close()
})
})
}
}
......@@ -8,8 +8,8 @@ const { nanoid } = require('nanoid')
const { DateTime } = require('luxon')
const semver = require('semver')
if (!semver.satisfies(process.version, '>=16')) {
console.error('ERROR: Node.js 16.x or later required!')
if (!semver.satisfies(process.version, '>=18')) {
console.error('ERROR: Node.js 18.x or later required!')
process.exit(1)
}
......
......@@ -11,8 +11,8 @@
"lint": "eslint --ext .js,.vue ./"
},
"dependencies": {
"@apollo/client": "3.6.1",
"@codemirror/autocomplete": "0.20.0",
"@apollo/client": "3.6.4",
"@codemirror/autocomplete": "0.20.1",
"@codemirror/basic-setup": "0.20.0",
"@codemirror/closebrackets": "0.19.2",
"@codemirror/commands": "0.20.0",
......@@ -25,15 +25,15 @@
"@codemirror/lang-html": "0.20.0",
"@codemirror/lang-javascript": "0.20.0",
"@codemirror/lang-json": "0.20.0",
"@codemirror/lang-markdown": "0.20.0",
"@codemirror/lang-markdown": "0.20.1",
"@codemirror/matchbrackets": "0.19.4",
"@codemirror/search": "0.20.1",
"@codemirror/state": "0.20.0",
"@codemirror/tooltip": "0.19.16",
"@codemirror/view": "0.20.3",
"@codemirror/view": "0.20.6",
"@lezer/common": "0.16.0",
"@quasar/extras": "1.13.6",
"@tiptap/core": "2.0.0-beta.175",
"@quasar/extras": "1.14.0",
"@tiptap/core": "2.0.0-beta.176",
"@tiptap/extension-code-block": "2.0.0-beta.37",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
"@tiptap/extension-color": "2.0.0-beta.9",
......@@ -44,9 +44,9 @@
"@tiptap/extension-highlight": "2.0.0-beta.33",
"@tiptap/extension-history": "2.0.0-beta.21",
"@tiptap/extension-image": "2.0.0-beta.27",
"@tiptap/extension-mention": "2.0.0-beta.96",
"@tiptap/extension-mention": "2.0.0-beta.97",
"@tiptap/extension-placeholder": "2.0.0-beta.48",
"@tiptap/extension-table": "2.0.0-beta.48",
"@tiptap/extension-table": "2.0.0-beta.49",
"@tiptap/extension-table-cell": "2.0.0-beta.20",
"@tiptap/extension-table-header": "2.0.0-beta.22",
"@tiptap/extension-table-row": "2.0.0-beta.19",
......@@ -55,38 +55,38 @@
"@tiptap/extension-text-align": "2.0.0-beta.29",
"@tiptap/extension-text-style": "2.0.0-beta.23",
"@tiptap/extension-typography": "2.0.0-beta.20",
"@tiptap/starter-kit": "2.0.0-beta.184",
"@tiptap/starter-kit": "2.0.0-beta.185",
"@tiptap/vue-3": "2.0.0-beta.91",
"@vue/apollo-option": "4.0.0-alpha.16",
"@vue/apollo-option": "4.0.0-alpha.17",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.29.4",
"clipboard": "2.0.10",
"browser-fs-access": "0.29.5",
"clipboard": "2.0.11",
"filesize": "8.0.7",
"filesize-parser": "1.5.0",
"graphql": "16.4.0",
"graphql": "16.5.0",
"graphql-tag": "2.12.6",
"js-cookie": "3.0.1",
"jwt-decode": "3.1.2",
"lodash": "4.17.21",
"luxon": "2.3.2",
"pinia": "2.0.13",
"luxon": "2.4.0",
"pinia": "2.0.14",
"pug": "3.0.2",
"quasar": "2.6.6",
"quasar": "2.7.0",
"tippy.js": "6.3.7",
"uuid": "8.3.2",
"v-network-graph": "0.5.13",
"v-network-graph": "0.5.16",
"vue": "3.2.31",
"vue-i18n": "9.1.9",
"vue-router": "4.0.14",
"vue-i18n": "9.1.10",
"vue-router": "4.0.15",
"vuedraggable": "4.1.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "3.4.0",
"@quasar/app-vite": "1.0.0-beta.14",
"@quasar/app-vite": "1.0.0",
"@types/lodash": "4.14.182",
"autoprefixer": "10.4.5",
"eslint": "8.14.0",
"autoprefixer": "10.4.7",
"eslint": "8.16.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.2.0",
......
<template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 850px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-down.svg', left, size='sm')
span {{$t(`admin.locale.downloadTitle`)}}
span {{t(`admin.locale.downloadTitle`)}}
q-card-section.q-pa-none
q-table.no-border-radius(
:data='locales'
:data='state.locales'
:columns='headers'
row-name='code'
flat
hide-bottom
:rows-per-page-options='[0]'
:loading='loading > 0'
:loading='state.loading > 0'
)
template(v-slot:body-cell-code='props')
q-td(:props='props')
......@@ -81,33 +81,52 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-space
q-btn.acrylic-btn(
flat
:label='$t(`common.actions.close`)'
:label='t(`common.actions.close`)'
color='grey'
padding='xs md'
@click='hide'
@click='onDialogCancel'
)
q-inner-loading(:showing='loading')
q-inner-loading(:showing='state.loading > 0')
q-spinner(color='accent', size='lg')
</template>
<script>
// import gql from 'graphql-tag'
// import cloneDeep from 'lodash/cloneDeep'
<script setup>
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive, ref } from 'vue'
export default {
emits: ['ok', 'hide'],
data () {
return {
import { useAdminStore } from '../stores/admin'
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
locales: [],
loading: 0
}
},
computed: {
headers () {
return [
})
const headers = [
{
label: this.$t('admin.locale.code'),
label: t('admin.locale.code'),
align: 'left',
field: 'code',
name: 'code',
......@@ -115,21 +134,21 @@ export default {
style: 'width: 90px'
},
{
label: this.$t('admin.locale.name'),
label: t('admin.locale.name'),
align: 'left',
field: 'name',
name: 'name',
sortable: true
},
{
label: this.$t('admin.locale.nativeName'),
label: t('admin.locale.nativeName'),
align: 'left',
field: 'nativeName',
name: 'nativeName',
sortable: true
},
{
label: this.$t('admin.locale.rtl'),
label: t('admin.locale.rtl'),
align: 'center',
field: 'isRTL',
name: 'isRTL',
......@@ -137,7 +156,7 @@ export default {
style: 'width: 10px'
},
{
label: this.$t('admin.locale.availability'),
label: t('admin.locale.availability'),
align: 'center',
field: 'availability',
name: 'availability',
......@@ -145,26 +164,18 @@ export default {
style: 'width: 120px'
},
{
label: this.$t('admin.locale.download'),
label: t('admin.locale.download'),
align: 'center',
field: 'isInstalled',
name: 'isInstalled',
sortable: false,
style: 'width: 100px'
}
]
}
},
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
}
}
]
// METHODS
async function download (lc) {
}
</script>
......@@ -16,7 +16,7 @@ q-page.admin-flags
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate'
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
......
......@@ -11,7 +11,7 @@ q-page.admin-flags
icon='las la-question-circle'
flat
color='grey'
href='https://docs.requarks.io/admin/flags'
href='https://docs.js.wiki/admin/flags'
target='_blank'
type='a'
)
......
......@@ -16,7 +16,7 @@ q-page.admin-general
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate'
icon='las la-redo-alt'
flat
color='secondary'
:loading='state.loading > 0'
......@@ -385,7 +385,6 @@ import { onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
import { useRoute, useRouter } from 'vue-router'
// QUASAR
......@@ -397,11 +396,6 @@ const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N
const { t } = useI18n()
......
......@@ -4,15 +4,15 @@ q-page.admin-locale
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-language.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.locale.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.locale.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.locale.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.locale.subtitle') }}
.col-auto.flex
q-btn.q-mr-md(
icon='las la-download'
:label='$t(`admin.locale.downloadNew`)'
:label='t(`admin.locale.downloadNew`)'
unelevated
color='primary'
:disabled='loading > 0'
:disabled='state.loading > 0'
@click='installNewLocale'
)
q-separator.q-mr-md(vertical)
......@@ -20,7 +20,7 @@ q-page.admin-locale
icon='las la-question-circle'
flat
color='grey'
href='https://docs.requarks.io/admin/locale'
href='https://docs.js.wiki/admin/locale'
target='_blank'
type='a'
)
......@@ -28,16 +28,16 @@ q-page.admin-locale
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='state.loading > 0'
@click='load'
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
:disabled='state.loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
......@@ -47,37 +47,37 @@ q-page.admin-locale
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{$t('admin.locale.settings')}}
.text-subtitle1 {{t('admin.locale.settings')}}
q-item
blueprint-icon(icon='translation')
q-item-section
q-item-label {{namespacing ? $t(`admin.locale.base.labelWithNS`) : $t(`admin.locale.base.label`)}}
q-item-label(caption) {{$t(`admin.locale.base.hint`)}}
q-item-label {{state.namespacing ? t(`admin.locale.base.labelWithNS`) : t(`admin.locale.base.label`)}}
q-item-label(caption) {{t(`admin.locale.base.hint`)}}
q-item-section
q-select(
outlined
v-model='selectedLocale'
v-model='state.selectedLocale'
:options='installedLocales'
option-value='code'
option-label='name'
emit-value
map-options
dense
:aria-label='$t(`admin.locale.base.label`)'
:aria-label='t(`admin.locale.base.label`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='unit')
q-item-section
q-item-label {{$t(`admin.locale.namespaces.label`)}}
q-item-label(caption) {{$t(`admin.locale.namespaces.hint`)}}
q-item-label {{t(`admin.locale.namespaces.label`)}}
q-item-label(caption) {{t(`admin.locale.namespaces.hint`)}}
q-item-section(avatar)
q-toggle(
v-model='namespacing'
v-model='state.namespacing'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.locale.namespaces.label`)'
:aria-label='t(`admin.locale.namespaces.label`)'
)
q-item
q-item-section
......@@ -86,21 +86,21 @@ q-page.admin-locale
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section
span {{ $t('admin.locale.namespacingPrefixWarning.title', { langCode: selectedLocale }) }}
.text-caption.text-yellow-1 {{ $t('admin.locale.namespacingPrefixWarning.subtitle') }}
span {{ t('admin.locale.namespacingPrefixWarning.title', { langCode: state.selectedLocale }) }}
.text-caption.text-yellow-1 {{ t('admin.locale.namespacingPrefixWarning.subtitle') }}
.col-5
//- -----------------------
//- Namespacing
//- -----------------------
q-card.shadow-1.q-pb-sm(v-if='namespacing')
q-card.shadow-1.q-pb-sm(v-if='state.namespacing')
q-card-section
.text-subtitle1 {{$t('admin.locale.activeNamespaces')}}
.text-subtitle1 {{t('admin.locale.activeNamespaces')}}
q-item(
v-for='(lc, idx) of installedLocales'
:key='lc.code'
:tag='lc.code !== selectedLocale ? `label` : null'
:tag='lc.code !== state.selectedLocale ? `label` : null'
)
blueprint-icon(:text='lc.code')
q-item-section
......@@ -108,8 +108,8 @@ q-page.admin-locale
q-item-label(caption) {{lc.nativeName}}
q-item-section(avatar)
q-toggle(
:disable='lc.code === selectedLocale'
v-model='namespaces'
:disable='lc.code === state.selectedLocale'
v-model='state.namespaces'
:val='lc.code'
color='primary'
checked-icon='las la-check'
......@@ -121,8 +121,8 @@ q-page.admin-locale
//- q-item
//- blueprint-icon(icon='test-passed')
//- q-item-section
//- q-item-label {{$t(`admin.locale.activeNamespaces.label`)}}
//- q-item-label(caption) {{$t(`admin.locale.activeNamespaces.hint`)}}
//- q-item-label {{t(`admin.locale.activeNamespaces.label`)}}
//- q-item-label(caption) {{t(`admin.locale.activeNamespaces.hint`)}}
//- q-item-section
//- q-select(
//- outlined
......@@ -136,71 +136,88 @@ q-page.admin-locale
//- emit-value
//- map-options
//- dense
//- :aria-label='$t(`admin.locale.activeNamespaces.label`)'
//- :aria-label='t(`admin.locale.activeNamespaces.label`)'
//- )
</template>
<script>
import { get } from 'vuex-pathify'
<script setup>
import gql from 'graphql-tag'
import filter from 'lodash/filter'
import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep'
import { createMetaMixin } from 'quasar'
import LocaleInstallDialog from '../components/LocaleInstallDialog.vue'
export default {
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.locale.title')
}
})
],
data () {
return {
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.locale.title')
})
// DATA
const state = reactive({
loading: 0,
locales: [],
selectedLocale: 'en',
namespacing: false,
namespaces: []
})
// COMPUTED
const installedLocales = computed(() => {
return filter(state.locales, ['isInstalled', true])
})
// WATCHERS
watch(() => adminStore.currentSiteId, (newValue) => {
load()
})
watch(() => state.selectedLocale, (newValue) => {
if (!state.namespaces.includes(newValue)) {
state.namespaces.push(newValue)
}
},
computed: {
currentSiteId: get('admin/currentSiteId', false),
installedLocales () {
return filter(this.locales, ['isInstalled', true])
}
},
watch: {
currentSiteId (newValue) {
this.load()
},
selectedLocale (newValue) {
if (!this.namespaces.includes(newValue)) {
this.namespaces.push(newValue)
}
}
},
mounted () {
if (this.currentSiteId) {
this.load()
}
},
methods: {
installNewLocale () {
this.$q.dialog({
})
// METHODS
function installNewLocale () {
$q.dialog({
component: LocaleInstallDialog
}).onOk(() => {
this.load()
})
},
async load () {
this.loading++
this.$q.loading.show()
const resp = await this.$apollo.query({
}
async function load () {
state.loading++
$q.loading.show()
const resp = await APOLLO_CLIENT.query({
query: gql`
query getLocales ($siteId: UUID!) {
locales {
......@@ -225,23 +242,24 @@ export default {
}
`,
variables: {
siteId: this.currentSiteId
siteId: adminStore.currentSiteId
},
fetchPolicy: 'network-only'
})
this.locales = cloneDeep(resp?.data?.locales)
this.selectedLocale = cloneDeep(resp?.data?.siteById?.locale)
this.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing)
this.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces)
if (!this.namespaces.includes(this.selectedLocale)) {
this.namespaces.push(this.selectedLocale)
state.locales = cloneDeep(resp?.data?.locales)
state.selectedLocale = cloneDeep(resp?.data?.siteById?.locale)
state.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing)
state.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces)
if (!state.namespaces.includes(state.selectedLocale)) {
state.namespaces.push(state.selectedLocale)
}
this.$q.loading.hide()
this.loading--
},
async download (lc) {
$q.loading.hide()
state.loading--
}
async function download (lc) {
lc.isDownloading = true
const respRaw = await this.$apollo.mutate({
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation downloadLocale ($locale: String!) {
localization {
......@@ -266,22 +284,22 @@ export default {
lc.isInstalled = true
lc.updatedAt = new Date().toISOString()
lc.installDate = lc.updatedAt
this.$store.commit('showNotification', {
$q.notify({
message: `Locale ${lc.name} has been installed successfully.`,
style: 'success',
icon: 'get_app'
type: 'positive'
})
} else {
this.$q.notify({
$q.notify({
type: 'negative',
message: resp.message
})
}
this.isDownloading = false
},
async save () {
this.loading = true
const respRaw = await this.$apollo.mutate({
state.isDownloading = false
}
async function save () {
state.loading = true
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveLocaleSettings (
$locale: String!
......@@ -307,18 +325,18 @@ export default {
}
`,
variables: {
locale: this.selectedLocale,
autoUpdate: this.autoUpdate,
namespacing: this.namespacing,
namespaces: this.namespaces
locale: state.selectedLocale,
autoUpdate: state.autoUpdate,
namespacing: state.namespacing,
namespaces: state.namespaces
}
})
const resp = _get(respRaw, 'data.localization.updateLocale.responseResult', {})
if (resp.succeeded) {
// Change UI language
this.$i18n.locale = this.selectedLocale
this.$i18n.locale = state.selectedLocale
this.$q.notify({
$q.notify({
type: 'positive',
message: 'Locale settings updated successfully.'
})
......@@ -327,13 +345,19 @@ export default {
window.location.reload(true)
}, 1000)
} else {
this.$q.notify({
$q.notify({
type: 'negative',
message: resp.message
})
}
this.loading = false
}
}
state.loading = false
}
// MOUNTED
onMounted(() => {
if (adminStore.currentSiteId) {
load()
}
})
</script>
......@@ -4,8 +4,8 @@ q-page.admin-login
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bunch-of-keys.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.login.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.login.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.login.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.login.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
......@@ -19,16 +19,16 @@ q-page.admin-login
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='state.loading > 0'
@click='load'
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
:disabled='state.loading > 0'
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
......@@ -38,12 +38,12 @@ q-page.admin-login
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{$t('admin.login.experience')}}
.text-subtitle1 {{t('admin.login.experience')}}
q-item(tag='label', v-ripple)
blueprint-icon(icon='full-image', indicator, :indicator-text='$t(`admin.extensions.requiresSharp`)')
blueprint-icon(icon='full-image', indicator, :indicator-text='t(`admin.extensions.requiresSharp`)')
q-item-section
q-item-label {{$t(`admin.login.background`)}}
q-item-label(caption) {{$t(`admin.login.backgroundHint`)}}
q-item-label {{t(`admin.login.background`)}}
q-item-label(caption) {{t(`admin.login.backgroundHint`)}}
q-item-section.col-auto
q-btn(
label='Upload'
......@@ -56,80 +56,80 @@ q-page.admin-login
q-item(tag='label', v-ripple)
blueprint-icon(icon='close-pane')
q-item-section
q-item-label {{$t(`admin.login.bypassScreen`)}}
q-item-label(caption) {{$t(`admin.login.bypassScreenHint`)}}
q-item-label {{t(`admin.login.bypassScreen`)}}
q-item-label(caption) {{t(`admin.login.bypassScreenHint`)}}
q-item-section(avatar)
q-toggle(
v-model='config.authAutoLogin'
v-model='state.config.authAutoLogin'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.login.bypassScreen`)'
:aria-label='t(`admin.login.bypassScreen`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='no-access')
q-item-section
q-item-label {{$t(`admin.login.bypassUnauthorized`)}}
q-item-label(caption) {{$t(`admin.login.bypassUnauthorizedHint`)}}
q-item-label {{t(`admin.login.bypassUnauthorized`)}}
q-item-label(caption) {{t(`admin.login.bypassUnauthorizedHint`)}}
q-item-section(avatar)
q-toggle(
v-model='config.authBypassUnauthorized'
v-model='state.config.authBypassUnauthorized'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.login.bypassUnauthorized`)'
:aria-label='t(`admin.login.bypassUnauthorized`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='double-right')
q-item-section
q-item-label {{$t(`admin.login.loginRedirect`)}}
q-item-label(caption) {{$t(`admin.login.loginRedirectHint`)}}
q-item-label {{t(`admin.login.loginRedirect`)}}
q-item-label(caption) {{t(`admin.login.loginRedirectHint`)}}
q-item-section
q-input(
outlined
v-model='config.loginRedirect'
v-model='state.config.loginRedirect'
dense
:rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.loginRedirectInvalidChars')
val => state.invalidCharsRegex.test(val) || t('admin.login.loginRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='$t(`admin.login.loginRedirect`)'
:aria-label='t(`admin.login.loginRedirect`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='chevron-right')
q-item-section
q-item-label {{$t(`admin.login.welcomeRedirect`)}}
q-item-label(caption) {{$t(`admin.login.welcomeRedirectHint`)}}
q-item-label {{t(`admin.login.welcomeRedirect`)}}
q-item-label(caption) {{t(`admin.login.welcomeRedirectHint`)}}
q-item-section
q-input(
outlined
v-model='config.welcomeRedirect'
v-model='state.config.welcomeRedirect'
dense
:rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.welcomeRedirectInvalidChars')
val => state.invalidCharsRegex.test(val) || t('admin.login.welcomeRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='$t(`admin.login.welcomeRedirect`)'
:aria-label='t(`admin.login.welcomeRedirect`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='exit')
q-item-section
q-item-label {{$t(`admin.login.logoutRedirect`)}}
q-item-label(caption) {{$t(`admin.login.logoutRedirectHint`)}}
q-item-label {{t(`admin.login.logoutRedirect`)}}
q-item-label(caption) {{t(`admin.login.logoutRedirectHint`)}}
q-item-section
q-input(
outlined
v-model='config.logoutRedirect'
v-model='state.config.logoutRedirect'
dense
:rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.logoutRedirectInvalidChars')
val => state.invalidCharsRegex.test(val) || t('admin.login.logoutRedirectInvalidChars')
]`
hide-bottom-space
:aria-label='$t(`admin.login.logoutRedirect`)'
:aria-label='t(`admin.login.logoutRedirect`)'
)
.col-12.col-lg-6
......@@ -138,11 +138,11 @@ q-page.admin-login
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{$t('admin.login.providers')}}
.text-subtitle1 {{t('admin.login.providers')}}
q-card-section.admin-login-providers.q-pt-none
draggable(
class='q-list rounded-borders'
:list='providers'
:list='state.providers'
:animation='150'
handle='.handle'
@end='dragStarted = false'
......@@ -171,30 +171,43 @@ q-page.admin-login
q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm')
q-card-section.text-caption {{ $t('admin.login.providersVisbleWarning') }}
q-card-section.text-caption {{ t('admin.login.providersVisbleWarning') }}
</template>
<script>
<script setup>
import { get } from 'vuex-pathify'
import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag'
import draggable from 'vuedraggable'
import { createMetaMixin } from 'quasar'
export default {
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.login.title')
}
})
],
components: {
draggable
},
data () {
return {
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin'
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.login.title')
})
// DATA
const state = reactive({
invalidCharsRegex: /^[^<>"]+$/,
loading: 0,
config: {
......@@ -210,16 +223,14 @@ export default {
{ id: 'google', label: 'Google', provider: 'Google', icon: 'google', isActive: true },
{ id: 'slack', label: 'Slack', provider: 'Slack', icon: 'slack', isActive: false }
]
}
},
computed: {
currentSiteId: get('admin/currentSiteId', false)
},
methods: {
async load () {
this.loading++
this.$q.loading.show()
// const resp = await this.$apollo.query({
})
// METHODS
async function load () {
state.loading++
$q.loading.show()
// const resp = await APOLLO_CLIENT.query({
// query: gql`
// query getSite (
// $id: UUID!
......@@ -232,17 +243,18 @@ export default {
// }
// `,
// variables: {
// id: this.currentSiteId
// id: adminStore.currentSiteId
// },
// fetchPolicy: 'network-only'
// })
// this.config = cloneDeep(resp?.data?.siteById)
this.$q.loading.hide()
this.loading--
},
async save () {
$q.loading.hide()
state.loading--
}
async function save () {
try {
await this.$apollo.mutate({
await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveLoginSettings (
$authAutoLogin: Boolean
......@@ -264,24 +276,32 @@ export default {
}
`,
variables: {
authAutoLogin: this.config.authAutoLogin ?? false,
authEnforce2FA: this.config.authEnforce2FA ?? false
authAutoLogin: state.config.authAutoLogin ?? false,
authEnforce2FA: state.config.authEnforce2FA ?? false
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
}
})
this.$store.commit('showNotification', {
style: 'success',
message: 'Configuration saved successfully.',
icon: 'check'
$q.notify({
type: 'positive',
message: 'Configuration saved successfully.'
})
} catch (err) {
this.$store.commit('pushGraphError', err)
}
}
$q.notify({
type: 'negative',
message: err.message
})
}
}
// MOUNTED
onMounted(() => {
if (adminStore.currentSiteId) {
load()
}
})
</script>
<style lang='scss'>
......
......@@ -16,7 +16,7 @@ q-page.admin-locale
target='_blank'
)
q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate'
icon='las la-redo-alt'
flat
color='secondary'
@click='refresh'
......@@ -100,7 +100,7 @@ q-page.admin-locale
<script setup>
import { useMeta, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { defineAsyncComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { nextTick, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAdminStore } from '../stores/admin'
......
......@@ -4,22 +4,22 @@ q-page.admin-storage
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-ssd.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.storage.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.storage.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.storage.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.storage.subtitle') }}
.col-auto.flex
q-spinner-tail.q-mr-md(
v-show='loading > 0'
v-show='state.loading > 0'
color='accent'
size='sm'
)
q-btn-toggle.q-mr-md(
v-model='displayMode'
v-model='state.displayMode'
push
no-caps
toggle-color='black'
:options=`[
{ label: $t('admin.storage.targets'), value: 'targets' },
{ label: $t('admin.storage.deliveryPaths'), value: 'delivery' }
{ label: t('admin.storage.targets'), value: 'targets' },
{ label: t('admin.storage.deliveryPaths'), value: 'delivery' }
]`
)
q-separator.q-mr-md(vertical)
......@@ -33,11 +33,11 @@ q-page.admin-storage
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:loading='loading > 0'
:loading='state.loading > 0'
)
q-separator(inset)
......@@ -45,7 +45,7 @@ q-page.admin-storage
//- TARGETS
//- ==========================================
.row.q-pa-md.q-col-gutter-md(v-if='displayMode === `targets`')
.row.q-pa-md.q-col-gutter-md(v-if='state.displayMode === `targets`')
.col-auto
q-card.rounded-borders.bg-dark
q-list(
......@@ -54,11 +54,11 @@ q-page.admin-storage
dark
)
q-item(
v-for='tgt of targets'
v-for='tgt of state.targets'
:key='tgt.key'
active-class='bg-primary text-white'
:active='selectedTarget === tgt.id'
:to='`/_admin/` + currentSiteId + `/storage/` + tgt.id'
:active='state.selectedTarget === tgt.id'
:to='`/_admin/` + adminStore.currentSiteId + `/storage/` + tgt.id'
clickable
)
q-item-section(side)
......@@ -70,76 +70,76 @@ q-page.admin-storage
q-item-label(caption, :class='getTargetSubtitleColor(tgt)') {{getTargetSubtitle(tgt)}}
q-item-section(side)
q-spinner-rings(:color='tgt.isEnabled ? `positive` : `negative`', size='sm')
.col(v-if='target')
.col(v-if='state.target')
//- -----------------------
//- Content Types
//- -----------------------
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{$t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ $t('admin.storage.contentTypesHint') }}
.text-subtitle1 {{t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='target.contentTypes.activeTypes'
:color='target.module === `db` ? `grey` : `primary`'
v-model='state.target.contentTypes.activeTypes'
:color='state.target.module === `db` ? `grey` : `primary`'
val='pages'
:aria-label='$t(`admin.storage.contentTypePages`)'
:disable='target.module === `db`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db`'
)
q-item-section
q-item-label {{$t(`admin.storage.contentTypePages`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypePagesHint`)}}
q-item-label {{t(`admin.storage.contentTypePages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypePagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='target.contentTypes.activeTypes'
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='images'
:aria-label='$t(`admin.storage.contentTypeImages`)'
:aria-label='t(`admin.storage.contentTypeImages`)'
)
q-item-section
q-item-label {{$t(`admin.storage.contentTypeImages`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeImagesHint`)}}
q-item-label {{t(`admin.storage.contentTypeImages`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeImagesHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='target.contentTypes.activeTypes'
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='documents'
:aria-label='$t(`admin.storage.contentTypeDocuments`)'
:aria-label='t(`admin.storage.contentTypeDocuments`)'
)
q-item-section
q-item-label {{$t(`admin.storage.contentTypeDocuments`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeDocumentsHint`)}}
q-item-label {{t(`admin.storage.contentTypeDocuments`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeDocumentsHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='target.contentTypes.activeTypes'
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='others'
:aria-label='$t(`admin.storage.contentTypeOthers`)'
:aria-label='t(`admin.storage.contentTypeOthers`)'
)
q-item-section
q-item-label {{$t(`admin.storage.contentTypeOthers`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeOthersHint`)}}
q-item-label {{t(`admin.storage.contentTypeOthers`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeOthersHint`)}}
q-item(tag='label')
q-item-section(avatar)
q-checkbox(
v-model='target.contentTypes.activeTypes'
v-model='state.target.contentTypes.activeTypes'
color='primary'
val='large'
:aria-label='$t(`admin.storage.contentTypeLargeFiles`)'
:aria-label='t(`admin.storage.contentTypeLargeFiles`)'
)
q-item-section
q-item-label {{$t(`admin.storage.contentTypeLargeFiles`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeLargeFilesHint`)}}
q-item-label.text-deep-orange(v-if='target.module === `db`', caption) {{$t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
q-item-label {{t(`admin.storage.contentTypeLargeFiles`)}}
q-item-label(caption) {{t(`admin.storage.contentTypeLargeFilesHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
q-item-section(side)
q-input(
outlined
:label='$t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='target.contentTypes.largeThreshold'
:label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='state.target.contentTypes.largeThreshold'
style='min-width: 150px;'
dense
)
......@@ -149,41 +149,41 @@ q-page.admin-storage
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{$t('admin.storage.assetDelivery')}}
.text-body2.text-grey {{ $t('admin.storage.assetDeliveryHint') }}
q-item(:tag='target.assetDelivery.isStreamingSupported ? `label` : null')
.text-subtitle1 {{t('admin.storage.assetDelivery')}}
.text-body2.text-grey {{ t('admin.storage.assetDeliveryHint') }}
q-item(:tag='state.target.assetDelivery.isStreamingSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='target.assetDelivery.streaming'
:color='target.module === `db` || !target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='$t(`admin.storage.contentTypePages`)'
:disable='target.module === `db` || !target.assetDelivery.isStreamingSupported'
v-model='state.target.assetDelivery.streaming'
:color='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported'
)
q-item-section
q-item-label {{$t(`admin.storage.assetStreaming`)}}
q-item-label(caption) {{$t(`admin.storage.assetStreamingHint`)}}
q-item-label.text-deep-orange(v-if='!target.assetDelivery.isStreamingSupported', caption) {{$t(`admin.storage.assetStreamingNotSupported`)}}
q-item(:tag='target.assetDelivery.isDirectAccessSupported ? `label` : null')
q-item-label {{t(`admin.storage.assetStreaming`)}}
q-item-label(caption) {{t(`admin.storage.assetStreamingHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isStreamingSupported', caption) {{t(`admin.storage.assetStreamingNotSupported`)}}
q-item(:tag='state.target.assetDelivery.isDirectAccessSupported ? `label` : null')
q-item-section(avatar)
q-checkbox(
v-model='target.assetDelivery.directAccess'
:color='!target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='$t(`admin.storage.contentTypePages`)'
:disable='!target.assetDelivery.isDirectAccessSupported'
v-model='state.target.assetDelivery.directAccess'
:color='!state.target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='t(`admin.storage.contentTypePages`)'
:disable='!state.target.assetDelivery.isDirectAccessSupported'
)
q-item-section
q-item-label {{$t(`admin.storage.assetDirectAccess`)}}
q-item-label(caption) {{$t(`admin.storage.assetDirectAccessHint`)}}
q-item-label.text-deep-orange(v-if='!target.assetDelivery.isDirectAccessSupported', caption) {{$t(`admin.storage.assetDirectAccessNotSupported`)}}
q-item-label {{t(`admin.storage.assetDirectAccess`)}}
q-item-label(caption) {{t(`admin.storage.assetDirectAccessHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isDirectAccessSupported', caption) {{t(`admin.storage.assetDirectAccessNotSupported`)}}
//- -----------------------
//- Setup
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='target.setup && target.setup.handler && target.setup.state !== `configured`')
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`')
q-card-section
.text-subtitle1 {{$t('admin.storage.setup')}}
.text-body2.text-grey {{ $t('admin.storage.setupHint') }}
template(v-if='target.setup.handler === `github` && target.setup.state === `notconfigured`')
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupHint') }}
template(v-if='state.target.setup.handler === `github` && state.target.setup.state === `notconfigured`')
q-item
blueprint-icon(icon='test-account')
q-item-section
......@@ -191,42 +191,42 @@ q-page.admin-storage
q-item-label(caption) Whether to use an organization or personal GitHub account during setup.
q-item-section.col-auto
q-btn-toggle(
v-model='target.setup.values.accountType'
v-model='state.target.setup.values.accountType'
push
glossy
no-caps
toggle-color='primary'
:options=`[
{ label: $t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: $t('admin.storage.githubAccTypePersonal'), value: 'personal' }
{ label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
]`
)
q-separator.q-my-sm(inset)
template(v-if='target.setup.values.accountType === `org`')
template(v-if='state.target.setup.values.accountType === `org`')
q-item
blueprint-icon(icon='github')
q-item-section
q-item-label {{ $t('admin.storage.githubOrg') }}
q-item-label(caption) {{ $t('admin.storage.githubOrgHint') }}
q-item-label {{ t('admin.storage.githubOrg') }}
q-item-label(caption) {{ t('admin.storage.githubOrgHint') }}
q-item-section
q-input(
outlined
v-model='target.setup.values.org'
v-model='state.target.setup.values.org'
dense
:aria-label='$t(`admin.storage.githubOrg`)'
:aria-label='t(`admin.storage.githubOrg`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='dns')
q-item-section
q-item-label {{ $t('admin.storage.githubPublicUrl') }}
q-item-label(caption) {{ $t('admin.storage.githubPublicUrlHint') }}
q-item-label {{ t('admin.storage.githubPublicUrl') }}
q-item-label(caption) {{ t('admin.storage.githubPublicUrlHint') }}
q-item-section
q-input(
outlined
v-model='target.setup.values.publicUrl'
v-model='state.target.setup.values.publicUrl'
dense
:aria-label='$t(`admin.storage.githubPublicUrl`)'
:aria-label='t(`admin.storage.githubPublicUrl`)'
)
q-card-section.q-pt-sm.text-right
form(
......@@ -242,37 +242,37 @@ q-page.admin-storage
q-btn(
unelevated
icon='las la-angle-double-right'
:label='$t(`admin.storage.startSetup`)'
:label='t(`admin.storage.startSetup`)'
color='secondary'
@click='setupGitHub'
:loading='setupCfg.loading'
)
template(v-else-if='target.setup.handler === `github` && target.setup.state === `pendinginstall`')
template(v-else-if='state.target.setup.handler === `github` && state.target.setup.state === `pendinginstall`')
q-card-section.q-py-none
q-banner(
rounded
:class='$q.dark.isActive ? `bg-teal-9 text-white` : `bg-teal-1 text-teal-9`'
) {{$t('admin.storage.githubFinish')}}
) {{t('admin.storage.githubFinish')}}
q-card-section.q-pt-sm.text-right
q-btn.q-mr-sm(
unelevated
icon='las la-times-circle'
:label='$t(`admin.storage.cancelSetup`)'
:label='t(`admin.storage.cancelSetup`)'
color='negative'
@click='setupDestroy'
)
q-btn(
unelevated
icon='las la-angle-double-right'
:label='$t(`admin.storage.finishSetup`)'
:label='t(`admin.storage.finishSetup`)'
color='secondary'
@click='setupGitHubStep(`verify`)'
:loading='setupCfg.loading'
)
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='target.setup && target.setup.handler && target.setup.state === `configured`')
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state === `configured`')
q-card-section
.text-subtitle1 {{$t('admin.storage.setup')}}
.text-body2.text-grey {{ $t('admin.storage.setupConfiguredHint') }}
.text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ t('admin.storage.setupConfiguredHint') }}
q-item
blueprint-icon.self-start(icon='matches', :hue-rotate='140')
q-item-section
......@@ -285,7 +285,7 @@ q-page.admin-storage
icon='las la-arrow-circle-right'
color='negative'
@click='setupDestroy'
:label='$t(`admin.storage.uninstall`)'
:label='t(`admin.storage.uninstall`)'
)
//- -----------------------
......@@ -293,14 +293,14 @@ q-page.admin-storage
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{$t('admin.storage.config')}}
.text-subtitle1 {{t('admin.storage.config')}}
q-banner.q-mt-md(
v-if='!target.config || Object.keys(target.config).length < 1'
v-if='!state.target.config || Object.keys(state.target.config).length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.noConfigOption')}}
) {{t('admin.storage.noConfigOption')}}
template(
v-for='(cfg, cfgKey, idx) in target.config'
v-for='(cfg, cfgKey, idx) in state.target.config'
)
template(
v-if='configIfCheck(cfg.if)'
......@@ -317,7 +317,7 @@ q-page.admin-storage
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.general.allowComments`)'
:aria-label='t(`admin.general.allowComments`)'
:disable='cfg.readOnly'
)
q-item(v-else)
......@@ -364,34 +364,34 @@ q-page.admin-storage
//- -----------------------
//- Sync
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='target.sync && Object.keys(target.sync).length > 0')
q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.sync && Object.keys(state.target.sync).length > 0')
q-card-section
.text-subtitle1 {{$t('admin.storage.sync')}}
.text-subtitle1 {{t('admin.storage.sync')}}
q-banner.q-mt-md(
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.noSyncModes')}}
) {{t('admin.storage.noSyncModes')}}
//- -----------------------
//- Actions
//- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{$t('admin.storage.actions')}}
.text-subtitle1 {{t('admin.storage.actions')}}
q-banner.q-mt-md(
v-if='!target.actions || target.actions.length < 1'
v-if='!state.target.actions || state.target.actions.length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.noActions')}}
) {{t('admin.storage.noActions')}}
q-banner.q-mt-md(
v-else-if='!target.isEnabled'
v-else-if='!state.target.isEnabled'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.actionsInactiveWarn')}}
) {{t('admin.storage.actionsInactiveWarn')}}
template(
v-if='target.isEnabled'
v-for='(act, idx) in target.actions'
v-if='state.target.isEnabled'
v-for='(act, idx) in state.target.actions'
)
q-separator.q-my-sm(inset, v-if='idx > 0')
q-item
......@@ -406,32 +406,32 @@ q-page.admin-storage
icon='las la-arrow-circle-right'
color='primary'
@click=''
:label='$t(`common.actions.proceed`)'
:label='t(`common.actions.proceed`)'
)
.col-auto(v-if='target')
.col-auto(v-if='state.target')
//- -----------------------
//- Infobox
//- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{target.title}}
.text-subtitle1 {{state.target.title}}
q-img.q-mt-sm.rounded-borders(
:src='target.banner'
fit='cover'
no-spinner
)
.text-body2.q-mt-md {{target.description}}
.text-body2.q-mt-md {{state.target.description}}
q-separator.q-mb-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{$t(`admin.storage.vendor`)}}
q-item-label {{target.vendor}}
q-item-label.text-grey {{t(`admin.storage.vendor`)}}
q-item-label {{state.target.vendor}}
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{$t(`admin.storage.vendorWebsite`)}}
q-item-label: a(:href='target.website', target='_blank', rel='noreferrer') {{target.website}}
q-item-label.text-grey {{t(`admin.storage.vendorWebsite`)}}
q-item-label: a(:href='state.target.website', target='_blank', rel='noreferrer') {{state.target.website}}
//- -----------------------
//- Status
......@@ -439,25 +439,25 @@ q-page.admin-storage
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 Status
template(v-if='target.module !== `db` && !(target.setup && target.setup.handler && target.setup.state !== `configured`)')
template(v-if='state.target.module !== `db` && !(state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)')
q-item(tag='label')
q-item-section
q-item-label {{$t(`admin.storage.enabled`)}}
q-item-label(caption) {{$t(`admin.storage.enabledHint`)}}
q-item-label.text-deep-orange(v-if='target.module === `db`', caption) {{$t(`admin.storage.enabledForced`)}}
q-item-label {{t(`admin.storage.enabled`)}}
q-item-label(caption) {{t(`admin.storage.enabledHint`)}}
q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.enabledForced`)}}
q-item-section(avatar)
q-toggle(
v-model='target.isEnabled'
:disable='target.module === `db` || (target.setup && target.setup.handler && target.setup.state !== `configured`)'
v-model='state.target.isEnabled'
:disable='state.target.module === `db` || (state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.general.allowSearch`)'
:aria-label='t(`admin.general.allowSearch`)'
)
q-separator.q-my-sm(inset)
q-item
q-item-section
q-item-label.text-grey {{$t(`admin.storage.currentState`)}}
q-item-label.text-grey {{t(`admin.storage.currentState`)}}
q-item-label.text-positive No issues detected.
//- -----------------------
......@@ -465,50 +465,50 @@ q-page.admin-storage
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{$t(`admin.storage.versioning`)}}
.text-body2.text-grey {{$t(`admin.storage.versioningHint`)}}
q-item(:tag='target.versioning.isSupported ? `label` : null')
.text-subtitle1 {{t(`admin.storage.versioning`)}}
.text-body2.text-grey {{t(`admin.storage.versioningHint`)}}
q-item(:tag='state.target.versioning.isSupported ? `label` : null')
q-item-section
q-item-label {{$t(`admin.storage.useVersioning`)}}
q-item-label(caption) {{$t(`admin.storage.useVersioningHint`)}}
q-item-label.text-deep-orange(v-if='!target.versioning.isSupported', caption) {{$t(`admin.storage.versioningNotSupported`)}}
q-item-label.text-deep-orange(v-if='target.versioning.isForceEnabled', caption) {{$t(`admin.storage.versioningForceEnabled`)}}
q-item-label {{t(`admin.storage.useVersioning`)}}
q-item-label(caption) {{t(`admin.storage.useVersioningHint`)}}
q-item-label.text-deep-orange(v-if='!state.target.versioning.isSupported', caption) {{t(`admin.storage.versioningNotSupported`)}}
q-item-label.text-deep-orange(v-if='state.target.versioning.isForceEnabled', caption) {{t(`admin.storage.versioningForceEnabled`)}}
q-item-section(avatar)
q-toggle(
v-model='target.versioning.enabled'
:disable='!target.versioning.isSupported || target.versioning.isForceEnabled'
v-model='state.target.versioning.enabled'
:disable='!state.target.versioning.isSupported || state.target.versioning.isForceEnabled'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.storage.useVersioning`)'
:aria-label='t(`admin.storage.useVersioning`)'
)
//- ==========================================
//- DELIVERY PATHS
//- ==========================================
.row.q-pa-md.q-col-gutter-md(v-if='displayMode === `delivery`')
.row.q-pa-md.q-col-gutter-md(v-if='state.displayMode === `delivery`')
.col
q-card.rounded-borders
q-card-section.flex.items-center
.text-caption.q-mr-sm {{ $t('admin.storage.deliveryPathsLegend') }}
.text-caption.q-mr-sm {{ t('admin.storage.deliveryPathsLegend') }}
q-chip(square, dense, color='blue-1', text-color='blue-8')
q-avatar(icon='las la-ellipsis-h', color='blue', text-color='white')
span.text-caption.q-px-sm {{ $t('admin.storage.deliveryPathsUserRequest') }}
span.text-caption.q-px-sm {{ t('admin.storage.deliveryPathsUserRequest') }}
q-chip(square, dense, color='teal-1', text-color='teal-8')
q-avatar(icon='las la-ellipsis-h', color='positive', text-color='white')
span.text-caption.q-px-sm {{ $t('admin.storage.deliveryPathsPushToOrigin') }}
span.text-caption.q-px-sm {{ t('admin.storage.deliveryPathsPushToOrigin') }}
q-chip(square, dense, color='red-1', text-color='red-8')
q-avatar(icon='las la-minus', color='negative', text-color='white')
span.text-caption.q-px-sm {{ $t('admin.storage.missingOrigin') }}
span.text-caption.q-px-sm {{ t('admin.storage.missingOrigin') }}
q-separator
v-network-graph(
:zoom-level='2'
:configs='deliveryConfig'
:nodes='deliveryNodes'
:edges='deliveryEdges'
:paths='deliveryPaths'
:layouts='deliveryLayouts'
:configs='state.deliveryConfig'
:nodes='state.deliveryNodes'
:edges='state.deliveryEdges'
:paths='state.deliveryPaths'
:layouts='state.deliveryLayouts'
style='height: 600px;'
)
template(#override-node='{ nodeId, scale, config, ...slotProps }')
......@@ -522,57 +522,57 @@ q-page.admin-storage
v-bind='slotProps'
)
image(
v-if='deliveryNodes[nodeId].icon && deliveryNodes[nodeId].icon.endsWith(`.svg`)'
v-if='state.deliveryNodes[nodeId].icon && state.deliveryNodes[nodeId].icon.endsWith(`.svg`)'
:x='(-config.radius + 5) * scale'
:y='(-config.radius + 5) * scale'
:width='(config.radius - 5) * scale * 2'
:height='(config.radius - 5) * scale * 2'
:xlink:href='deliveryNodes[nodeId].icon'
:xlink:href='state.deliveryNodes[nodeId].icon'
)
text(
v-if='deliveryNodes[nodeId].icon && deliveryNodes[nodeId].iconText'
:class='deliveryNodes[nodeId].icon'
v-if='state.deliveryNodes[nodeId].icon && state.deliveryNodes[nodeId].iconText'
:class='state.deliveryNodes[nodeId].icon'
:font-size='22 * scale'
fill='#ffffff'
text-anchor='middle'
dominant-baseline='central'
v-html='deliveryNodes[nodeId].iconText'
v-html='state.deliveryNodes[nodeId].iconText'
)
//- .overline.my-5 {{$t('admin.storage.syncDirection')}}
//- .body-2.ml-3 {{$t('admin.storage.syncDirectionSubtitle')}}
//- .overline.my-5 {{t('admin.storage.syncDirection')}}
//- .body-2.ml-3 {{t('admin.storage.syncDirectionSubtitle')}}
//- .pr-3.pt-3
//- v-radio-group.ml-3.py-0(v-model='target.mode')
//- v-radio(
//- :label='$t(`admin.storage.syncDirBi`)'
//- :label='t(`admin.storage.syncDirBi`)'
//- color='primary'
//- value='sync'
//- :disabled='target.supportedModes.indexOf(`sync`) < 0'
//- )
//- v-radio(
//- :label='$t(`admin.storage.syncDirPush`)'
//- :label='t(`admin.storage.syncDirPush`)'
//- color='primary'
//- value='push'
//- :disabled='target.supportedModes.indexOf(`push`) < 0'
//- )
//- v-radio(
//- :label='$t(`admin.storage.syncDirPull`)'
//- :label='t(`admin.storage.syncDirPull`)'
//- color='primary'
//- value='pull'
//- :disabled='target.supportedModes.indexOf(`pull`) < 0'
//- )
//- .body-2.ml-3
//- strong {{$t('admin.storage.syncDirBi')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`sync`) < 0') {{$t('admin.storage.unsupported')}}]
//- .pb-3 {{$t('admin.storage.syncDirBiHint')}}
//- strong {{$t('admin.storage.syncDirPush')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`push`) < 0') {{$t('admin.storage.unsupported')}}]
//- .pb-3 {{$t('admin.storage.syncDirPushHint')}}
//- strong {{$t('admin.storage.syncDirPull')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`pull`) < 0') {{$t('admin.storage.unsupported')}}]
//- .pb-3 {{$t('admin.storage.syncDirPullHint')}}
//- strong {{t('admin.storage.syncDirBi')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`sync`) < 0') {{t('admin.storage.unsupported')}}]
//- .pb-3 {{t('admin.storage.syncDirBiHint')}}
//- strong {{t('admin.storage.syncDirPush')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`push`) < 0') {{t('admin.storage.unsupported')}}]
//- .pb-3 {{t('admin.storage.syncDirPushHint')}}
//- strong {{t('admin.storage.syncDirPull')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`pull`) < 0') {{t('admin.storage.unsupported')}}]
//- .pb-3 {{t('admin.storage.syncDirPullHint')}}
//- template(v-if='target.hasSchedule')
//- v-divider.mt-3
//- .overline.my-5 {{$t('admin.storage.syncSchedule')}}
//- .body-2.ml-3 {{$t('admin.storage.syncScheduleHint')}}
//- .overline.my-5 {{t('admin.storage.syncSchedule')}}
//- .body-2.ml-3 {{t('admin.storage.syncScheduleHint')}}
//- .pa-3
//- duration-picker(v-model='target.syncInterval')
//- i18next.caption.mt-3(path='admin.storage.syncScheduleCurrent', tag='div')
......@@ -582,19 +582,52 @@ q-page.admin-storage
</template>
<script>
<script setup>
import find from 'lodash/find'
import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag'
import { get } from 'vuex-pathify'
import transform from 'lodash/transform'
import * as vNG from 'v-network-graph'
import * as VNetworkGraph from 'v-network-graph'
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
import GithubSetupInstallDialog from '../components/GithubSetupInstallDialog.vue'
export default {
data () {
return {
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N
const { t } = useI18n()
// META
useMeta({
title: t('admin.storage.title')
})
// DATA
const state = reactive({
loading: 0,
displayMode: 'targets',
runningAction: false,
......@@ -614,9 +647,9 @@ export default {
nodes: {}
},
deliveryPaths: [],
deliveryConfig: vNG.defineConfigs({
deliveryConfig: VNetworkGraph.defineConfigs({
view: {
layoutHandler: new vNG.GridLayout({ grid: 15 }),
layoutHandler: new VNetworkGraph.GridLayout({ grid: 15 }),
fit: true,
mouseWheelZoomEnabled: false,
grid: {
......@@ -668,69 +701,60 @@ export default {
}
}
})
}
},
computed: {
currentSiteId: get('admin/currentSiteId', false)
},
watch: {
async currentSiteId (newValue) {
await this.load()
this.$nextTick(() => {
this.$router.replace(`/_admin/${newValue}/storage/${this.selectedTarget}`)
})
// REFS
const githubSetupForm = ref(null)
// WATCHERS
watch(() => adminStore.currentSiteId, async (newValue) => {
await load()
nextTick(() => {
router.replace(`/_admin/${newValue}/storage/${state.selectedTarget}`)
})
},
displayMode (newValue) {
})
watch(() => state.displayMode, (newValue) => {
if (newValue === 'delivery') {
this.generateGraph()
generateGraph()
}
},
selectedTarget (newValue) {
this.target = find(this.targets, ['id', newValue]) || null
},
targets (newValue) {
})
watch(() => state.selectedTarget, (newValue) => {
state.target = find(state.targets, ['id', newValue]) || null
})
watch(() => state.targets, (newValue) => {
if (newValue && newValue.length > 0) {
if (this.desiredTarget) {
this.selectedTarget = this.desiredTarget
this.desiredTarget = ''
if (state.desiredTarget) {
state.selectedTarget = state.desiredTarget
state.desiredTarget = ''
} else {
this.selectedTarget = find(this.targets, ['module', 'db'])?.id || null
if (!this.$route.params.id) {
this.$router.replace(`/_admin/${this.currentSiteId}/storage/${this.selectedTarget}`)
state.selectedTarget = find(state.targets, ['module', 'db'])?.id || null
if (!route.params.id) {
router.replace(`/_admin/${adminStore.currentSiteId}/storage/${state.selectedTarget}`)
}
}
this.handleSetupCallback()
handleSetupCallback()
}
},
$route (to, from) {
})
watch(() => route, (to, from) => {
if (!to.params.id) {
return
}
if (this.targets.length < 1) {
this.desiredTarget = to.params.id
} else {
this.selectedTarget = to.params.id
}
}
},
mounted () {
if (!this.selectedTarget && this.$route.params.id) {
if (this.targets.length < 1) {
this.desiredTarget = this.$route.params.id
if (state.targets.length < 1) {
state.desiredTarget = to.params.id
} else {
this.selectedTarget = this.$route.params.id
}
state.selectedTarget = to.params.id
}
if (this.currentSiteId) {
this.load()
}
this.handleSetupCallback()
},
methods: {
async load () {
this.loading++
this.$q.loading.show()
const resp = await this.$apollo.query({
})
// METHODS
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query getStorageTargets (
$siteId: UUID!
......@@ -758,31 +782,41 @@ export default {
}
}`,
variables: {
siteId: this.currentSiteId
siteId: adminStore.currentSiteId
},
fetchPolicy: 'network-only'
})
this.targets = cloneDeep(resp?.data?.storageTargets)
this.$q.loading.hide()
this.loading--
},
configIfCheck (ifs) {
state.targets = cloneDeep(resp?.data?.storageTargets)
} catch (err) {
$q.notify({
type: 'negative',
message: 'Failed to load storage configuration.',
caption: err.message,
timeout: 20000
})
}
$q.loading.hide()
state.loading--
}
function configIfCheck (ifs) {
if (!ifs || ifs.length < 1) { return true }
return ifs.every(s => this.target.config[s.key]?.value === s.eq)
},
async refresh () {
await this.$apollo.queries.targets.refetch()
this.$store.commit('showNotification', {
message: 'List of storage targets has been refreshed.',
style: 'success',
icon: 'cached'
return ifs.every(s => state.target.config[s.key]?.value === s.eq)
}
async function refresh () {
await load()
$q.notify({
type: 'positive',
message: 'List of storage targets has been refreshed.'
})
},
async save ({ silent }) {
}
async function save ({ silent }) {
let saveSuccess = false
if (!silent) { this.$q.loading.show() }
if (!silent) { $q.loading.show() }
try {
const resp = await this.$apollo.mutate({
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation (
$siteId: UUID!
......@@ -800,8 +834,8 @@ export default {
}
`,
variables: {
siteId: this.currentSiteId,
targets: this.targets.map(tgt => ({
siteId: adminStore.currentSiteId,
targets: state.targets.map(tgt => ({
id: tgt.id,
module: tgt.module,
isEnabled: tgt.isEnabled,
......@@ -817,99 +851,105 @@ export default {
if (resp?.data?.updateStorageTargets?.status?.succeeded) {
saveSuccess = true
if (!silent) {
this.$q.notify({
$q.notify({
type: 'positive',
message: this.$t('admin.storage.saveSuccess')
message: t('admin.storage.saveSuccess')
})
}
} else {
throw new Error(resp?.data?.updateStorageTargets?.status?.message || 'Unexpected error')
}
} catch (err) {
this.$q.notify({
$q.notify({
type: 'negative',
message: this.$t('admin.storage.saveFailed'),
message: t('admin.storage.saveFailed'),
caption: err.message
})
}
if (!silent) { this.$q.loading.hide() }
if (!silent) { $q.loading.hide() }
return saveSuccess
},
getTargetSubtitle (target) {
}
function getTargetSubtitle (target) {
if (!target.isEnabled) {
return this.$t('admin.storage.inactiveTarget')
return t('admin.storage.inactiveTarget')
}
const hasPages = target.contentTypes?.activeTypes?.includes('pages')
const hasAssets = target.contentTypes?.activeTypes?.filter(c => c !== 'pages')?.length > 0
if (hasPages && hasAssets) {
return this.$t('admin.storage.pagesAndAssets')
return t('admin.storage.pagesAndAssets')
} else if (hasPages) {
return this.$t('admin.storage.pagesOnly')
return t('admin.storage.pagesOnly')
} else if (hasAssets) {
return this.$t('admin.storage.assetsOnly')
return t('admin.storage.assetsOnly')
} else {
return this.$t('admin.storage.notConfigured')
return t('admin.storage.notConfigured')
}
},
getTargetSubtitleColor (target) {
if (this.selectedTarget === target.id) {
}
function getTargetSubtitleColor (target) {
if (state.selectedTarget === target.id) {
return 'text-blue-2'
} else if (target.isEnabled) {
return 'text-positive'
} else {
return 'text-grey-7'
}
},
getDefaultSchedule (val) {
}
function getDefaultSchedule (val) {
if (!val) { return 'N/A' }
return '' // moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
},
async executeAction (targetKey, handler) {
this.$store.commit('loadingStart', 'admin-storage-executeaction')
this.runningAction = true
this.runningActionHandler = handler
try {
await this.$apollo.mutate({
mutation: gql`{}`,
variables: {
targetKey,
handler
}
})
this.$store.commit('showNotification', {
message: 'Action completed.',
style: 'success',
icon: 'check'
})
} catch (err) {
console.warn(err)
}
this.runningAction = false
this.runningActionHandler = ''
this.$store.commit('loadingStop', 'admin-storage-executeaction')
},
async handleSetupCallback () {
if (this.targets.length < 1 || !this.selectedTarget) { return }
}
this.$nextTick(() => {
if (this.target?.setup?.handler === 'github' && this.$route.query.code) {
this.setupGitHubStep('connect', this.$route.query.code)
async function executeAction (targetKey, handler) {
// this.$store.commit('loadingStart', 'admin-storage-executeaction')
// this.runningAction = true
// this.runningActionHandler = handler
// try {
// await this.$apollo.mutate({
// mutation: gql`{}`,
// variables: {
// targetKey,
// handler
// }
// })
// this.$store.commit('showNotification', {
// message: 'Action completed.',
// style: 'success',
// icon: 'check'
// })
// } catch (err) {
// console.warn(err)
// }
// this.runningAction = false
// this.runningActionHandler = ''
// this.$store.commit('loadingStop', 'admin-storage-executeaction')
}
async function handleSetupCallback () {
if (state.targets.length < 1 || !state.selectedTarget) { return }
nextTick(() => {
if (state.target?.setup?.handler === 'github' && route.query.code) {
setupGitHubStep('connect', route.query.code)
}
})
},
async setupDestroy () {
this.$q.dialog({
title: this.$t('admin.storage.destroyConfirm'),
message: this.$t('admin.storage.destroyConfirmInfo'),
}
async function setupDestroy () {
$q.dialog({
title: t('admin.storage.destroyConfirm'),
message: t('admin.storage.destroyConfirmInfo'),
cancel: true,
persistent: true
}).onOk(async () => {
this.$q.loading.show({
message: this.$t('admin.storage.destroyingSetup')
$q.loading.show({
message: t('admin.storage.destroyingSetup')
})
try {
const resp = await this.$apollo.mutate({
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation (
$targetId: UUID!
......@@ -925,72 +965,73 @@ export default {
}
`,
variables: {
targetId: this.selectedTarget
targetId: state.selectedTarget
}
})
if (resp?.data?.destroyStorageTargetSetup?.status?.succeeded) {
this.target.setup.state = 'notconfigured'
state.target.setup.state = 'notconfigured'
setTimeout(() => {
this.$q.loading.hide()
this.$q.notify({
$q.loading.hide()
$q.notify({
type: 'positive',
message: this.$t('admin.storage.githubSetupDestroySuccess')
message: t('admin.storage.githubSetupDestroySuccess')
})
}, 2000)
} else {
throw new Error(resp?.data?.destroyStorageTargetSetup?.status?.message || 'Unexpected error')
}
} catch (err) {
this.$q.notify({
$q.notify({
type: 'negative',
message: this.$t('admin.storage.githubSetupDestroyFailed'),
message: t('admin.storage.githubSetupDestroyFailed'),
caption: err.message
})
this.$q.loading.hide()
$q.loading.hide()
}
})
},
async setupGitHub () {
}
async function setupGitHub () {
// -> Format values
this.target.setup.values.publicUrl = this.target.setup.values.publicUrl.toLowerCase()
state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.toLowerCase()
// -> Basic input check
if (this.target.setup.values.accountType === 'org' && this.target.setup.values.org.length < 1) {
return this.$q.notify({
if (state.target.setup.values.accountType === 'org' && state.target.setup.values.org.length < 1) {
return $q.notify({
type: 'negative',
message: 'Invalid GitHub Organization',
caption: 'Enter a valid github organization.'
})
}
if (this.target.setup.values.publicUrl.length < 11 || !/^https?:\/\/.{4,}$/.test(this.target.setup.values.publicUrl)) {
return this.$q.notify({
if (state.target.setup.values.publicUrl.length < 11 || !/^https?:\/\/.{4,}$/.test(state.target.setup.values.publicUrl)) {
return $q.notify({
type: 'negative',
message: 'Invalid Wiki Public URL',
caption: 'Enter a valid public URL for your wiki.'
})
}
if (this.target.setup.values.publicUrl.endsWith('/')) {
this.target.setup.values.publicUrl = this.target.setup.values.publicUrl.slice(0, -1)
if (state.target.setup.values.publicUrl.endsWith('/')) {
state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.slice(0, -1)
}
// -> Generate manifest
this.setupCfg.loading = true
if (this.target.setup.values.accountType === 'org') {
this.setupCfg.action = `https://github.com/organizations/${this.target.setup.values.org}/settings/apps/new`
state.setupCfg.loading = true
if (state.target.setup.values.accountType === 'org') {
state.setupCfg.action = `https://github.com/organizations/${state.target.setup.values.org}/settings/apps/new`
} else {
this.setupCfg.action = 'https://github.com/settings/apps/new'
state.setupCfg.action = 'https://github.com/settings/apps/new'
}
this.setupCfg.manifest = JSON.stringify({
name: `Wiki.js - ${this.currentSiteId.slice(-12)}`,
state.setupCfg.manifest = JSON.stringify({
name: `Wiki.js - ${adminStore.currentSiteId.slice(-12)}`,
description: 'Connects your Wiki.js to GitHub repositories and synchronize their contents.',
url: this.target.setup.values.publicUrl,
url: state.target.setup.values.publicUrl,
hook_attributes: {
url: `${this.target.setup.values.publicUrl}/_github/${this.currentSiteId}/events`
url: `${state.target.setup.values.publicUrl}/_github/${adminStore.currentSiteId}/events`
},
redirect_url: `${this.target.setup.values.publicUrl}/_admin/${this.currentSiteId}/storage/${this.target.id}`,
redirect_url: `${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`,
callback_urls: [
`${this.target.setup.values.publicUrl}/_admin/${this.currentSiteId}/storage/${this.target.id}`
`${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`
],
public: false,
default_permissions: {
......@@ -1004,23 +1045,24 @@ export default {
'push'
]
})
this.$q.loading.show({
message: this.$t('admin.storage.githubPreparingManifest')
$q.loading.show({
message: t('admin.storage.githubPreparingManifest')
})
if (await this.save({ silent: true })) {
this.$refs.githubSetupForm.submit()
if (await save({ silent: true })) {
githubSetupForm.value.submit()
} else {
this.setupCfg.loading = false
this.$q.loading.hide()
state.setupCfg.loading = false
$q.loading.hide()
}
},
async setupGitHubStep (step, code) {
this.$q.loading.show({
message: this.$t('admin.storage.githubVerifying')
}
async function setupGitHubStep (step, code) {
$q.loading.show({
message: t('admin.storage.githubVerifying')
})
try {
const resp = await this.$apollo.mutate({
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation (
$targetId: UUID!
......@@ -1049,15 +1091,15 @@ export default {
if (resp?.data?.setupStorageTarget?.status?.succeeded) {
switch (resp.data.setupStorageTarget.state?.nextStep) {
case 'installApp': {
this.$router.replace({ query: null })
this.$q.loading.hide()
router.replace({ query: null })
$q.loading.hide()
this.$q.dialog({
$q.dialog({
component: GithubSetupInstallDialog,
persistent: true
}).onOk(() => {
this.$q.loading.show({
message: this.$t('admin.storage.githubRedirecting')
$q.loading.show({
message: t('admin.storage.githubRedirecting')
})
window.location.assign(resp.data.setupStorageTarget.state?.url)
}).onCancel(() => {
......@@ -1069,10 +1111,10 @@ export default {
this.target.isEnabled = true
this.target.setup.state = 'configured'
setTimeout(() => {
this.$q.loading.hide()
this.$q.notify({
$q.loading.hide()
$q.notify({
type: 'positive',
message: this.$t('admin.storage.githubSetupSuccess')
message: t('admin.storage.githubSetupSuccess')
})
}, 2000)
break
......@@ -1085,101 +1127,117 @@ export default {
throw new Error(resp?.data?.setupStorageTarget?.status?.message || 'Unexpected error')
}
} catch (err) {
this.$q.loading.hide()
this.$q.notify({
$q.loading.hide()
$q.notify({
type: 'negative',
message: this.$t('admin.storage.githubSetupFailed'),
message: t('admin.storage.githubSetupFailed'),
caption: err.message
})
}
},
generateGraph () {
}
function generateGraph () {
const types = [
{ key: 'images', label: this.$t('admin.storage.contentTypeImages'), icon: 'las', iconText: '&#xf1c5;' },
{ key: 'documents', label: this.$t('admin.storage.contentTypeDocuments'), icon: 'las', iconText: '&#xf1c1;' },
{ key: 'others', label: this.$t('admin.storage.contentTypeOthers'), icon: 'las', iconText: '&#xf15b;' },
{ key: 'large', label: this.$t('admin.storage.contentTypeLargeFiles'), icon: 'las', iconText: '&#xf1c6;' }
{ key: 'images', label: t('admin.storage.contentTypeImages'), icon: 'las', iconText: '&#xf1c5;' },
{ key: 'documents', label: t('admin.storage.contentTypeDocuments'), icon: 'las', iconText: '&#xf1c1;' },
{ key: 'others', label: t('admin.storage.contentTypeOthers'), icon: 'las', iconText: '&#xf15b;' },
{ key: 'large', label: t('admin.storage.contentTypeLargeFiles'), icon: 'las', iconText: '&#xf1c6;' }
]
// -> Create PagesNodes
this.deliveryNodes = {
user: { name: this.$t('admin.storage.deliveryPathsUser'), borderRadius: 16, icon: '/_assets/icons/fluent-account.svg' },
pages: { name: this.$t('admin.storage.contentTypePages'), color: '#3f51b5', icon: 'las', iconText: '&#xf15c;' },
state.deliveryNodes = {
user: { name: t('admin.storage.deliveryPathsUser'), borderRadius: 16, icon: '/_assets/icons/fluent-account.svg' },
pages: { name: t('admin.storage.contentTypePages'), color: '#3f51b5', icon: 'las', iconText: '&#xf15c;' },
pages_wiki: { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
}
this.deliveryEdges = {
state.deliveryEdges = {
user_pages: { source: 'user', target: 'pages' },
pages_in: { source: 'pages', target: 'pages_wiki' },
pages_out: { source: 'pages_wiki', target: 'pages' }
}
this.deliveryLayouts.nodes = {
state.deliveryLayouts.nodes = {
user: { x: -30, y: 30 },
pages: { x: 0, y: 0 },
pages_wiki: { x: 60, y: 0 }
}
this.deliveryPaths = []
state.deliveryPaths = []
// -> Create Asset Nodes
for (const [i, t] of types.entries()) {
this.deliveryNodes[t.key] = { name: t.label, color: '#3f51b5', icon: t.icon, iconText: t.iconText }
this.deliveryEdges[`user_${t.key}`] = { source: 'user', target: t.key }
this.deliveryLayouts.nodes[t.key] = { x: 0, y: (i + 1) * 15 }
for (const [i, tp] of types.entries()) {
state.deliveryNodes[tp.key] = { name: tp.label, color: '#3f51b5', icon: tp.icon, iconText: tp.iconText }
state.deliveryEdges[`user_${tp.key}`] = { source: 'user', target: t.key }
state.deliveryLayouts.nodes[tp.key] = { x: 0, y: (i + 1) * 15 }
// -> Find target with direct access
const dt = find(this.targets, tgt => {
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(t.key) && tgt.isEnabled && tgt.assetDelivery.isDirectAccessSupported && tgt.assetDelivery.directAccess
const dt = find(state.targets, tgt => {
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(tp.key) && tgt.isEnabled && tgt.assetDelivery.isDirectAccessSupported && tgt.assetDelivery.directAccess
})
if (dt) {
this.deliveryNodes[`${t.key}_${dt.module}`] = { name: dt.title, icon: dt.icon }
this.deliveryNodes[`${t.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
this.deliveryLayouts.nodes[`${t.key}_${dt.module}`] = { x: 60, y: (i + 1) * 15 }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 120, y: (i + 1) * 15 }
this.deliveryEdges[`${t.key}_${dt.module}_in`] = { source: t.key, target: `${t.key}_${dt.module}` }
this.deliveryEdges[`${t.key}_${dt.module}_out`] = { source: `${t.key}_${dt.module}`, target: t.key }
this.deliveryEdges[`${t.key}_${dt.module}_wiki`] = { source: `${t.key}_wiki`, target: `${t.key}_${dt.module}`, color: '#02c39a', animationSpeed: 25 }
state.deliveryNodes[`${tp.key}_${dt.module}`] = { name: dt.title, icon: dt.icon }
state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
state.deliveryLayouts.nodes[`${tp.key}_${dt.module}`] = { x: 60, y: (i + 1) * 15 }
state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 120, y: (i + 1) * 15 }
state.deliveryEdges[`${tp.key}_${dt.module}_in`] = { source: tp.key, target: `${tp.key}_${dt.module}` }
state.deliveryEdges[`${tp.key}_${dt.module}_out`] = { source: `${tp.key}_${dt.module}`, target: tp.key }
state.deliveryEdges[`${tp.key}_${dt.module}_wiki`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${dt.module}`, color: '#02c39a', animationSpeed: 25 }
continue
}
// -> Find target with streaming
const st = find(this.targets, tgt => {
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(t.key) && tgt.isEnabled && tgt.assetDelivery.isStreamingSupported && tgt.assetDelivery.streaming
const st = find(state.targets, tgt => {
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(tp.key) && tgt.isEnabled && tgt.assetDelivery.isStreamingSupported && tgt.assetDelivery.streaming
})
if (st) {
this.deliveryNodes[`${t.key}_${st.module}`] = { name: st.title, icon: st.icon }
this.deliveryNodes[`${t.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
this.deliveryLayouts.nodes[`${t.key}_${st.module}`] = { x: 120, y: (i + 1) * 15 }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
this.deliveryEdges[`${t.key}_wiki_in`] = { source: t.key, target: `${t.key}_wiki` }
this.deliveryEdges[`${t.key}_wiki_out`] = { source: `${t.key}_wiki`, target: t.key }
this.deliveryEdges[`${t.key}_${st.module}_out`] = { source: `${t.key}_${st.module}`, target: `${t.key}_wiki` }
this.deliveryEdges[`${t.key}_${st.module}_in`] = { source: `${t.key}_wiki`, target: `${t.key}_${st.module}` }
this.deliveryEdges[`${t.key}_${st.module}_wiki`] = { source: `${t.key}_wiki`, target: `${t.key}_${st.module}`, color: '#02c39a', animationSpeed: 25 }
state.deliveryNodes[`${tp.key}_${st.module}`] = { name: st.title, icon: st.icon }
state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
state.deliveryLayouts.nodes[`${tp.key}_${st.module}`] = { x: 120, y: (i + 1) * 15 }
state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
state.deliveryEdges[`${tp.key}_wiki_in`] = { source: tp.key, target: `${tp.key}_wiki` }
state.deliveryEdges[`${tp.key}_wiki_out`] = { source: `${tp.key}_wiki`, target: tp.key }
state.deliveryEdges[`${tp.key}_${st.module}_out`] = { source: `${tp.key}_${st.module}`, target: `${tp.key}_wiki` }
state.deliveryEdges[`${tp.key}_${st.module}_in`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${st.module}` }
state.deliveryEdges[`${tp.key}_${st.module}_wiki`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${st.module}`, color: '#02c39a', animationSpeed: 25 }
continue
}
// -> Check DB fallback
const dbt = find(this.targets, ['module', 'db'])
if (dbt.contentTypes.activeTypes.includes(t.key)) {
this.deliveryNodes[`${t.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
this.deliveryEdges[`${t.key}_db_in`] = { source: t.key, target: `${t.key}_wiki` }
this.deliveryEdges[`${t.key}_db_out`] = { source: `${t.key}_wiki`, target: t.key }
const dbt = find(state.targets, ['module', 'db'])
if (dbt.contentTypes.activeTypes.includes(tp.key)) {
state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
state.deliveryEdges[`${tp.key}_db_in`] = { source: tp.key, target: `${tp.key}_wiki` }
state.deliveryEdges[`${tp.key}_db_out`] = { source: `${tp.key}_wiki`, target: tp.key }
} else {
this.deliveryNodes[`${t.key}_wiki`] = { name: this.$t('admin.storage.missingOrigin'), color: '#f03a47', icon: 'las', iconText: '&#xf071;' }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
this.deliveryEdges[`${t.key}_db_in`] = { source: t.key, target: `${t.key}_wiki`, color: '#f03a47', animate: false }
this.deliveryPaths.push({ edges: [`${t.key}_db_in`], color: '#f03a4755' })
state.deliveryNodes[`${tp.key}_wiki`] = { name: t('admin.storage.missingOrigin'), color: '#f03a47', icon: 'las', iconText: '&#xf071;' }
state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
state.deliveryEdges[`${tp.key}_db_in`] = { source: tp.key, target: `${tp.key}_wiki`, color: '#f03a47', animate: false }
state.deliveryPaths.push({ edges: [`${tp.key}_db_in`], color: '#f03a4755' })
}
}
}
// MOUNTED
onMounted(() => {
if (!state.selectedTarget && route.params.id) {
if (state.targets.length < 1) {
state.desiredTarget = route.params.id
} else {
state.selectedTarget = route.params.id
}
}
}
if (adminStore.currentSiteId) {
load()
}
handleSetupCallback()
})
</script>
<style lang='scss' scoped>
......
......@@ -16,7 +16,7 @@ q-page.admin-system
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate'
icon='las la-redo-alt'
flat
color='secondary'
:loading='state.loading > 0'
......@@ -234,26 +234,10 @@ import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import ClipboardJS from 'clipboard'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
import { useRoute, useRouter } from 'vue-router'
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N
const { t } = useI18n()
......
......@@ -30,20 +30,20 @@ const routes = [
{ path: '', redirect: '/_admin/dashboard' },
{ path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') },
{ path: 'sites', component: () => import('../pages/AdminSites.vue') },
// // -> Site
// -> Site
{ path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') },
{ path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') },
// { path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
// { path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
{ path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
{ path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
// { path: ':siteid/navigation', component: () => import('../pages/AdminNavigation.vue') },
// { path: ':siteid/storage/:id?', component: () => import('../pages/AdminStorage.vue') },
{ path: ':siteid/storage/:id?', component: () => import('../pages/AdminStorage.vue') },
// { path: ':siteid/rendering', component: () => import('../pages/AdminRendering.vue') },
// { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
// // -> Users
// -> Users
// { path: 'auth', component: () => import('../pages/AdminAuth.vue') },
// { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') },
// { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') },
// // -> System
// -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') },
// { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
// { path: 'mail', component: () => import('../pages/AdminMail.vue') },
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment