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 # [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 ARG VARIANT=18-bullseye
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} FROM node:${VARIANT}
# [Optional] Uncomment this section to install additional OS packages. ENV DEBIAN_FRONTEND=noninteractive
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install an additional version of node using nvm # Copy library scripts to execute
# ARG EXTRA_NODE_VERSION=10 ADD https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/containers/javascript-node/.devcontainer/library-scripts/common-debian.sh /tmp/library-scripts/
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 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 # [Option] Install zsh
# RUN su node -c "npm install -g <your-package-list-here>" 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 # Add Docker Source
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 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 - ...@@ -33,6 +70,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -
git \ git \
gnupg2 \ gnupg2 \
nano \ nano \
netcat \
pandoc \ pandoc \
unzip \ unzip \
wget wget
...@@ -48,7 +86,7 @@ ENV npm_config_fund false ...@@ -48,7 +86,7 @@ ENV npm_config_fund false
RUN sed -i 's/#force_color_prompt=/force_color_prompt=/' /root/.bashrc RUN sed -i 's/#force_color_prompt=/force_color_prompt=/' /root/.bashrc
# Fetch wait-for utility # 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 RUN chmod +rx /usr/local/bin/wait-for
# Copy the startup file # Copy the startup file
......
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
cd /workspace cd /workspace
echo "Disabling git info in terminal..."
git config codespaces-theme.hide-status 1 git config codespaces-theme.hide-status 1
git config oh-my-zsh.hide-info 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!" echo "Ready!"
...@@ -32,10 +32,10 @@ ...@@ -32,10 +32,10 @@
"arcanis.vscode-zipfs", "arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"eamodio.gitlens", "eamodio.gitlens",
"johnsoncodehk.volar", "Vue.volar",
"oderwat.indent-rainbow", "oderwat.indent-rainbow",
"redhat.vscode-yaml", "redhat.vscode-yaml",
"visualstudioexptteam.vscodeintellicode", "VisualStudioExptTeam.vscodeintellicode",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"mrmlnc.vscode-duplicate", "mrmlnc.vscode-duplicate",
......
...@@ -6,10 +6,10 @@ services: ...@@ -6,10 +6,10 @@ services:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args: 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. # Append -bullseye or -buster to pin to an OS version.
# Use -bullseye variants on local arm64/Apple Silicon. # Use -bullseye variants on local arm64/Apple Silicon.
VARIANT: 16-bullseye VARIANT: 18-bullseye
volumes: volumes:
- ..:/workspace - ..:/workspace
......
const _ = require('lodash')
const cfgHelper = require('../helpers/config')
const Promise = require('bluebird')
const fs = require('fs-extra') const fs = require('fs-extra')
const path = require('path') const path = require('path')
...@@ -11,71 +8,13 @@ module.exports = { ...@@ -11,71 +8,13 @@ module.exports = {
channel: 'BETA', channel: 'BETA',
version: WIKI.version, version: WIKI.version,
releaseDate: WIKI.releaseDate, releaseDate: WIKI.releaseDate,
minimumVersionRequired: '2.0.0-beta.0', minimumVersionRequired: '3.0.0-beta.0',
minimumNodeRequired: '10.12.0' minimumNodeRequired: '18.0.0'
}, },
init() { init() {
// Clear content cache // Clear content cache
fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache')) fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
return this 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') ...@@ -8,8 +8,8 @@ const { nanoid } = require('nanoid')
const { DateTime } = require('luxon') const { DateTime } = require('luxon')
const semver = require('semver') const semver = require('semver')
if (!semver.satisfies(process.version, '>=16')) { if (!semver.satisfies(process.version, '>=18')) {
console.error('ERROR: Node.js 16.x or later required!') console.error('ERROR: Node.js 18.x or later required!')
process.exit(1) process.exit(1)
} }
......
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
"lint": "eslint --ext .js,.vue ./" "lint": "eslint --ext .js,.vue ./"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "3.6.1", "@apollo/client": "3.6.4",
"@codemirror/autocomplete": "0.20.0", "@codemirror/autocomplete": "0.20.1",
"@codemirror/basic-setup": "0.20.0", "@codemirror/basic-setup": "0.20.0",
"@codemirror/closebrackets": "0.19.2", "@codemirror/closebrackets": "0.19.2",
"@codemirror/commands": "0.20.0", "@codemirror/commands": "0.20.0",
...@@ -25,15 +25,15 @@ ...@@ -25,15 +25,15 @@
"@codemirror/lang-html": "0.20.0", "@codemirror/lang-html": "0.20.0",
"@codemirror/lang-javascript": "0.20.0", "@codemirror/lang-javascript": "0.20.0",
"@codemirror/lang-json": "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/matchbrackets": "0.19.4",
"@codemirror/search": "0.20.1", "@codemirror/search": "0.20.1",
"@codemirror/state": "0.20.0", "@codemirror/state": "0.20.0",
"@codemirror/tooltip": "0.19.16", "@codemirror/tooltip": "0.19.16",
"@codemirror/view": "0.20.3", "@codemirror/view": "0.20.6",
"@lezer/common": "0.16.0", "@lezer/common": "0.16.0",
"@quasar/extras": "1.13.6", "@quasar/extras": "1.14.0",
"@tiptap/core": "2.0.0-beta.175", "@tiptap/core": "2.0.0-beta.176",
"@tiptap/extension-code-block": "2.0.0-beta.37", "@tiptap/extension-code-block": "2.0.0-beta.37",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68", "@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
"@tiptap/extension-color": "2.0.0-beta.9", "@tiptap/extension-color": "2.0.0-beta.9",
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
"@tiptap/extension-highlight": "2.0.0-beta.33", "@tiptap/extension-highlight": "2.0.0-beta.33",
"@tiptap/extension-history": "2.0.0-beta.21", "@tiptap/extension-history": "2.0.0-beta.21",
"@tiptap/extension-image": "2.0.0-beta.27", "@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-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-cell": "2.0.0-beta.20",
"@tiptap/extension-table-header": "2.0.0-beta.22", "@tiptap/extension-table-header": "2.0.0-beta.22",
"@tiptap/extension-table-row": "2.0.0-beta.19", "@tiptap/extension-table-row": "2.0.0-beta.19",
...@@ -55,38 +55,38 @@ ...@@ -55,38 +55,38 @@
"@tiptap/extension-text-align": "2.0.0-beta.29", "@tiptap/extension-text-align": "2.0.0-beta.29",
"@tiptap/extension-text-style": "2.0.0-beta.23", "@tiptap/extension-text-style": "2.0.0-beta.23",
"@tiptap/extension-typography": "2.0.0-beta.20", "@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", "@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", "apollo-upload-client": "17.0.0",
"browser-fs-access": "0.29.4", "browser-fs-access": "0.29.5",
"clipboard": "2.0.10", "clipboard": "2.0.11",
"filesize": "8.0.7", "filesize": "8.0.7",
"filesize-parser": "1.5.0", "filesize-parser": "1.5.0",
"graphql": "16.4.0", "graphql": "16.5.0",
"graphql-tag": "2.12.6", "graphql-tag": "2.12.6",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"jwt-decode": "3.1.2", "jwt-decode": "3.1.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"luxon": "2.3.2", "luxon": "2.4.0",
"pinia": "2.0.13", "pinia": "2.0.14",
"pug": "3.0.2", "pug": "3.0.2",
"quasar": "2.6.6", "quasar": "2.7.0",
"tippy.js": "6.3.7", "tippy.js": "6.3.7",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-network-graph": "0.5.13", "v-network-graph": "0.5.16",
"vue": "3.2.31", "vue": "3.2.31",
"vue-i18n": "9.1.9", "vue-i18n": "9.1.10",
"vue-router": "4.0.14", "vue-router": "4.0.15",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
"zxcvbn": "4.4.2" "zxcvbn": "4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@intlify/vite-plugin-vue-i18n": "3.4.0", "@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", "@types/lodash": "4.14.182",
"autoprefixer": "10.4.5", "autoprefixer": "10.4.7",
"eslint": "8.14.0", "eslint": "8.16.0",
"eslint-config-standard": "17.0.0", "eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.2.0", "eslint-plugin-n": "15.2.0",
......
<template lang="pug"> <template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide') q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 850px;') q-card(style='min-width: 850px;')
q-card-section.card-header q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-down.svg', left, size='sm') 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-card-section.q-pa-none
q-table.no-border-radius( q-table.no-border-radius(
:data='locales' :data='state.locales'
:columns='headers' :columns='headers'
row-name='code' row-name='code'
flat flat
hide-bottom hide-bottom
:rows-per-page-options='[0]' :rows-per-page-options='[0]'
:loading='loading > 0' :loading='state.loading > 0'
) )
template(v-slot:body-cell-code='props') template(v-slot:body-cell-code='props')
q-td(:props='props') q-td(:props='props')
...@@ -81,90 +81,101 @@ q-dialog(ref='dialog', @hide='onDialogHide') ...@@ -81,90 +81,101 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
flat flat
:label='$t(`common.actions.close`)' :label='t(`common.actions.close`)'
color='grey' color='grey'
padding='xs md' 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') q-spinner(color='accent', size='lg')
</template> </template>
<script> <script setup>
// import gql from 'graphql-tag' import { useI18n } from 'vue-i18n'
// import cloneDeep from 'lodash/cloneDeep' import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive, ref } from 'vue'
export default {
emits: ['ok', 'hide'], import { useAdminStore } from '../stores/admin'
data () {
return { // EMITS
locales: [],
loading: 0 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
})
const headers = [
{
label: t('admin.locale.code'),
align: 'left',
field: 'code',
name: 'code',
sortable: true,
style: 'width: 90px'
},
{
label: t('admin.locale.name'),
align: 'left',
field: 'name',
name: 'name',
sortable: true
},
{
label: t('admin.locale.nativeName'),
align: 'left',
field: 'nativeName',
name: 'nativeName',
sortable: true
}, },
computed: { {
headers () { label: t('admin.locale.rtl'),
return [ align: 'center',
{ field: 'isRTL',
label: this.$t('admin.locale.code'), name: 'isRTL',
align: 'left', sortable: false,
field: 'code', style: 'width: 10px'
name: 'code',
sortable: true,
style: 'width: 90px'
},
{
label: this.$t('admin.locale.name'),
align: 'left',
field: 'name',
name: 'name',
sortable: true
},
{
label: this.$t('admin.locale.nativeName'),
align: 'left',
field: 'nativeName',
name: 'nativeName',
sortable: true
},
{
label: this.$t('admin.locale.rtl'),
align: 'center',
field: 'isRTL',
name: 'isRTL',
sortable: false,
style: 'width: 10px'
},
{
label: this.$t('admin.locale.availability'),
align: 'center',
field: 'availability',
name: 'availability',
sortable: false,
style: 'width: 120px'
},
{
label: this.$t('admin.locale.download'),
align: 'center',
field: 'isInstalled',
name: 'isInstalled',
sortable: false,
style: 'width: 100px'
}
]
}
}, },
methods: { {
show () { label: t('admin.locale.availability'),
this.$refs.dialog.show() align: 'center',
}, field: 'availability',
hide () { name: 'availability',
this.$refs.dialog.hide() sortable: false,
}, style: 'width: 120px'
onDialogHide () { },
this.$emit('hide') {
} label: t('admin.locale.download'),
align: 'center',
field: 'isInstalled',
name: 'isInstalled',
sortable: false,
style: 'width: 100px'
} }
]
// METHODS
async function download (lc) {
} }
</script> </script>
...@@ -16,7 +16,7 @@ q-page.admin-flags ...@@ -16,7 +16,7 @@ q-page.admin-flags
type='a' type='a'
) )
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='loading > 0' :loading='loading > 0'
......
...@@ -11,7 +11,7 @@ q-page.admin-flags ...@@ -11,7 +11,7 @@ q-page.admin-flags
icon='las la-question-circle' icon='las la-question-circle'
flat flat
color='grey' color='grey'
href='https://docs.requarks.io/admin/flags' href='https://docs.js.wiki/admin/flags'
target='_blank' target='_blank'
type='a' type='a'
) )
......
...@@ -16,7 +16,7 @@ q-page.admin-general ...@@ -16,7 +16,7 @@ q-page.admin-general
type='a' type='a'
) )
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='state.loading > 0' :loading='state.loading > 0'
...@@ -385,7 +385,6 @@ import { onMounted, reactive, watch } from 'vue' ...@@ -385,7 +385,6 @@ import { onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin' import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site' import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data' import { useDataStore } from 'src/stores/data'
import { useRoute, useRouter } from 'vue-router'
// QUASAR // QUASAR
...@@ -397,11 +396,6 @@ const adminStore = useAdminStore() ...@@ -397,11 +396,6 @@ const adminStore = useAdminStore()
const siteStore = useSiteStore() const siteStore = useSiteStore()
const dataStore = useDataStore() const dataStore = useDataStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N // I18N
const { t } = useI18n() const { t } = useI18n()
......
...@@ -4,15 +4,15 @@ q-page.admin-locale ...@@ -4,15 +4,15 @@ q-page.admin-locale
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-language.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-language.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.locale.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.locale.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.locale.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.locale.subtitle') }}
.col-auto.flex .col-auto.flex
q-btn.q-mr-md( q-btn.q-mr-md(
icon='las la-download' icon='las la-download'
:label='$t(`admin.locale.downloadNew`)' :label='t(`admin.locale.downloadNew`)'
unelevated unelevated
color='primary' color='primary'
:disabled='loading > 0' :disabled='state.loading > 0'
@click='installNewLocale' @click='installNewLocale'
) )
q-separator.q-mr-md(vertical) q-separator.q-mr-md(vertical)
...@@ -20,7 +20,7 @@ q-page.admin-locale ...@@ -20,7 +20,7 @@ q-page.admin-locale
icon='las la-question-circle' icon='las la-question-circle'
flat flat
color='grey' color='grey'
href='https://docs.requarks.io/admin/locale' href='https://docs.js.wiki/admin/locale'
target='_blank' target='_blank'
type='a' type='a'
) )
...@@ -28,16 +28,16 @@ q-page.admin-locale ...@@ -28,16 +28,16 @@ q-page.admin-locale
icon='las la-redo-alt' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='loading > 0' :loading='state.loading > 0'
@click='load' @click='load'
) )
q-btn( q-btn(
unelevated unelevated
icon='mdi-check' icon='fa-solid fa-check'
:label='$t(`common.actions.apply`)' :label='t(`common.actions.apply`)'
color='secondary' color='secondary'
@click='save' @click='save'
:disabled='loading > 0' :disabled='state.loading > 0'
) )
q-separator(inset) q-separator(inset)
.row.q-pa-md.q-col-gutter-md .row.q-pa-md.q-col-gutter-md
...@@ -47,37 +47,37 @@ q-page.admin-locale ...@@ -47,37 +47,37 @@ q-page.admin-locale
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.locale.settings')}} .text-subtitle1 {{t('admin.locale.settings')}}
q-item q-item
blueprint-icon(icon='translation') blueprint-icon(icon='translation')
q-item-section q-item-section
q-item-label {{namespacing ? $t(`admin.locale.base.labelWithNS`) : $t(`admin.locale.base.label`)}} 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-label(caption) {{t(`admin.locale.base.hint`)}}
q-item-section q-item-section
q-select( q-select(
outlined outlined
v-model='selectedLocale' v-model='state.selectedLocale'
:options='installedLocales' :options='installedLocales'
option-value='code' option-value='code'
option-label='name' option-label='name'
emit-value emit-value
map-options map-options
dense dense
:aria-label='$t(`admin.locale.base.label`)' :aria-label='t(`admin.locale.base.label`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='unit') blueprint-icon(icon='unit')
q-item-section q-item-section
q-item-label {{$t(`admin.locale.namespaces.label`)}} q-item-label {{t(`admin.locale.namespaces.label`)}}
q-item-label(caption) {{$t(`admin.locale.namespaces.hint`)}} q-item-label(caption) {{t(`admin.locale.namespaces.hint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='namespacing' v-model='state.namespacing'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.locale.namespaces.label`)' :aria-label='t(`admin.locale.namespaces.label`)'
) )
q-item q-item
q-item-section q-item-section
...@@ -86,21 +86,21 @@ q-page.admin-locale ...@@ -86,21 +86,21 @@ q-page.admin-locale
q-card-section.col-auto.q-pr-none q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm') q-icon(name='las la-info-circle', size='sm')
q-card-section q-card-section
span {{ $t('admin.locale.namespacingPrefixWarning.title', { langCode: selectedLocale }) }} span {{ t('admin.locale.namespacingPrefixWarning.title', { langCode: state.selectedLocale }) }}
.text-caption.text-yellow-1 {{ $t('admin.locale.namespacingPrefixWarning.subtitle') }} .text-caption.text-yellow-1 {{ t('admin.locale.namespacingPrefixWarning.subtitle') }}
.col-5 .col-5
//- ----------------------- //- -----------------------
//- Namespacing //- 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 q-card-section
.text-subtitle1 {{$t('admin.locale.activeNamespaces')}} .text-subtitle1 {{t('admin.locale.activeNamespaces')}}
q-item( q-item(
v-for='(lc, idx) of installedLocales' v-for='(lc, idx) of installedLocales'
:key='lc.code' :key='lc.code'
:tag='lc.code !== selectedLocale ? `label` : null' :tag='lc.code !== state.selectedLocale ? `label` : null'
) )
blueprint-icon(:text='lc.code') blueprint-icon(:text='lc.code')
q-item-section q-item-section
...@@ -108,8 +108,8 @@ q-page.admin-locale ...@@ -108,8 +108,8 @@ q-page.admin-locale
q-item-label(caption) {{lc.nativeName}} q-item-label(caption) {{lc.nativeName}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
:disable='lc.code === selectedLocale' :disable='lc.code === state.selectedLocale'
v-model='namespaces' v-model='state.namespaces'
:val='lc.code' :val='lc.code'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
...@@ -121,8 +121,8 @@ q-page.admin-locale ...@@ -121,8 +121,8 @@ q-page.admin-locale
//- q-item //- q-item
//- blueprint-icon(icon='test-passed') //- blueprint-icon(icon='test-passed')
//- q-item-section //- q-item-section
//- q-item-label {{$t(`admin.locale.activeNamespaces.label`)}} //- q-item-label {{t(`admin.locale.activeNamespaces.label`)}}
//- q-item-label(caption) {{$t(`admin.locale.activeNamespaces.hint`)}} //- q-item-label(caption) {{t(`admin.locale.activeNamespaces.hint`)}}
//- q-item-section //- q-item-section
//- q-select( //- q-select(
//- outlined //- outlined
...@@ -136,204 +136,228 @@ q-page.admin-locale ...@@ -136,204 +136,228 @@ q-page.admin-locale
//- emit-value //- emit-value
//- map-options //- map-options
//- dense //- dense
//- :aria-label='$t(`admin.locale.activeNamespaces.label`)' //- :aria-label='t(`admin.locale.activeNamespaces.label`)'
//- ) //- )
</template> </template>
<script> <script setup>
import { get } from 'vuex-pathify'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import filter from 'lodash/filter' import filter from 'lodash/filter'
import _get from 'lodash/get' import _get from 'lodash/get'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import { createMetaMixin } from 'quasar'
import LocaleInstallDialog from '../components/LocaleInstallDialog.vue' import LocaleInstallDialog from '../components/LocaleInstallDialog.vue'
export default { import { useI18n } from 'vue-i18n'
mixins: [ import { useMeta, useQuasar } from 'quasar'
createMetaMixin(function () { import { computed, onMounted, reactive, watch } from 'vue'
return {
title: this.$t('admin.locale.title') import { useAdminStore } from 'src/stores/admin'
} import { useSiteStore } from 'src/stores/site'
}) import { useDataStore } from 'src/stores/data'
],
data () { // QUASAR
return {
loading: 0, const $q = useQuasar()
locales: [],
selectedLocale: 'en', // STORES
namespacing: false,
namespaces: [] const adminStore = useAdminStore()
} const siteStore = useSiteStore()
}, const dataStore = useDataStore()
computed: {
currentSiteId: get('admin/currentSiteId', false), // I18N
installedLocales () {
return filter(this.locales, ['isInstalled', true]) const { t } = useI18n()
}
}, // META
watch: {
currentSiteId (newValue) { useMeta({
this.load() title: t('admin.locale.title')
}, })
selectedLocale (newValue) {
if (!this.namespaces.includes(newValue)) { // DATA
this.namespaces.push(newValue)
} const state = reactive({
} loading: 0,
}, locales: [],
mounted () { selectedLocale: 'en',
if (this.currentSiteId) { namespacing: false,
this.load() namespaces: []
} })
},
methods: { // COMPUTED
installNewLocale () {
this.$q.dialog({ const installedLocales = computed(() => {
component: LocaleInstallDialog return filter(state.locales, ['isInstalled', true])
}).onOk(() => { })
this.load()
}) // WATCHERS
},
async load () { watch(() => adminStore.currentSiteId, (newValue) => {
this.loading++ load()
this.$q.loading.show() })
const resp = await this.$apollo.query({ watch(() => state.selectedLocale, (newValue) => {
query: gql` if (!state.namespaces.includes(newValue)) {
query getLocales ($siteId: UUID!) { state.namespaces.push(newValue)
locales { }
availability })
code
createdAt // METHODS
isInstalled
installDate function installNewLocale () {
isRTL $q.dialog({
name component: LocaleInstallDialog
nativeName }).onOk(() => {
updatedAt this.load()
} })
siteById( }
id: $siteId
) { async function load () {
id state.loading++
locale $q.loading.show()
localeNamespacing const resp = await APOLLO_CLIENT.query({
localeNamespaces query: gql`
} query getLocales ($siteId: UUID!) {
} locales {
`, availability
variables: { code
siteId: this.currentSiteId createdAt
}, isInstalled
fetchPolicy: 'network-only' installDate
}) isRTL
this.locales = cloneDeep(resp?.data?.locales) name
this.selectedLocale = cloneDeep(resp?.data?.siteById?.locale) nativeName
this.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing) updatedAt
this.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces) }
if (!this.namespaces.includes(this.selectedLocale)) { siteById(
this.namespaces.push(this.selectedLocale) id: $siteId
) {
id
locale
localeNamespacing
localeNamespaces
}
} }
this.$q.loading.hide() `,
this.loading-- variables: {
siteId: adminStore.currentSiteId
}, },
async download (lc) { fetchPolicy: 'network-only'
lc.isDownloading = true })
const respRaw = await this.$apollo.mutate({ state.locales = cloneDeep(resp?.data?.locales)
mutation: gql` state.selectedLocale = cloneDeep(resp?.data?.siteById?.locale)
mutation downloadLocale ($locale: String!) { state.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing)
localization { state.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces)
downloadLocale (locale: $locale) { if (!state.namespaces.includes(state.selectedLocale)) {
responseResult { state.namespaces.push(state.selectedLocale)
succeeded }
errorCode $q.loading.hide()
slug state.loading--
message }
}
} async function download (lc) {
lc.isDownloading = true
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation downloadLocale ($locale: String!) {
localization {
downloadLocale (locale: $locale) {
responseResult {
succeeded
errorCode
slug
message
} }
} }
`,
variables: {
locale: lc.code
} }
})
const resp = _get(respRaw, 'data.localization.downloadLocale.responseResult', {})
if (resp.succeeded) {
lc.isDownloading = false
lc.isInstalled = true
lc.updatedAt = new Date().toISOString()
lc.installDate = lc.updatedAt
this.$store.commit('showNotification', {
message: `Locale ${lc.name} has been installed successfully.`,
style: 'success',
icon: 'get_app'
})
} else {
this.$q.notify({
type: 'negative',
message: resp.message
})
} }
this.isDownloading = false `,
}, variables: {
async save () { locale: lc.code
this.loading = true }
const respRaw = await this.$apollo.mutate({ })
mutation: gql` const resp = _get(respRaw, 'data.localization.downloadLocale.responseResult', {})
mutation saveLocaleSettings ( if (resp.succeeded) {
$locale: String! lc.isDownloading = false
$autoUpdate: Boolean! lc.isInstalled = true
$namespacing: Boolean! lc.updatedAt = new Date().toISOString()
$namespaces: [String]! lc.installDate = lc.updatedAt
$q.notify({
message: `Locale ${lc.name} has been installed successfully.`,
type: 'positive'
})
} else {
$q.notify({
type: 'negative',
message: resp.message
})
}
state.isDownloading = false
}
async function save () {
state.loading = true
const respRaw = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveLocaleSettings (
$locale: String!
$autoUpdate: Boolean!
$namespacing: Boolean!
$namespaces: [String]!
) {
localization {
updateLocale(
locale: $locale
autoUpdate: $autoUpdate
namespacing: $namespacing
namespaces: $namespaces
) { ) {
localization { responseResult {
updateLocale( succeeded
locale: $locale errorCode
autoUpdate: $autoUpdate slug
namespacing: $namespacing message
namespaces: $namespaces
) {
responseResult {
succeeded
errorCode
slug
message
}
}
} }
} }
`,
variables: {
locale: this.selectedLocale,
autoUpdate: this.autoUpdate,
namespacing: this.namespacing,
namespaces: this.namespaces
} }
})
const resp = _get(respRaw, 'data.localization.updateLocale.responseResult', {})
if (resp.succeeded) {
// Change UI language
this.$i18n.locale = this.selectedLocale
this.$q.notify({
type: 'positive',
message: 'Locale settings updated successfully.'
})
setTimeout(() => {
window.location.reload(true)
}, 1000)
} else {
this.$q.notify({
type: 'negative',
message: resp.message
})
} }
this.loading = false `,
variables: {
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 = state.selectedLocale
$q.notify({
type: 'positive',
message: 'Locale settings updated successfully.'
})
setTimeout(() => {
window.location.reload(true)
}, 1000)
} else {
$q.notify({
type: 'negative',
message: resp.message
})
} }
state.loading = false
} }
// MOUNTED
onMounted(() => {
if (adminStore.currentSiteId) {
load()
}
})
</script> </script>
...@@ -4,8 +4,8 @@ q-page.admin-login ...@@ -4,8 +4,8 @@ q-page.admin-login
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bunch-of-keys.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-bunch-of-keys.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.login.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.login.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.login.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.login.subtitle') }}
.col-auto .col-auto
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle' icon='las la-question-circle'
...@@ -19,16 +19,16 @@ q-page.admin-login ...@@ -19,16 +19,16 @@ q-page.admin-login
icon='las la-redo-alt' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='loading > 0' :loading='state.loading > 0'
@click='load' @click='load'
) )
q-btn( q-btn(
unelevated unelevated
icon='mdi-check' icon='fa-solid fa-check'
:label='$t(`common.actions.apply`)' :label='t(`common.actions.apply`)'
color='secondary' color='secondary'
@click='save' @click='save'
:disabled='loading > 0' :disabled='state.loading > 0'
) )
q-separator(inset) q-separator(inset)
.row.q-pa-md.q-col-gutter-md .row.q-pa-md.q-col-gutter-md
...@@ -38,12 +38,12 @@ q-page.admin-login ...@@ -38,12 +38,12 @@ q-page.admin-login
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.login.experience')}} .text-subtitle1 {{t('admin.login.experience')}}
q-item(tag='label', v-ripple) 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-section
q-item-label {{$t(`admin.login.background`)}} q-item-label {{t(`admin.login.background`)}}
q-item-label(caption) {{$t(`admin.login.backgroundHint`)}} q-item-label(caption) {{t(`admin.login.backgroundHint`)}}
q-item-section.col-auto q-item-section.col-auto
q-btn( q-btn(
label='Upload' label='Upload'
...@@ -56,80 +56,80 @@ q-page.admin-login ...@@ -56,80 +56,80 @@ q-page.admin-login
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='close-pane') blueprint-icon(icon='close-pane')
q-item-section q-item-section
q-item-label {{$t(`admin.login.bypassScreen`)}} q-item-label {{t(`admin.login.bypassScreen`)}}
q-item-label(caption) {{$t(`admin.login.bypassScreenHint`)}} q-item-label(caption) {{t(`admin.login.bypassScreenHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.authAutoLogin' v-model='state.config.authAutoLogin'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.login.bypassScreen`)' :aria-label='t(`admin.login.bypassScreen`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple) q-item(tag='label', v-ripple)
blueprint-icon(icon='no-access') blueprint-icon(icon='no-access')
q-item-section q-item-section
q-item-label {{$t(`admin.login.bypassUnauthorized`)}} q-item-label {{t(`admin.login.bypassUnauthorized`)}}
q-item-label(caption) {{$t(`admin.login.bypassUnauthorizedHint`)}} q-item-label(caption) {{t(`admin.login.bypassUnauthorizedHint`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='config.authBypassUnauthorized' v-model='state.config.authBypassUnauthorized'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.login.bypassUnauthorized`)' :aria-label='t(`admin.login.bypassUnauthorized`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='double-right') blueprint-icon(icon='double-right')
q-item-section q-item-section
q-item-label {{$t(`admin.login.loginRedirect`)}} q-item-label {{t(`admin.login.loginRedirect`)}}
q-item-label(caption) {{$t(`admin.login.loginRedirectHint`)}} q-item-label(caption) {{t(`admin.login.loginRedirectHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.loginRedirect' v-model='state.config.loginRedirect'
dense dense
:rules=`[ :rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.loginRedirectInvalidChars') val => state.invalidCharsRegex.test(val) || t('admin.login.loginRedirectInvalidChars')
]` ]`
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.login.loginRedirect`)' :aria-label='t(`admin.login.loginRedirect`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='chevron-right') blueprint-icon(icon='chevron-right')
q-item-section q-item-section
q-item-label {{$t(`admin.login.welcomeRedirect`)}} q-item-label {{t(`admin.login.welcomeRedirect`)}}
q-item-label(caption) {{$t(`admin.login.welcomeRedirectHint`)}} q-item-label(caption) {{t(`admin.login.welcomeRedirectHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.welcomeRedirect' v-model='state.config.welcomeRedirect'
dense dense
:rules=`[ :rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.welcomeRedirectInvalidChars') val => state.invalidCharsRegex.test(val) || t('admin.login.welcomeRedirectInvalidChars')
]` ]`
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.login.welcomeRedirect`)' :aria-label='t(`admin.login.welcomeRedirect`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='exit') blueprint-icon(icon='exit')
q-item-section q-item-section
q-item-label {{$t(`admin.login.logoutRedirect`)}} q-item-label {{t(`admin.login.logoutRedirect`)}}
q-item-label(caption) {{$t(`admin.login.logoutRedirectHint`)}} q-item-label(caption) {{t(`admin.login.logoutRedirectHint`)}}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='config.logoutRedirect' v-model='state.config.logoutRedirect'
dense dense
:rules=`[ :rules=`[
val => invalidCharsRegex.test(val) || $t('admin.login.logoutRedirectInvalidChars') val => state.invalidCharsRegex.test(val) || t('admin.login.logoutRedirectInvalidChars')
]` ]`
hide-bottom-space hide-bottom-space
:aria-label='$t(`admin.login.logoutRedirect`)' :aria-label='t(`admin.login.logoutRedirect`)'
) )
.col-12.col-lg-6 .col-12.col-lg-6
...@@ -138,11 +138,11 @@ q-page.admin-login ...@@ -138,11 +138,11 @@ q-page.admin-login
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.login.providers')}} .text-subtitle1 {{t('admin.login.providers')}}
q-card-section.admin-login-providers.q-pt-none q-card-section.admin-login-providers.q-pt-none
draggable( draggable(
class='q-list rounded-borders' class='q-list rounded-borders'
:list='providers' :list='state.providers'
:animation='150' :animation='150'
handle='.handle' handle='.handle'
@end='dragStarted = false' @end='dragStarted = false'
...@@ -171,117 +171,137 @@ q-page.admin-login ...@@ -171,117 +171,137 @@ q-page.admin-login
q-card-section.items-center(horizontal) q-card-section.items-center(horizontal)
q-card-section.col-auto.q-pr-none q-card-section.col-auto.q-pr-none
q-icon(name='las la-info-circle', size='sm') 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> </template>
<script> <script setup>
import { get } from 'vuex-pathify' import { get } from 'vuex-pathify'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import { createMetaMixin } from 'quasar'
export default { import { useI18n } from 'vue-i18n'
mixins: [ import { useMeta, useQuasar } from 'quasar'
createMetaMixin(function () { import { computed, onMounted, reactive, watch } from 'vue'
return {
title: this.$t('admin.login.title') import { useAdminStore } from 'src/stores/admin'
}
}) // QUASAR
],
components: { const $q = useQuasar()
draggable
}, // STORES
data () {
return { const adminStore = useAdminStore()
invalidCharsRegex: /^[^<>"]+$/,
loading: 0, // I18N
config: {
authAutoLogin: false, const { t } = useI18n()
authHideLocal: false,
authBypassUnauthorized: false, // META
loginRedirect: '/',
welcomeRedirect: '/', useMeta({
logoutRedirect: '/' title: t('admin.login.title')
}, })
providers: [
{ id: 'local', label: 'Local Authentication', provider: 'Username-Password', icon: 'database', isActive: true }, // DATA
{ id: 'google', label: 'Google', provider: 'Google', icon: 'google', isActive: true },
{ id: 'slack', label: 'Slack', provider: 'Slack', icon: 'slack', isActive: false } const state = reactive({
] invalidCharsRegex: /^[^<>"]+$/,
} loading: 0,
}, config: {
computed: { authAutoLogin: false,
currentSiteId: get('admin/currentSiteId', false) authHideLocal: false,
authBypassUnauthorized: false,
loginRedirect: '/',
welcomeRedirect: '/',
logoutRedirect: '/'
}, },
methods: { providers: [
async load () { { id: 'local', label: 'Local Authentication', provider: 'Username-Password', icon: 'database', isActive: true },
this.loading++ { id: 'google', label: 'Google', provider: 'Google', icon: 'google', isActive: true },
this.$q.loading.show() { id: 'slack', label: 'Slack', provider: 'Slack', icon: 'slack', isActive: false }
// const resp = await this.$apollo.query({ ]
// query: gql` })
// query getSite (
// $id: UUID! // METHODS
// ) {
// siteById( async function load () {
// id: $id state.loading++
// ) { $q.loading.show()
// id // const resp = await APOLLO_CLIENT.query({
// } // query: gql`
// } // query getSite (
// `, // $id: UUID!
// variables: { // ) {
// id: this.currentSiteId // siteById(
// }, // id: $id
// fetchPolicy: 'network-only' // ) {
// }) // id
// this.config = cloneDeep(resp?.data?.siteById) // }
this.$q.loading.hide() // }
this.loading-- // `,
}, // variables: {
async save () { // id: adminStore.currentSiteId
try { // },
await this.$apollo.mutate({ // fetchPolicy: 'network-only'
mutation: gql` // })
mutation saveLoginSettings ( // this.config = cloneDeep(resp?.data?.siteById)
$authAutoLogin: Boolean $q.loading.hide()
$authEnforce2FA: Boolean state.loading--
}
async function save () {
try {
await APOLLO_CLIENT.mutate({
mutation: gql`
mutation saveLoginSettings (
$authAutoLogin: Boolean
$authEnforce2FA: Boolean
) {
site {
updateConfig(
authAutoLogin: $authAutoLogin,
authEnforce2FA: $authEnforce2FA
) { ) {
site { responseResult {
updateConfig( succeeded
authAutoLogin: $authAutoLogin, errorCode
authEnforce2FA: $authEnforce2FA slug
) { message
responseResult {
succeeded
errorCode
slug
message
}
}
} }
} }
`,
variables: {
authAutoLogin: this.config.authAutoLogin ?? false,
authEnforce2FA: this.config.authEnforce2FA ?? false
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
} }
}) }
this.$store.commit('showNotification', { `,
style: 'success', variables: {
message: 'Configuration saved successfully.', authAutoLogin: state.config.authAutoLogin ?? false,
icon: 'check' authEnforce2FA: state.config.authEnforce2FA ?? false
}) },
} catch (err) { watchLoading (isLoading) {
this.$store.commit('pushGraphError', err) this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
} }
} })
$q.notify({
type: 'positive',
message: 'Configuration saved successfully.'
})
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
} }
} }
// MOUNTED
onMounted(() => {
if (adminStore.currentSiteId) {
load()
}
})
</script> </script>
<style lang='scss'> <style lang='scss'>
......
...@@ -16,7 +16,7 @@ q-page.admin-locale ...@@ -16,7 +16,7 @@ q-page.admin-locale
target='_blank' target='_blank'
) )
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
@click='refresh' @click='refresh'
...@@ -100,7 +100,7 @@ q-page.admin-locale ...@@ -100,7 +100,7 @@ q-page.admin-locale
<script setup> <script setup>
import { useMeta, useQuasar } from 'quasar' import { useMeta, useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n' 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 { useRouter } from 'vue-router'
import { useAdminStore } from '../stores/admin' import { useAdminStore } from '../stores/admin'
......
...@@ -4,22 +4,22 @@ q-page.admin-storage ...@@ -4,22 +4,22 @@ q-page.admin-storage
.col-auto .col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-ssd.svg') img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-ssd.svg')
.col.q-pl-md .col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.storage.title') }} .text-h5.text-primary.animated.fadeInLeft {{ t('admin.storage.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.storage.subtitle') }} .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.storage.subtitle') }}
.col-auto.flex .col-auto.flex
q-spinner-tail.q-mr-md( q-spinner-tail.q-mr-md(
v-show='loading > 0' v-show='state.loading > 0'
color='accent' color='accent'
size='sm' size='sm'
) )
q-btn-toggle.q-mr-md( q-btn-toggle.q-mr-md(
v-model='displayMode' v-model='state.displayMode'
push push
no-caps no-caps
toggle-color='black' toggle-color='black'
:options=`[ :options=`[
{ label: $t('admin.storage.targets'), value: 'targets' }, { label: t('admin.storage.targets'), value: 'targets' },
{ label: $t('admin.storage.deliveryPaths'), value: 'delivery' } { label: t('admin.storage.deliveryPaths'), value: 'delivery' }
]` ]`
) )
q-separator.q-mr-md(vertical) q-separator.q-mr-md(vertical)
...@@ -33,11 +33,11 @@ q-page.admin-storage ...@@ -33,11 +33,11 @@ q-page.admin-storage
) )
q-btn( q-btn(
unelevated unelevated
icon='mdi-check' icon='fa-solid fa-check'
:label='$t(`common.actions.apply`)' :label='t(`common.actions.apply`)'
color='secondary' color='secondary'
@click='save' @click='save'
:loading='loading > 0' :loading='state.loading > 0'
) )
q-separator(inset) q-separator(inset)
...@@ -45,7 +45,7 @@ q-page.admin-storage ...@@ -45,7 +45,7 @@ q-page.admin-storage
//- TARGETS //- 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 .col-auto
q-card.rounded-borders.bg-dark q-card.rounded-borders.bg-dark
q-list( q-list(
...@@ -54,11 +54,11 @@ q-page.admin-storage ...@@ -54,11 +54,11 @@ q-page.admin-storage
dark dark
) )
q-item( q-item(
v-for='tgt of targets' v-for='tgt of state.targets'
:key='tgt.key' :key='tgt.key'
active-class='bg-primary text-white' active-class='bg-primary text-white'
:active='selectedTarget === tgt.id' :active='state.selectedTarget === tgt.id'
:to='`/_admin/` + currentSiteId + `/storage/` + tgt.id' :to='`/_admin/` + adminStore.currentSiteId + `/storage/` + tgt.id'
clickable clickable
) )
q-item-section(side) q-item-section(side)
...@@ -70,76 +70,76 @@ q-page.admin-storage ...@@ -70,76 +70,76 @@ q-page.admin-storage
q-item-label(caption, :class='getTargetSubtitleColor(tgt)') {{getTargetSubtitle(tgt)}} q-item-label(caption, :class='getTargetSubtitleColor(tgt)') {{getTargetSubtitle(tgt)}}
q-item-section(side) q-item-section(side)
q-spinner-rings(:color='tgt.isEnabled ? `positive` : `negative`', size='sm') q-spinner-rings(:color='tgt.isEnabled ? `positive` : `negative`', size='sm')
.col(v-if='target') .col(v-if='state.target')
//- ----------------------- //- -----------------------
//- Content Types //- Content Types
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm q-card.shadow-1.q-pb-sm
q-card-section q-card-section
.text-subtitle1 {{$t('admin.storage.contentTypes')}} .text-subtitle1 {{t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ $t('admin.storage.contentTypesHint') }} .text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
q-item(tag='label') q-item(tag='label')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.contentTypes.activeTypes' v-model='state.target.contentTypes.activeTypes'
:color='target.module === `db` ? `grey` : `primary`' :color='state.target.module === `db` ? `grey` : `primary`'
val='pages' val='pages'
:aria-label='$t(`admin.storage.contentTypePages`)' :aria-label='t(`admin.storage.contentTypePages`)'
:disable='target.module === `db`' :disable='state.target.module === `db`'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.contentTypePages`)}} q-item-label {{t(`admin.storage.contentTypePages`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypePagesHint`)}} q-item-label(caption) {{t(`admin.storage.contentTypePagesHint`)}}
q-item(tag='label') q-item(tag='label')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.contentTypes.activeTypes' v-model='state.target.contentTypes.activeTypes'
color='primary' color='primary'
val='images' val='images'
:aria-label='$t(`admin.storage.contentTypeImages`)' :aria-label='t(`admin.storage.contentTypeImages`)'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.contentTypeImages`)}} q-item-label {{t(`admin.storage.contentTypeImages`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeImagesHint`)}} q-item-label(caption) {{t(`admin.storage.contentTypeImagesHint`)}}
q-item(tag='label') q-item(tag='label')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.contentTypes.activeTypes' v-model='state.target.contentTypes.activeTypes'
color='primary' color='primary'
val='documents' val='documents'
:aria-label='$t(`admin.storage.contentTypeDocuments`)' :aria-label='t(`admin.storage.contentTypeDocuments`)'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.contentTypeDocuments`)}} q-item-label {{t(`admin.storage.contentTypeDocuments`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeDocumentsHint`)}} q-item-label(caption) {{t(`admin.storage.contentTypeDocumentsHint`)}}
q-item(tag='label') q-item(tag='label')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.contentTypes.activeTypes' v-model='state.target.contentTypes.activeTypes'
color='primary' color='primary'
val='others' val='others'
:aria-label='$t(`admin.storage.contentTypeOthers`)' :aria-label='t(`admin.storage.contentTypeOthers`)'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.contentTypeOthers`)}} q-item-label {{t(`admin.storage.contentTypeOthers`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeOthersHint`)}} q-item-label(caption) {{t(`admin.storage.contentTypeOthersHint`)}}
q-item(tag='label') q-item(tag='label')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.contentTypes.activeTypes' v-model='state.target.contentTypes.activeTypes'
color='primary' color='primary'
val='large' val='large'
:aria-label='$t(`admin.storage.contentTypeLargeFiles`)' :aria-label='t(`admin.storage.contentTypeLargeFiles`)'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.contentTypeLargeFiles`)}} q-item-label {{t(`admin.storage.contentTypeLargeFiles`)}}
q-item-label(caption) {{$t(`admin.storage.contentTypeLargeFilesHint`)}} 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.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
q-item-section(side) q-item-section(side)
q-input( q-input(
outlined outlined
:label='$t(`admin.storage.contentTypeLargeFilesThreshold`)' :label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
v-model='target.contentTypes.largeThreshold' v-model='state.target.contentTypes.largeThreshold'
style='min-width: 150px;' style='min-width: 150px;'
dense dense
) )
...@@ -149,41 +149,41 @@ q-page.admin-storage ...@@ -149,41 +149,41 @@ q-page.admin-storage
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.storage.assetDelivery')}} .text-subtitle1 {{t('admin.storage.assetDelivery')}}
.text-body2.text-grey {{ $t('admin.storage.assetDeliveryHint') }} .text-body2.text-grey {{ t('admin.storage.assetDeliveryHint') }}
q-item(:tag='target.assetDelivery.isStreamingSupported ? `label` : null') q-item(:tag='state.target.assetDelivery.isStreamingSupported ? `label` : null')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.assetDelivery.streaming' v-model='state.target.assetDelivery.streaming'
:color='target.module === `db` || !target.assetDelivery.isStreamingSupported ? `grey` : `primary`' :color='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
:aria-label='$t(`admin.storage.contentTypePages`)' :aria-label='t(`admin.storage.contentTypePages`)'
:disable='target.module === `db` || !target.assetDelivery.isStreamingSupported' :disable='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.assetStreaming`)}} q-item-label {{t(`admin.storage.assetStreaming`)}}
q-item-label(caption) {{$t(`admin.storage.assetStreamingHint`)}} 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-label.text-deep-orange(v-if='!state.target.assetDelivery.isStreamingSupported', caption) {{t(`admin.storage.assetStreamingNotSupported`)}}
q-item(:tag='target.assetDelivery.isDirectAccessSupported ? `label` : null') q-item(:tag='state.target.assetDelivery.isDirectAccessSupported ? `label` : null')
q-item-section(avatar) q-item-section(avatar)
q-checkbox( q-checkbox(
v-model='target.assetDelivery.directAccess' v-model='state.target.assetDelivery.directAccess'
:color='!target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`' :color='!state.target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
:aria-label='$t(`admin.storage.contentTypePages`)' :aria-label='t(`admin.storage.contentTypePages`)'
:disable='!target.assetDelivery.isDirectAccessSupported' :disable='!state.target.assetDelivery.isDirectAccessSupported'
) )
q-item-section q-item-section
q-item-label {{$t(`admin.storage.assetDirectAccess`)}} q-item-label {{t(`admin.storage.assetDirectAccess`)}}
q-item-label(caption) {{$t(`admin.storage.assetDirectAccessHint`)}} 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.text-deep-orange(v-if='!state.target.assetDelivery.isDirectAccessSupported', caption) {{t(`admin.storage.assetDirectAccessNotSupported`)}}
//- ----------------------- //- -----------------------
//- Setup //- 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 q-card-section
.text-subtitle1 {{$t('admin.storage.setup')}} .text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ $t('admin.storage.setupHint') }} .text-body2.text-grey {{ t('admin.storage.setupHint') }}
template(v-if='target.setup.handler === `github` && target.setup.state === `notconfigured`') template(v-if='state.target.setup.handler === `github` && state.target.setup.state === `notconfigured`')
q-item q-item
blueprint-icon(icon='test-account') blueprint-icon(icon='test-account')
q-item-section q-item-section
...@@ -191,42 +191,42 @@ q-page.admin-storage ...@@ -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-label(caption) Whether to use an organization or personal GitHub account during setup.
q-item-section.col-auto q-item-section.col-auto
q-btn-toggle( q-btn-toggle(
v-model='target.setup.values.accountType' v-model='state.target.setup.values.accountType'
push push
glossy glossy
no-caps no-caps
toggle-color='primary' toggle-color='primary'
:options=`[ :options=`[
{ label: $t('admin.storage.githubAccTypeOrg'), value: 'org' }, { label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
{ label: $t('admin.storage.githubAccTypePersonal'), value: 'personal' } { label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
]` ]`
) )
q-separator.q-my-sm(inset) 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 q-item
blueprint-icon(icon='github') blueprint-icon(icon='github')
q-item-section q-item-section
q-item-label {{ $t('admin.storage.githubOrg') }} q-item-label {{ t('admin.storage.githubOrg') }}
q-item-label(caption) {{ $t('admin.storage.githubOrgHint') }} q-item-label(caption) {{ t('admin.storage.githubOrgHint') }}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='target.setup.values.org' v-model='state.target.setup.values.org'
dense dense
:aria-label='$t(`admin.storage.githubOrg`)' :aria-label='t(`admin.storage.githubOrg`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
blueprint-icon(icon='dns') blueprint-icon(icon='dns')
q-item-section q-item-section
q-item-label {{ $t('admin.storage.githubPublicUrl') }} q-item-label {{ t('admin.storage.githubPublicUrl') }}
q-item-label(caption) {{ $t('admin.storage.githubPublicUrlHint') }} q-item-label(caption) {{ t('admin.storage.githubPublicUrlHint') }}
q-item-section q-item-section
q-input( q-input(
outlined outlined
v-model='target.setup.values.publicUrl' v-model='state.target.setup.values.publicUrl'
dense dense
:aria-label='$t(`admin.storage.githubPublicUrl`)' :aria-label='t(`admin.storage.githubPublicUrl`)'
) )
q-card-section.q-pt-sm.text-right q-card-section.q-pt-sm.text-right
form( form(
...@@ -242,37 +242,37 @@ q-page.admin-storage ...@@ -242,37 +242,37 @@ q-page.admin-storage
q-btn( q-btn(
unelevated unelevated
icon='las la-angle-double-right' icon='las la-angle-double-right'
:label='$t(`admin.storage.startSetup`)' :label='t(`admin.storage.startSetup`)'
color='secondary' color='secondary'
@click='setupGitHub' @click='setupGitHub'
:loading='setupCfg.loading' :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-card-section.q-py-none
q-banner( q-banner(
rounded rounded
:class='$q.dark.isActive ? `bg-teal-9 text-white` : `bg-teal-1 text-teal-9`' :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-card-section.q-pt-sm.text-right
q-btn.q-mr-sm( q-btn.q-mr-sm(
unelevated unelevated
icon='las la-times-circle' icon='las la-times-circle'
:label='$t(`admin.storage.cancelSetup`)' :label='t(`admin.storage.cancelSetup`)'
color='negative' color='negative'
@click='setupDestroy' @click='setupDestroy'
) )
q-btn( q-btn(
unelevated unelevated
icon='las la-angle-double-right' icon='las la-angle-double-right'
:label='$t(`admin.storage.finishSetup`)' :label='t(`admin.storage.finishSetup`)'
color='secondary' color='secondary'
@click='setupGitHubStep(`verify`)' @click='setupGitHubStep(`verify`)'
:loading='setupCfg.loading' :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 q-card-section
.text-subtitle1 {{$t('admin.storage.setup')}} .text-subtitle1 {{t('admin.storage.setup')}}
.text-body2.text-grey {{ $t('admin.storage.setupConfiguredHint') }} .text-body2.text-grey {{ t('admin.storage.setupConfiguredHint') }}
q-item q-item
blueprint-icon.self-start(icon='matches', :hue-rotate='140') blueprint-icon.self-start(icon='matches', :hue-rotate='140')
q-item-section q-item-section
...@@ -285,7 +285,7 @@ q-page.admin-storage ...@@ -285,7 +285,7 @@ q-page.admin-storage
icon='las la-arrow-circle-right' icon='las la-arrow-circle-right'
color='negative' color='negative'
@click='setupDestroy' @click='setupDestroy'
:label='$t(`admin.storage.uninstall`)' :label='t(`admin.storage.uninstall`)'
) )
//- ----------------------- //- -----------------------
...@@ -293,14 +293,14 @@ q-page.admin-storage ...@@ -293,14 +293,14 @@ q-page.admin-storage
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.storage.config')}} .text-subtitle1 {{t('admin.storage.config')}}
q-banner.q-mt-md( 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 rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`' :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.noConfigOption')}} ) {{t('admin.storage.noConfigOption')}}
template( template(
v-for='(cfg, cfgKey, idx) in target.config' v-for='(cfg, cfgKey, idx) in state.target.config'
) )
template( template(
v-if='configIfCheck(cfg.if)' v-if='configIfCheck(cfg.if)'
...@@ -317,7 +317,7 @@ q-page.admin-storage ...@@ -317,7 +317,7 @@ q-page.admin-storage
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.general.allowComments`)' :aria-label='t(`admin.general.allowComments`)'
:disable='cfg.readOnly' :disable='cfg.readOnly'
) )
q-item(v-else) q-item(v-else)
...@@ -364,34 +364,34 @@ q-page.admin-storage ...@@ -364,34 +364,34 @@ q-page.admin-storage
//- ----------------------- //- -----------------------
//- Sync //- 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 q-card-section
.text-subtitle1 {{$t('admin.storage.sync')}} .text-subtitle1 {{t('admin.storage.sync')}}
q-banner.q-mt-md( q-banner.q-mt-md(
rounded rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`' :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.noSyncModes')}} ) {{t('admin.storage.noSyncModes')}}
//- ----------------------- //- -----------------------
//- Actions //- Actions
//- ----------------------- //- -----------------------
q-card.shadow-1.q-pb-sm.q-mt-md q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section q-card-section
.text-subtitle1 {{$t('admin.storage.actions')}} .text-subtitle1 {{t('admin.storage.actions')}}
q-banner.q-mt-md( q-banner.q-mt-md(
v-if='!target.actions || target.actions.length < 1' v-if='!state.target.actions || state.target.actions.length < 1'
rounded rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`' :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( q-banner.q-mt-md(
v-else-if='!target.isEnabled' v-else-if='!state.target.isEnabled'
rounded rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`' :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{$t('admin.storage.actionsInactiveWarn')}} ) {{t('admin.storage.actionsInactiveWarn')}}
template( template(
v-if='target.isEnabled' v-if='state.target.isEnabled'
v-for='(act, idx) in target.actions' v-for='(act, idx) in state.target.actions'
) )
q-separator.q-my-sm(inset, v-if='idx > 0') q-separator.q-my-sm(inset, v-if='idx > 0')
q-item q-item
...@@ -406,32 +406,32 @@ q-page.admin-storage ...@@ -406,32 +406,32 @@ q-page.admin-storage
icon='las la-arrow-circle-right' icon='las la-arrow-circle-right'
color='primary' color='primary'
@click='' @click=''
:label='$t(`common.actions.proceed`)' :label='t(`common.actions.proceed`)'
) )
.col-auto(v-if='target') .col-auto(v-if='state.target')
//- ----------------------- //- -----------------------
//- Infobox //- Infobox
//- ----------------------- //- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 350px;') q-card.rounded-borders.q-pb-md(style='width: 350px;')
q-card-section q-card-section
.text-subtitle1 {{target.title}} .text-subtitle1 {{state.target.title}}
q-img.q-mt-sm.rounded-borders( q-img.q-mt-sm.rounded-borders(
:src='target.banner' :src='target.banner'
fit='cover' fit='cover'
no-spinner no-spinner
) )
.text-body2.q-mt-md {{target.description}} .text-body2.q-mt-md {{state.target.description}}
q-separator.q-mb-sm(inset) q-separator.q-mb-sm(inset)
q-item q-item
q-item-section q-item-section
q-item-label.text-grey {{$t(`admin.storage.vendor`)}} q-item-label.text-grey {{t(`admin.storage.vendor`)}}
q-item-label {{target.vendor}} q-item-label {{state.target.vendor}}
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
q-item-section q-item-section
q-item-label.text-grey {{$t(`admin.storage.vendorWebsite`)}} 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: a(:href='state.target.website', target='_blank', rel='noreferrer') {{state.target.website}}
//- ----------------------- //- -----------------------
//- Status //- Status
...@@ -439,25 +439,25 @@ q-page.admin-storage ...@@ -439,25 +439,25 @@ q-page.admin-storage
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;') q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section q-card-section
.text-subtitle1 Status .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(tag='label')
q-item-section q-item-section
q-item-label {{$t(`admin.storage.enabled`)}} q-item-label {{t(`admin.storage.enabled`)}}
q-item-label(caption) {{$t(`admin.storage.enabledHint`)}} 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.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.enabledForced`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='target.isEnabled' v-model='state.target.isEnabled'
:disable='target.module === `db` || (target.setup && target.setup.handler && target.setup.state !== `configured`)' :disable='state.target.module === `db` || (state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.general.allowSearch`)' :aria-label='t(`admin.general.allowSearch`)'
) )
q-separator.q-my-sm(inset) q-separator.q-my-sm(inset)
q-item q-item
q-item-section 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. q-item-label.text-positive No issues detected.
//- ----------------------- //- -----------------------
...@@ -465,50 +465,50 @@ q-page.admin-storage ...@@ -465,50 +465,50 @@ q-page.admin-storage
//- ----------------------- //- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;') q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section q-card-section
.text-subtitle1 {{$t(`admin.storage.versioning`)}} .text-subtitle1 {{t(`admin.storage.versioning`)}}
.text-body2.text-grey {{$t(`admin.storage.versioningHint`)}} .text-body2.text-grey {{t(`admin.storage.versioningHint`)}}
q-item(:tag='target.versioning.isSupported ? `label` : null') q-item(:tag='state.target.versioning.isSupported ? `label` : null')
q-item-section q-item-section
q-item-label {{$t(`admin.storage.useVersioning`)}} q-item-label {{t(`admin.storage.useVersioning`)}}
q-item-label(caption) {{$t(`admin.storage.useVersioningHint`)}} 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='!state.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.text-deep-orange(v-if='state.target.versioning.isForceEnabled', caption) {{t(`admin.storage.versioningForceEnabled`)}}
q-item-section(avatar) q-item-section(avatar)
q-toggle( q-toggle(
v-model='target.versioning.enabled' v-model='state.target.versioning.enabled'
:disable='!target.versioning.isSupported || target.versioning.isForceEnabled' :disable='!state.target.versioning.isSupported || state.target.versioning.isForceEnabled'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
:aria-label='$t(`admin.storage.useVersioning`)' :aria-label='t(`admin.storage.useVersioning`)'
) )
//- ========================================== //- ==========================================
//- DELIVERY PATHS //- 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 .col
q-card.rounded-borders q-card.rounded-borders
q-card-section.flex.items-center 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-chip(square, dense, color='blue-1', text-color='blue-8')
q-avatar(icon='las la-ellipsis-h', color='blue', text-color='white') 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-chip(square, dense, color='teal-1', text-color='teal-8')
q-avatar(icon='las la-ellipsis-h', color='positive', text-color='white') 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-chip(square, dense, color='red-1', text-color='red-8')
q-avatar(icon='las la-minus', color='negative', text-color='white') 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 q-separator
v-network-graph( v-network-graph(
:zoom-level='2' :zoom-level='2'
:configs='deliveryConfig' :configs='state.deliveryConfig'
:nodes='deliveryNodes' :nodes='state.deliveryNodes'
:edges='deliveryEdges' :edges='state.deliveryEdges'
:paths='deliveryPaths' :paths='state.deliveryPaths'
:layouts='deliveryLayouts' :layouts='state.deliveryLayouts'
style='height: 600px;' style='height: 600px;'
) )
template(#override-node='{ nodeId, scale, config, ...slotProps }') template(#override-node='{ nodeId, scale, config, ...slotProps }')
...@@ -522,57 +522,57 @@ q-page.admin-storage ...@@ -522,57 +522,57 @@ q-page.admin-storage
v-bind='slotProps' v-bind='slotProps'
) )
image( 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' :x='(-config.radius + 5) * scale'
:y='(-config.radius + 5) * scale' :y='(-config.radius + 5) * scale'
:width='(config.radius - 5) * scale * 2' :width='(config.radius - 5) * scale * 2'
:height='(config.radius - 5) * scale * 2' :height='(config.radius - 5) * scale * 2'
:xlink:href='deliveryNodes[nodeId].icon' :xlink:href='state.deliveryNodes[nodeId].icon'
) )
text( text(
v-if='deliveryNodes[nodeId].icon && deliveryNodes[nodeId].iconText' v-if='state.deliveryNodes[nodeId].icon && state.deliveryNodes[nodeId].iconText'
:class='deliveryNodes[nodeId].icon' :class='state.deliveryNodes[nodeId].icon'
:font-size='22 * scale' :font-size='22 * scale'
fill='#ffffff' fill='#ffffff'
text-anchor='middle' text-anchor='middle'
dominant-baseline='central' dominant-baseline='central'
v-html='deliveryNodes[nodeId].iconText' v-html='state.deliveryNodes[nodeId].iconText'
) )
//- .overline.my-5 {{$t('admin.storage.syncDirection')}} //- .overline.my-5 {{t('admin.storage.syncDirection')}}
//- .body-2.ml-3 {{$t('admin.storage.syncDirectionSubtitle')}} //- .body-2.ml-3 {{t('admin.storage.syncDirectionSubtitle')}}
//- .pr-3.pt-3 //- .pr-3.pt-3
//- v-radio-group.ml-3.py-0(v-model='target.mode') //- v-radio-group.ml-3.py-0(v-model='target.mode')
//- v-radio( //- v-radio(
//- :label='$t(`admin.storage.syncDirBi`)' //- :label='t(`admin.storage.syncDirBi`)'
//- color='primary' //- color='primary'
//- value='sync' //- value='sync'
//- :disabled='target.supportedModes.indexOf(`sync`) < 0' //- :disabled='target.supportedModes.indexOf(`sync`) < 0'
//- ) //- )
//- v-radio( //- v-radio(
//- :label='$t(`admin.storage.syncDirPush`)' //- :label='t(`admin.storage.syncDirPush`)'
//- color='primary' //- color='primary'
//- value='push' //- value='push'
//- :disabled='target.supportedModes.indexOf(`push`) < 0' //- :disabled='target.supportedModes.indexOf(`push`) < 0'
//- ) //- )
//- v-radio( //- v-radio(
//- :label='$t(`admin.storage.syncDirPull`)' //- :label='t(`admin.storage.syncDirPull`)'
//- color='primary' //- color='primary'
//- value='pull' //- value='pull'
//- :disabled='target.supportedModes.indexOf(`pull`) < 0' //- :disabled='target.supportedModes.indexOf(`pull`) < 0'
//- ) //- )
//- .body-2.ml-3 //- .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')}}] //- 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')}} //- .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')}}] //- 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')}} //- .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')}}] //- 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')}} //- .pb-3 {{t('admin.storage.syncDirPullHint')}}
//- template(v-if='target.hasSchedule') //- template(v-if='target.hasSchedule')
//- v-divider.mt-3 //- v-divider.mt-3
//- .overline.my-5 {{$t('admin.storage.syncSchedule')}} //- .overline.my-5 {{t('admin.storage.syncSchedule')}}
//- .body-2.ml-3 {{$t('admin.storage.syncScheduleHint')}} //- .body-2.ml-3 {{t('admin.storage.syncScheduleHint')}}
//- .pa-3 //- .pa-3
//- duration-picker(v-model='target.syncInterval') //- duration-picker(v-model='target.syncInterval')
//- i18next.caption.mt-3(path='admin.storage.syncScheduleCurrent', tag='div') //- i18next.caption.mt-3(path='admin.storage.syncScheduleCurrent', tag='div')
...@@ -582,604 +582,662 @@ q-page.admin-storage ...@@ -582,604 +582,662 @@ q-page.admin-storage
</template> </template>
<script> <script setup>
import find from 'lodash/find' import find from 'lodash/find'
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { get } from 'vuex-pathify'
import transform from 'lodash/transform' 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' import GithubSetupInstallDialog from '../components/GithubSetupInstallDialog.vue'
export default { // QUASAR
data () {
return { const $q = useQuasar()
loading: 0,
displayMode: 'targets', // STORES
runningAction: false,
runningActionHandler: '', const adminStore = useAdminStore()
selectedTarget: '', const siteStore = useSiteStore()
desiredTarget: '', const dataStore = useDataStore()
target: null,
targets: [], // ROUTER
setupCfg: {
action: '', const router = useRouter()
manifest: '', const route = useRoute()
loading: false
}, // I18N
deliveryNodes: {},
deliveryEdges: {}, const { t } = useI18n()
deliveryLayouts: {
nodes: {} // META
},
deliveryPaths: [], useMeta({
deliveryConfig: vNG.defineConfigs({ title: t('admin.storage.title')
view: { })
layoutHandler: new vNG.GridLayout({ grid: 15 }),
fit: true, // DATA
mouseWheelZoomEnabled: false,
grid: { const state = reactive({
visible: true, loading: 0,
interval: 2.5, displayMode: 'targets',
thickIncrements: 0 runningAction: false,
} runningActionHandler: '',
}, selectedTarget: '',
node: { desiredTarget: '',
draggable: false, target: null,
selectable: true, targets: [],
normal: { setupCfg: {
type: 'rect', action: '',
color: node => node.color || '#1976D2', manifest: '',
borderRadius: node => node.borderRadius || 5 loading: false
},
label: {
margin: 8
}
},
edge: {
normal: {
width: 3,
dasharray: edge => edge.animate === false ? 20 : 3,
animate: edge => !(edge.animate === false),
animationSpeed: edge => edge.animationSpeed || 50,
color: edge => edge.color || '#1976D2'
},
type: 'straight',
gap: 7,
margin: 4,
marker: {
source: {
type: 'none'
},
target: {
type: 'none'
}
}
},
path: {
visible: true,
end: 'edgeOfNode',
margin: 4,
path: {
width: 7,
color: p => p.color,
linecap: 'square'
}
}
})
}
}, },
computed: { deliveryNodes: {},
currentSiteId: get('admin/currentSiteId', false) deliveryEdges: {},
deliveryLayouts: {
nodes: {}
}, },
watch: { deliveryPaths: [],
async currentSiteId (newValue) { deliveryConfig: VNetworkGraph.defineConfigs({
await this.load() view: {
this.$nextTick(() => { layoutHandler: new VNetworkGraph.GridLayout({ grid: 15 }),
this.$router.replace(`/_admin/${newValue}/storage/${this.selectedTarget}`) fit: true,
}) mouseWheelZoomEnabled: false,
}, grid: {
displayMode (newValue) { visible: true,
if (newValue === 'delivery') { interval: 2.5,
this.generateGraph() thickIncrements: 0
} }
}, },
selectedTarget (newValue) { node: {
this.target = find(this.targets, ['id', newValue]) || null draggable: false,
selectable: true,
normal: {
type: 'rect',
color: node => node.color || '#1976D2',
borderRadius: node => node.borderRadius || 5
},
label: {
margin: 8
}
}, },
targets (newValue) { edge: {
if (newValue && newValue.length > 0) { normal: {
if (this.desiredTarget) { width: 3,
this.selectedTarget = this.desiredTarget dasharray: edge => edge.animate === false ? 20 : 3,
this.desiredTarget = '' animate: edge => !(edge.animate === false),
} else { animationSpeed: edge => edge.animationSpeed || 50,
this.selectedTarget = find(this.targets, ['module', 'db'])?.id || null color: edge => edge.color || '#1976D2'
if (!this.$route.params.id) { },
this.$router.replace(`/_admin/${this.currentSiteId}/storage/${this.selectedTarget}`) type: 'straight',
} gap: 7,
margin: 4,
marker: {
source: {
type: 'none'
},
target: {
type: 'none'
} }
this.handleSetupCallback()
} }
}, },
$route (to, from) { path: {
if (!to.params.id) { visible: true,
return end: 'edgeOfNode',
} margin: 4,
if (this.targets.length < 1) { path: {
this.desiredTarget = to.params.id width: 7,
} else { color: p => p.color,
this.selectedTarget = to.params.id linecap: 'square'
} }
} }
}, })
mounted () { })
if (!this.selectedTarget && this.$route.params.id) {
if (this.targets.length < 1) { // REFS
this.desiredTarget = this.$route.params.id
} else { const githubSetupForm = ref(null)
this.selectedTarget = this.$route.params.id
// WATCHERS
watch(() => adminStore.currentSiteId, async (newValue) => {
await load()
nextTick(() => {
router.replace(`/_admin/${newValue}/storage/${state.selectedTarget}`)
})
})
watch(() => state.displayMode, (newValue) => {
if (newValue === 'delivery') {
generateGraph()
}
})
watch(() => state.selectedTarget, (newValue) => {
state.target = find(state.targets, ['id', newValue]) || null
})
watch(() => state.targets, (newValue) => {
if (newValue && newValue.length > 0) {
if (state.desiredTarget) {
state.selectedTarget = state.desiredTarget
state.desiredTarget = ''
} else {
state.selectedTarget = find(state.targets, ['module', 'db'])?.id || null
if (!route.params.id) {
router.replace(`/_admin/${adminStore.currentSiteId}/storage/${state.selectedTarget}`)
} }
} }
if (this.currentSiteId) { handleSetupCallback()
this.load() }
} })
this.handleSetupCallback() watch(() => route, (to, from) => {
}, if (!to.params.id) {
methods: { return
async load () { }
this.loading++ if (state.targets.length < 1) {
this.$q.loading.show() state.desiredTarget = to.params.id
const resp = await this.$apollo.query({ } else {
query: gql` state.selectedTarget = to.params.id
query getStorageTargets ( }
$siteId: UUID! })
// METHODS
async function load () {
state.loading++
$q.loading.show()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query getStorageTargets (
$siteId: UUID!
) {
storageTargets (
siteId: $siteId
) {
id
isEnabled
module
title
description
icon
banner
vendor
website
contentTypes
assetDelivery
versioning
sync
status
setup
config
actions
}
}`,
variables: {
siteId: adminStore.currentSiteId
},
fetchPolicy: 'network-only'
})
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 => 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 function save ({ silent }) {
let saveSuccess = false
if (!silent) { $q.loading.show() }
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation (
$siteId: UUID!
$targets: [StorageTargetInput]!
) { ) {
storageTargets ( updateStorageTargets(
siteId: $siteId siteId: $siteId
targets: $targets
) { ) {
id status {
isEnabled succeeded
module message
title
description
icon
banner
vendor
website
contentTypes
assetDelivery
versioning
sync
status
setup
config
actions
}
}`,
variables: {
siteId: this.currentSiteId
},
fetchPolicy: 'network-only'
})
this.targets = cloneDeep(resp?.data?.storageTargets)
this.$q.loading.hide()
this.loading--
},
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'
})
},
async save ({ silent }) {
let saveSuccess = false
if (!silent) { this.$q.loading.show() }
try {
const resp = await this.$apollo.mutate({
mutation: gql`
mutation (
$siteId: UUID!
$targets: [StorageTargetInput]!
) {
updateStorageTargets(
siteId: $siteId
targets: $targets
) {
status {
succeeded
message
}
}
} }
`,
variables: {
siteId: this.currentSiteId,
targets: this.targets.map(tgt => ({
id: tgt.id,
module: tgt.module,
isEnabled: tgt.isEnabled,
contentTypes: tgt.contentTypes.activeTypes,
largeThreshold: tgt.contentTypes.largeThreshold,
assetDeliveryFileStreaming: tgt.assetDelivery.streaming,
assetDeliveryDirectAccess: tgt.assetDelivery.directAccess,
useVersioning: tgt.versioning.enabled,
config: transform(tgt.config, (r, v, k) => { r[k] = v.value }, {})
}))
}
})
if (resp?.data?.updateStorageTargets?.status?.succeeded) {
saveSuccess = true
if (!silent) {
this.$q.notify({
type: 'positive',
message: this.$t('admin.storage.saveSuccess')
})
} }
} else {
throw new Error(resp?.data?.updateStorageTargets?.status?.message || 'Unexpected error')
} }
} catch (err) { `,
this.$q.notify({ variables: {
type: 'negative', siteId: adminStore.currentSiteId,
message: this.$t('admin.storage.saveFailed'), targets: state.targets.map(tgt => ({
caption: err.message id: tgt.id,
}) module: tgt.module,
} isEnabled: tgt.isEnabled,
if (!silent) { this.$q.loading.hide() } contentTypes: tgt.contentTypes.activeTypes,
return saveSuccess largeThreshold: tgt.contentTypes.largeThreshold,
}, assetDeliveryFileStreaming: tgt.assetDelivery.streaming,
getTargetSubtitle (target) { assetDeliveryDirectAccess: tgt.assetDelivery.directAccess,
if (!target.isEnabled) { useVersioning: tgt.versioning.enabled,
return this.$t('admin.storage.inactiveTarget') config: transform(tgt.config, (r, v, k) => { r[k] = v.value }, {})
} }))
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')
} else if (hasPages) {
return this.$t('admin.storage.pagesOnly')
} else if (hasAssets) {
return this.$t('admin.storage.assetsOnly')
} else {
return this.$t('admin.storage.notConfigured')
}
},
getTargetSubtitleColor (target) {
if (this.selectedTarget === target.id) {
return 'text-blue-2'
} else if (target.isEnabled) {
return 'text-positive'
} else {
return 'text-grey-7'
} }
}, })
getDefaultSchedule (val) { if (resp?.data?.updateStorageTargets?.status?.succeeded) {
if (!val) { return 'N/A' } saveSuccess = true
return '' // moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]') if (!silent) {
}, $q.notify({
async executeAction (targetKey, handler) { type: 'positive',
this.$store.commit('loadingStart', 'admin-storage-executeaction') message: t('admin.storage.saveSuccess')
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 } else {
this.runningActionHandler = '' throw new Error(resp?.data?.updateStorageTargets?.status?.message || 'Unexpected error')
this.$store.commit('loadingStop', 'admin-storage-executeaction') }
}, } catch (err) {
async handleSetupCallback () { $q.notify({
if (this.targets.length < 1 || !this.selectedTarget) { return } type: 'negative',
message: t('admin.storage.saveFailed'),
caption: err.message
})
}
if (!silent) { $q.loading.hide() }
return saveSuccess
}
this.$nextTick(() => { function getTargetSubtitle (target) {
if (this.target?.setup?.handler === 'github' && this.$route.query.code) { if (!target.isEnabled) {
this.setupGitHubStep('connect', this.$route.query.code) return t('admin.storage.inactiveTarget')
} }
}) const hasPages = target.contentTypes?.activeTypes?.includes('pages')
}, const hasAssets = target.contentTypes?.activeTypes?.filter(c => c !== 'pages')?.length > 0
async setupDestroy () { if (hasPages && hasAssets) {
this.$q.dialog({ return t('admin.storage.pagesAndAssets')
title: this.$t('admin.storage.destroyConfirm'), } else if (hasPages) {
message: this.$t('admin.storage.destroyConfirmInfo'), return t('admin.storage.pagesOnly')
cancel: true, } else if (hasAssets) {
persistent: true return t('admin.storage.assetsOnly')
}).onOk(async () => { } else {
this.$q.loading.show({ return t('admin.storage.notConfigured')
message: this.$t('admin.storage.destroyingSetup') }
}) }
function getTargetSubtitleColor (target) {
if (state.selectedTarget === target.id) {
return 'text-blue-2'
} else if (target.isEnabled) {
return 'text-positive'
} else {
return 'text-grey-7'
}
}
function getDefaultSchedule (val) {
if (!val) { return 'N/A' }
return '' // moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
}
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')
}
try { async function handleSetupCallback () {
const resp = await this.$apollo.mutate({ if (state.targets.length < 1 || !state.selectedTarget) { return }
mutation: gql`
mutation ( nextTick(() => {
$targetId: UUID! if (state.target?.setup?.handler === 'github' && route.query.code) {
) { setupGitHubStep('connect', route.query.code)
destroyStorageTargetSetup( }
targetId: $targetId })
) { }
status {
succeeded async function setupDestroy () {
message $q.dialog({
} title: t('admin.storage.destroyConfirm'),
} message: t('admin.storage.destroyConfirmInfo'),
cancel: true,
persistent: true
}).onOk(async () => {
$q.loading.show({
message: t('admin.storage.destroyingSetup')
})
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation (
$targetId: UUID!
) {
destroyStorageTargetSetup(
targetId: $targetId
) {
status {
succeeded
message
} }
`,
variables: {
targetId: this.selectedTarget
} }
})
if (resp?.data?.destroyStorageTargetSetup?.status?.succeeded) {
this.target.setup.state = 'notconfigured'
setTimeout(() => {
this.$q.loading.hide()
this.$q.notify({
type: 'positive',
message: this.$t('admin.storage.githubSetupDestroySuccess')
})
}, 2000)
} else {
throw new Error(resp?.data?.destroyStorageTargetSetup?.status?.message || 'Unexpected error')
} }
} catch (err) { `,
this.$q.notify({ variables: {
type: 'negative', targetId: state.selectedTarget
message: this.$t('admin.storage.githubSetupDestroyFailed'),
caption: err.message
})
this.$q.loading.hide()
} }
}) })
}, if (resp?.data?.destroyStorageTargetSetup?.status?.succeeded) {
async setupGitHub () { state.target.setup.state = 'notconfigured'
// -> Format values setTimeout(() => {
this.target.setup.values.publicUrl = this.target.setup.values.publicUrl.toLowerCase() $q.loading.hide()
$q.notify({
// -> Basic input check type: 'positive',
if (this.target.setup.values.accountType === 'org' && this.target.setup.values.org.length < 1) { message: t('admin.storage.githubSetupDestroySuccess')
return this.$q.notify({ })
type: 'negative', }, 2000)
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({
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)
}
// -> 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`
} else { } else {
this.setupCfg.action = 'https://github.com/settings/apps/new' throw new Error(resp?.data?.destroyStorageTargetSetup?.status?.message || 'Unexpected error')
} }
this.setupCfg.manifest = JSON.stringify({ } catch (err) {
name: `Wiki.js - ${this.currentSiteId.slice(-12)}`, $q.notify({
description: 'Connects your Wiki.js to GitHub repositories and synchronize their contents.', type: 'negative',
url: this.target.setup.values.publicUrl, message: t('admin.storage.githubSetupDestroyFailed'),
hook_attributes: { caption: err.message
url: `${this.target.setup.values.publicUrl}/_github/${this.currentSiteId}/events`
},
redirect_url: `${this.target.setup.values.publicUrl}/_admin/${this.currentSiteId}/storage/${this.target.id}`,
callback_urls: [
`${this.target.setup.values.publicUrl}/_admin/${this.currentSiteId}/storage/${this.target.id}`
],
public: false,
default_permissions: {
contents: 'write',
metadata: 'read',
members: 'read'
},
default_events: [
'create',
'delete',
'push'
]
})
this.$q.loading.show({
message: this.$t('admin.storage.githubPreparingManifest')
}) })
if (await this.save({ silent: true })) { $q.loading.hide()
this.$refs.githubSetupForm.submit() }
} else { })
this.setupCfg.loading = false }
this.$q.loading.hide()
} async function setupGitHub () {
// -> Format values
state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.toLowerCase()
// -> Basic input check
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 (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 (state.target.setup.values.publicUrl.endsWith('/')) {
state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.slice(0, -1)
}
// -> Generate manifest
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 {
state.setupCfg.action = 'https://github.com/settings/apps/new'
}
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: state.target.setup.values.publicUrl,
hook_attributes: {
url: `${state.target.setup.values.publicUrl}/_github/${adminStore.currentSiteId}/events`
}, },
async setupGitHubStep (step, code) { redirect_url: `${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`,
this.$q.loading.show({ callback_urls: [
message: this.$t('admin.storage.githubVerifying') `${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`
}) ],
public: false,
default_permissions: {
contents: 'write',
metadata: 'read',
members: 'read'
},
default_events: [
'create',
'delete',
'push'
]
})
$q.loading.show({
message: t('admin.storage.githubPreparingManifest')
})
if (await save({ silent: true })) {
githubSetupForm.value.submit()
} else {
state.setupCfg.loading = false
$q.loading.hide()
}
}
try { async function setupGitHubStep (step, code) {
const resp = await this.$apollo.mutate({ $q.loading.show({
mutation: gql` message: t('admin.storage.githubVerifying')
mutation ( })
$targetId: UUID!
$state: JSON! try {
) { const resp = await APOLLO_CLIENT.mutate({
setupStorageTarget( mutation: gql`
targetId: $targetId mutation (
state: $state $targetId: UUID!
) { $state: JSON!
status { ) {
succeeded setupStorageTarget(
message targetId: $targetId
} state: $state
state ) {
} status {
} succeeded
`, message
variables: {
targetId: this.selectedTarget,
state: {
step,
...code && { code }
}
}
})
if (resp?.data?.setupStorageTarget?.status?.succeeded) {
switch (resp.data.setupStorageTarget.state?.nextStep) {
case 'installApp': {
this.$router.replace({ query: null })
this.$q.loading.hide()
this.$q.dialog({
component: GithubSetupInstallDialog,
persistent: true
}).onOk(() => {
this.$q.loading.show({
message: this.$t('admin.storage.githubRedirecting')
})
window.location.assign(resp.data.setupStorageTarget.state?.url)
}).onCancel(() => {
throw new Error('Setup was aborted prematurely.')
})
break
}
case 'completed': {
this.target.isEnabled = true
this.target.setup.state = 'configured'
setTimeout(() => {
this.$q.loading.hide()
this.$q.notify({
type: 'positive',
message: this.$t('admin.storage.githubSetupSuccess')
})
}, 2000)
break
}
default: {
throw new Error('Unknown Setup Step')
} }
state
} }
} else {
throw new Error(resp?.data?.setupStorageTarget?.status?.message || 'Unexpected error')
} }
} catch (err) { `,
this.$q.loading.hide() variables: {
this.$q.notify({ targetId: this.selectedTarget,
type: 'negative', state: {
message: this.$t('admin.storage.githubSetupFailed'), step,
caption: err.message ...code && { code }
}) }
}
},
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;' }
]
// -> 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;' },
pages_wiki: { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
}
this.deliveryEdges = {
user_pages: { source: 'user', target: 'pages' },
pages_in: { source: 'pages', target: 'pages_wiki' },
pages_out: { source: 'pages_wiki', target: 'pages' }
} }
this.deliveryLayouts.nodes = { })
user: { x: -30, y: 30 }, if (resp?.data?.setupStorageTarget?.status?.succeeded) {
pages: { x: 0, y: 0 }, switch (resp.data.setupStorageTarget.state?.nextStep) {
pages_wiki: { x: 60, y: 0 } case 'installApp': {
router.replace({ query: null })
$q.loading.hide()
$q.dialog({
component: GithubSetupInstallDialog,
persistent: true
}).onOk(() => {
$q.loading.show({
message: t('admin.storage.githubRedirecting')
})
window.location.assign(resp.data.setupStorageTarget.state?.url)
}).onCancel(() => {
throw new Error('Setup was aborted prematurely.')
})
break
}
case 'completed': {
this.target.isEnabled = true
this.target.setup.state = 'configured'
setTimeout(() => {
$q.loading.hide()
$q.notify({
type: 'positive',
message: t('admin.storage.githubSetupSuccess')
})
}, 2000)
break
}
default: {
throw new Error('Unknown Setup Step')
}
} }
this.deliveryPaths = [] } else {
throw new Error(resp?.data?.setupStorageTarget?.status?.message || 'Unexpected error')
}
} catch (err) {
$q.loading.hide()
$q.notify({
type: 'negative',
message: t('admin.storage.githubSetupFailed'),
caption: err.message
})
}
}
// -> Create Asset Nodes function generateGraph () {
const types = [
{ 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;' }
]
for (const [i, t] of types.entries()) { // -> Create PagesNodes
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 }
// -> Find target with direct access state.deliveryNodes = {
const dt = find(this.targets, tgt => { user: { name: t('admin.storage.deliveryPathsUser'), borderRadius: 16, icon: '/_assets/icons/fluent-account.svg' },
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(t.key) && tgt.isEnabled && tgt.assetDelivery.isDirectAccessSupported && tgt.assetDelivery.directAccess pages: { name: t('admin.storage.contentTypePages'), color: '#3f51b5', icon: 'las', iconText: '&#xf15c;' },
}) pages_wiki: { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
}
state.deliveryEdges = {
user_pages: { source: 'user', target: 'pages' },
pages_in: { source: 'pages', target: 'pages_wiki' },
pages_out: { source: 'pages_wiki', target: 'pages' }
}
state.deliveryLayouts.nodes = {
user: { x: -30, y: 30 },
pages: { x: 0, y: 0 },
pages_wiki: { x: 60, y: 0 }
}
state.deliveryPaths = []
if (dt) { // -> Create Asset Nodes
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 }
continue
}
// -> Find target with streaming 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 }
const st = find(this.targets, tgt => { // -> Find target with direct access
return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(t.key) && tgt.isEnabled && tgt.assetDelivery.isStreamingSupported && tgt.assetDelivery.streaming 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 (st) { if (dt) {
this.deliveryNodes[`${t.key}_${st.module}`] = { name: st.title, icon: st.icon } state.deliveryNodes[`${tp.key}_${dt.module}`] = { name: dt.title, icon: dt.icon }
this.deliveryNodes[`${t.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' } state.deliveryNodes[`${tp.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 } state.deliveryLayouts.nodes[`${tp.key}_${dt.module}`] = { x: 60, y: (i + 1) * 15 }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 60, y: (i + 1) * 15 } state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 120, y: (i + 1) * 15 }
this.deliveryEdges[`${t.key}_wiki_in`] = { source: t.key, target: `${t.key}_wiki` } state.deliveryEdges[`${tp.key}_${dt.module}_in`] = { source: tp.key, target: `${tp.key}_${dt.module}` }
this.deliveryEdges[`${t.key}_wiki_out`] = { source: `${t.key}_wiki`, target: t.key } state.deliveryEdges[`${tp.key}_${dt.module}_out`] = { source: `${tp.key}_${dt.module}`, target: tp.key }
this.deliveryEdges[`${t.key}_${st.module}_out`] = { source: `${t.key}_${st.module}`, target: `${t.key}_wiki` } state.deliveryEdges[`${tp.key}_${dt.module}_wiki`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${dt.module}`, color: '#02c39a', animationSpeed: 25 }
this.deliveryEdges[`${t.key}_${st.module}_in`] = { source: `${t.key}_wiki`, target: `${t.key}_${st.module}` } continue
this.deliveryEdges[`${t.key}_${st.module}_wiki`] = { source: `${t.key}_wiki`, target: `${t.key}_${st.module}`, color: '#02c39a', animationSpeed: 25 } }
continue
}
// -> Check DB fallback // -> Find target with streaming
const dbt = find(this.targets, ['module', 'db']) const st = find(state.targets, tgt => {
if (dbt.contentTypes.activeTypes.includes(t.key)) { return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(tp.key) && tgt.isEnabled && tgt.assetDelivery.isStreamingSupported && tgt.assetDelivery.streaming
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` } if (st) {
this.deliveryEdges[`${t.key}_db_out`] = { source: `${t.key}_wiki`, target: t.key } state.deliveryNodes[`${tp.key}_${st.module}`] = { name: st.title, icon: st.icon }
} else { state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
this.deliveryNodes[`${t.key}_wiki`] = { name: this.$t('admin.storage.missingOrigin'), color: '#f03a47', icon: 'las', iconText: '&#xf071;' } state.deliveryLayouts.nodes[`${tp.key}_${st.module}`] = { x: 120, y: (i + 1) * 15 }
this.deliveryLayouts.nodes[`${t.key}_wiki`] = { x: 60, y: (i + 1) * 15 } state.deliveryLayouts.nodes[`${tp.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 } state.deliveryEdges[`${tp.key}_wiki_in`] = { source: tp.key, target: `${tp.key}_wiki` }
this.deliveryPaths.push({ edges: [`${t.key}_db_in`], color: '#f03a4755' }) 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(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 {
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> </script>
<style lang='scss' scoped> <style lang='scss' scoped>
......
...@@ -16,7 +16,7 @@ q-page.admin-system ...@@ -16,7 +16,7 @@ q-page.admin-system
type='a' type='a'
) )
q-btn.q-mr-sm.acrylic-btn( q-btn.q-mr-sm.acrylic-btn(
icon='fa-solid fa-rotate' icon='las la-redo-alt'
flat flat
color='secondary' color='secondary'
:loading='state.loading > 0' :loading='state.loading > 0'
...@@ -234,26 +234,10 @@ import { useMeta, useQuasar } from 'quasar' ...@@ -234,26 +234,10 @@ import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import ClipboardJS from 'clipboard' 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 // QUASAR
const $q = useQuasar() const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
const dataStore = useDataStore()
// ROUTER
const router = useRouter()
const route = useRoute()
// I18N // I18N
const { t } = useI18n() const { t } = useI18n()
......
...@@ -30,20 +30,20 @@ const routes = [ ...@@ -30,20 +30,20 @@ const routes = [
{ path: '', redirect: '/_admin/dashboard' }, { path: '', redirect: '/_admin/dashboard' },
{ path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') }, { path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') },
{ path: 'sites', component: () => import('../pages/AdminSites.vue') }, { path: 'sites', component: () => import('../pages/AdminSites.vue') },
// // -> Site // -> Site
{ path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') }, { path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') },
{ path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') }, { path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') },
// { path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') }, { path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
// { path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') }, { path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
// { path: ':siteid/navigation', component: () => import('../pages/AdminNavigation.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/rendering', component: () => import('../pages/AdminRendering.vue') },
// { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') }, // { path: ':siteid/theme', component: () => import('../pages/AdminTheme.vue') },
// // -> Users // -> Users
// { path: 'auth', component: () => import('../pages/AdminAuth.vue') }, // { path: 'auth', component: () => import('../pages/AdminAuth.vue') },
// { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') }, // { path: 'groups/:id?/:section?', component: () => import('../pages/AdminGroups.vue') },
// { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') }, // { path: 'users/:id?/:section?', component: () => import('../pages/AdminUsers.vue') },
// // -> System // -> System
// { path: 'api', component: () => import('../pages/AdminApi.vue') }, // { path: 'api', component: () => import('../pages/AdminApi.vue') },
// { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') }, // { path: 'extensions', component: () => import('../pages/AdminExtensions.vue') },
// { path: 'mail', component: () => import('../pages/AdminMail.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