2025-02-20 10:54:43 +01:00
|
|
|
<script setup>
|
|
|
|
|
|
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
|
const i18n = useI18n();
|
|
|
|
|
const t = i18n.t;
|
|
|
|
|
|
2025-02-22 16:29:28 +01:00
|
|
|
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
|
2025-02-22 12:12:35 +01:00
|
|
|
import { Button, ButtonGroup } from 'pankow';
|
2025-03-02 14:58:08 +01:00
|
|
|
import Access from '../components/app/Access.vue';
|
2025-03-01 19:22:11 +01:00
|
|
|
import Backups from '../components/app/Backups.vue';
|
2025-02-22 11:13:15 +01:00
|
|
|
import Cron from '../components/app/Cron.vue';
|
2025-03-01 19:22:11 +01:00
|
|
|
import Display from '../components/app/Display.vue';
|
|
|
|
|
import Email from '../components/app/Email.vue';
|
|
|
|
|
import Eventlog from '../components/app/Eventlog.vue';
|
2025-03-07 11:54:43 +01:00
|
|
|
import Graphs from '../components/app/Graphs.vue';
|
2025-03-01 19:22:11 +01:00
|
|
|
import Info from '../components/app/Info.vue';
|
2025-03-03 12:45:40 +01:00
|
|
|
import Location from '../components/app/Location.vue';
|
2025-03-07 12:08:19 +01:00
|
|
|
import Proxy from '../components/app/Proxy.vue';
|
2025-02-25 19:04:58 +01:00
|
|
|
import Resources from '../components/app/Resources.vue';
|
2025-02-22 18:31:21 +01:00
|
|
|
import Repair from '../components/app/Repair.vue';
|
2025-03-01 19:22:11 +01:00
|
|
|
import Security from '../components/app/Security.vue';
|
2025-03-06 23:52:41 +01:00
|
|
|
import Services from '../components/app/Services.vue';
|
2025-03-06 16:40:52 +01:00
|
|
|
import Storage from '../components/app/Storage.vue';
|
2025-02-21 11:51:52 +01:00
|
|
|
import Uninstall from '../components/app/Uninstall.vue';
|
2025-03-01 19:22:11 +01:00
|
|
|
import Updates from '../components/app/Updates.vue';
|
2025-02-20 10:54:43 +01:00
|
|
|
import AppsModel from '../models/AppsModel.js';
|
2025-03-03 12:45:40 +01:00
|
|
|
import TasksModel from '../models/TasksModel.js';
|
2025-03-03 11:22:56 +01:00
|
|
|
import { API_ORIGIN, APP_TYPES, ISTATES, RSTATES, HSTATES } from '../constants.js';
|
2025-02-20 10:54:43 +01:00
|
|
|
|
|
|
|
|
const appsModel = AppsModel.create();
|
2025-03-03 12:45:40 +01:00
|
|
|
const tasksModel = TasksModel.create();
|
2025-02-22 18:38:00 +01:00
|
|
|
const installationStateLabel = AppsModel.installationStateLabel;
|
2025-02-20 16:12:36 +01:00
|
|
|
|
2025-02-25 19:04:58 +01:00
|
|
|
const busy = ref(true);
|
2025-02-20 10:54:43 +01:00
|
|
|
const id = ref('');
|
|
|
|
|
const app = ref({});
|
2025-02-22 12:12:35 +01:00
|
|
|
const view = ref('');
|
2025-02-20 10:54:43 +01:00
|
|
|
const link = ref('');
|
|
|
|
|
const infoMenu = ref([]);
|
|
|
|
|
const hasLocalStorage = ref(false);
|
2025-02-25 15:21:32 +01:00
|
|
|
const hasOptionalServices = ref(false);
|
|
|
|
|
const hasEmail = ref(false);
|
2025-03-03 12:45:40 +01:00
|
|
|
const busyStopTask = ref(false);
|
2025-02-20 10:54:43 +01:00
|
|
|
|
2025-02-21 14:07:07 +01:00
|
|
|
const isAppStopped = computed(() => {
|
|
|
|
|
return appsModel.isStopped(app.value);
|
|
|
|
|
});
|
2025-02-21 12:20:23 +01:00
|
|
|
|
2025-02-22 12:12:35 +01:00
|
|
|
function onSetView(newView) {
|
|
|
|
|
view.value = newView;
|
|
|
|
|
window.location.hash = `/app/${id.value}/${newView}`;
|
2025-02-21 11:51:52 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-21 14:07:07 +01:00
|
|
|
async function onToggleRunState() {
|
|
|
|
|
if (isAppStopped.value) {
|
|
|
|
|
const [error] = await appsModel.start(app.value.id);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
} else {
|
|
|
|
|
const [error] = await appsModel.stop(app.value.id);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let refreshTimer = null;
|
2025-02-20 10:54:43 +01:00
|
|
|
async function refresh() {
|
|
|
|
|
const [error, result] = await appsModel.get(id.value);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
app.value = result;
|
|
|
|
|
|
2025-02-26 20:30:33 +01:00
|
|
|
link.value = (result.installationState !== ISTATES.INSTALLED || result.health !== HSTATES.HEALTHY || result.runState === RSTATES.STOPPED) ? '' : `https://${result.fqdn}`;
|
2025-02-20 10:54:43 +01:00
|
|
|
hasLocalStorage.value = result.manifest && result.manifest.addons && result.manifest.addons.localstorage;
|
2025-02-25 15:21:32 +01:00
|
|
|
hasOptionalServices.value = result.manifest && result.manifest.addons && (result.manifest.addons.turn?.optional || result.manifest.addons.redis?.optional);
|
|
|
|
|
hasEmail.value = result.manifest && result.manifest.addons && (result.manifest.addons.sendmail || result.manifest.addons.recvmail);
|
2025-02-20 10:54:43 +01:00
|
|
|
|
2025-02-21 14:07:07 +01:00
|
|
|
// TODO info menu will likely not change during polling
|
2025-02-21 12:20:23 +01:00
|
|
|
infoMenu.value = [];
|
2025-02-20 10:54:43 +01:00
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.docsAction'),
|
|
|
|
|
disabled: !result.manifest?.documentationUrl,
|
|
|
|
|
// TODO support real href links
|
|
|
|
|
action: () => { window.location.href = result.manifest.documentationUrl; }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.manifest?.postInstallMessage) {
|
|
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.firstTimeSetupAction'),
|
|
|
|
|
// TODO action
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result.manifest?.configurePath) {
|
|
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.adminPageAction'),
|
|
|
|
|
// TODO support real href links
|
|
|
|
|
action: () => { window.location.href = link.value + result.manifest.configurePath; }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (result.manifest?.addons?.localstorage?.ftp) {
|
|
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.sftpInfoAction'),
|
|
|
|
|
// TODO action
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
infoMenu.value.push({ separator: true });
|
|
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.forumUrlAction'),
|
|
|
|
|
disabled: !result.manifest?.forumUrl,
|
|
|
|
|
// TODO support real href links
|
|
|
|
|
action: () => { window.location.href = result.manifest.forumUrl; }
|
|
|
|
|
});
|
|
|
|
|
infoMenu.value.push({ separator: true });
|
|
|
|
|
infoMenu.value.push({
|
|
|
|
|
label: t('app.projectWebsiteAction'),
|
|
|
|
|
disabled: !result.manifest?.website,
|
|
|
|
|
// TODO support real href links
|
|
|
|
|
action: () => { window.location.href = result.manifest.website; }
|
|
|
|
|
});
|
2025-02-21 12:20:23 +01:00
|
|
|
|
|
|
|
|
refreshTimer = setTimeout(refresh, 2000);
|
2025-02-20 10:54:43 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-03 12:45:40 +01:00
|
|
|
async function onStopAppTask() {
|
|
|
|
|
if (!app.value.taskId) return;
|
|
|
|
|
|
|
|
|
|
busyStopTask.value = true;
|
|
|
|
|
|
|
|
|
|
const [error] = await tasksModel.stop(app.value.taskId);
|
|
|
|
|
if (error) console.error(error);
|
|
|
|
|
|
|
|
|
|
busyStopTask.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-20 10:54:43 +01:00
|
|
|
onMounted(async () => {
|
|
|
|
|
const tmp = window.location.hash.slice('#/app/'.length);
|
|
|
|
|
if (!tmp) return;
|
|
|
|
|
|
|
|
|
|
const parts = tmp.split('/');
|
|
|
|
|
if (parts.length !== 2) return;
|
|
|
|
|
|
|
|
|
|
id.value = parts[0];
|
|
|
|
|
|
|
|
|
|
await refresh();
|
2025-02-20 16:12:36 +01:00
|
|
|
|
2025-02-22 12:12:35 +01:00
|
|
|
onSetView(parts[1] || 'info');
|
2025-02-25 19:04:58 +01:00
|
|
|
|
|
|
|
|
busy.value = false;
|
2025-02-20 10:54:43 +01:00
|
|
|
});
|
|
|
|
|
|
2025-02-22 11:13:15 +01:00
|
|
|
onBeforeUnmount(() => {
|
2025-02-21 12:20:23 +01:00
|
|
|
if (refreshTimer) clearTimeout(refreshTimer);
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-20 10:54:43 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="configure-outer">
|
2025-02-25 19:04:58 +01:00
|
|
|
<div class="configure-inner" v-if="!busy">
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="titlebar">
|
2025-02-25 15:21:32 +01:00
|
|
|
<div style="display: flex; flex-grow: 1;">
|
2025-03-01 11:44:38 +01:00
|
|
|
<img :src="app.iconUrl" v-fallback-image="API_ORIGIN + '/img/appicon_fallback.png'" style="height: 64px; width: 64px; margin-right: 10px;"/>
|
2025-02-22 18:38:00 +01:00
|
|
|
<h2>
|
2025-02-25 15:21:32 +01:00
|
|
|
<a class="applink" :href="link || null" target="_blank">{{ app.label || app.fqdn }}</a>
|
|
|
|
|
<div class="statelabel">
|
2025-02-22 18:38:00 +01:00
|
|
|
{{ installationStateLabel(app) }} {{ app.message ? ' - ' + app.message : '' }}
|
|
|
|
|
</div>
|
|
|
|
|
</h2>
|
2025-02-25 15:21:32 +01:00
|
|
|
</div>
|
2025-02-22 12:12:35 +01:00
|
|
|
|
|
|
|
|
<div style="display: flex; gap: 10px; align-items: center;">
|
2025-03-03 21:24:27 +01:00
|
|
|
<Button v-if="app.taskId" danger tool plain icon="fa-solid fa-xmark" v-tooltip="'Cancel Task'" :loading="busyStopTask" :disabled="busyStopTask" @click="onStopAppTask()"/>
|
2025-02-22 16:29:28 +01:00
|
|
|
<Button secondary tool
|
2025-02-22 12:12:35 +01:00
|
|
|
@click="onToggleRunState()"
|
|
|
|
|
:disabled="app.taskId || app.error || app.installationState === 'pending_start' || app.installationState === 'pending_stop'"
|
|
|
|
|
v-tooltip="$t(isAppStopped ? 'app.uninstall.startStop.startAction' : 'app.uninstall.startStop.stopAction')"
|
|
|
|
|
:loading="app.installationState === 'pending_start' || app.installationState === 'pending_stop'"
|
|
|
|
|
:icon="isAppStopped ? 'fa-solid fa-play' : 'fa-solid fa-power-off'"
|
|
|
|
|
/>
|
|
|
|
|
<ButtonGroup>
|
2025-02-22 16:29:28 +01:00
|
|
|
<Button secondary tool :href="`/logs.html?appId=${app.id}`" target="_blank" v-tooltip="$t('app.logsActionTooltip')" icon="fa-solid fa-align-left" />
|
|
|
|
|
<Button secondary tool v-if="app.type !== APP_TYPES.PROXIED" :href="`/terminal.html?id=${app.id}`" target="_blank" v-tooltip="$t('app.terminalActionTooltip')" icon="fa fa-terminal" />
|
|
|
|
|
<Button secondary tool v-if="hasLocalStorage" :href="`/filemanager.html#/home/app/${app.id}`" target="_blank" v-tooltip="$t('app.filemanagerActionTooltip')" icon="fas fa-folder" />
|
2025-02-22 12:12:35 +01:00
|
|
|
</ButtonGroup>
|
|
|
|
|
|
2025-02-22 16:29:28 +01:00
|
|
|
<Button secondary tool icon="fa-solid fa-book" v-tooltip="$t('app.docsActionTooltip')" :menu="infoMenu" />
|
2025-02-22 12:12:35 +01:00
|
|
|
</div>
|
2025-02-20 10:54:43 +01:00
|
|
|
</div>
|
|
|
|
|
|
2025-03-03 12:45:40 +01:00
|
|
|
<div class="apptask-progress">
|
|
|
|
|
<div class="apptask-progress-filled" :style="{ width: app.progress+'%' }"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="configure-body">
|
|
|
|
|
<div class="configure-menu">
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('info')" :active="view === 'info' ? true : null">{{ $t('app.infoTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('display')" :active="view === 'display' ? true : null">{{ $t('app.displayTabTitle') }}</div>
|
2025-02-25 15:21:32 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('location')" :active="view === 'location' ? true : null" v-if="app.accessLevel === 'admin'">{{ $t('app.locationTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('proxy')" :active="view === 'proxy' ? true : null" v-if="app.type === APP_TYPES.PROXIED">Proxy</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('access')" :active="view === 'access' ? true : null" v-if="app.accessLevel === 'admin'">{{ $t('app.accessControlTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('resources')" :active="view === 'resources' ? true : null" v-if="app.type !== APP_TYPES.PROXIED">{{ $t('app.resourcesTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('services')" :active="view === 'services' ? true : null" v-if="app.type !== APP_TYPES.PROXIED && hasOptionalServices">{{ $t('app.servicesTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('storage')" :active="view === 'storage' ? true : null" v-if="app.accessLevel === 'admin' && app.type !== APP_TYPES.PROXIED">{{ $t('app.storageTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('graphs')" :active="view === 'graphs' ? true : null" v-if="app.type !== APP_TYPES.PROXIED">{{ $t('app.graphsTabTitle') }}</div>
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('security')" :active="view === 'security' ? true : null">{{ $t('app.securityTabTitle') }}</div>
|
2025-02-25 15:21:32 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('email')" :active="view === 'email' ? true : null" v-if="app.accessLevel === 'admin' && hasEmail && app.type !== APP_TYPES.PROXIED">{{ $t('app.emailTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('cron')" :active="view === 'cron' ? true : null" v-if="app.type !== APP_TYPES.PROXIED">{{ $t('app.cronTabTitle') }}</div>
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('updates')" :active="view === 'updates' ? true : null">{{ $t('app.updatesTabTitle') }}</div>
|
2025-02-25 15:21:32 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('backups')" :active="view === 'backups' ? true : null" v-if="app.type !== APP_TYPES.PROXIED">{{ $t('app.backupsTabTitle') }}</div>
|
2025-02-22 12:12:35 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('repair')" :active="view === 'repair' ? true : null">{{ $t('app.repairTabTitle') }}</div>
|
|
|
|
|
<div class="configure-menu-item" @click="onSetView('eventlog')" :active="view === 'eventlog' ? true : null">{{ $t('app.eventlogTabTitle') }}</div>
|
2025-02-25 15:21:32 +01:00
|
|
|
<div class="configure-menu-item" @click="onSetView('uninstall')" :active="view === 'uninstall' ? true : null" v-if="app.accessLevel === 'admin'">{{ $t('app.uninstallTabTitle') }}</div>
|
2025-02-22 12:12:35 +01:00
|
|
|
</div>
|
|
|
|
|
<div class="configure-content">
|
2025-03-06 16:54:30 +01:00
|
|
|
<Transition name="slide-fade" mode="out-in">
|
|
|
|
|
<Info v-if="view === 'info'" :app="app"/>
|
|
|
|
|
<Display v-else-if="view === 'display'" :app="app"/>
|
|
|
|
|
<Location v-else-if="view === 'location'" :app="app"/>
|
2025-03-07 12:08:19 +01:00
|
|
|
<Proxy v-else-if="view === 'proxy'" :app="app"/>
|
2025-03-06 16:54:30 +01:00
|
|
|
<Access v-else-if="view === 'access'" :app="app"/>
|
|
|
|
|
<Resources v-else-if="view === 'resources'" :app="app"/>
|
2025-03-06 23:52:41 +01:00
|
|
|
<Services v-else-if="view === 'services'" :app="app"/>
|
2025-03-06 16:54:30 +01:00
|
|
|
<Storage v-else-if="view === 'storage'" :app="app"/>
|
2025-03-07 11:54:43 +01:00
|
|
|
<Graphs v-else-if="view === 'graphs'" :app="app"/>
|
2025-03-06 16:54:30 +01:00
|
|
|
<Security v-else-if="view === 'security'" :app="app"/>
|
|
|
|
|
<Email v-else-if="view === 'email'" :app="app"/>
|
|
|
|
|
<Cron v-else-if="view === 'cron'" :app="app"/>
|
|
|
|
|
<Updates v-else-if="view === 'updates'" :app="app"/>
|
|
|
|
|
<Backups v-else-if="view === 'backups'" :app="app"/>
|
|
|
|
|
<Repair v-else-if="view === 'repair'" :app="app"/>
|
|
|
|
|
<Eventlog v-else-if="view === 'eventlog'" :app="app"/>
|
|
|
|
|
<Uninstall v-else-if="view === 'uninstall'" :app="app"/>
|
|
|
|
|
</Transition>
|
2025-02-22 12:12:35 +01:00
|
|
|
</div>
|
2025-02-20 10:54:43 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
2025-02-21 23:20:02 +01:00
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
2025-03-06 16:54:30 +01:00
|
|
|
.slide-fade-enter-active {
|
|
|
|
|
transition: all 0.2s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slide-fade-leave-active {
|
|
|
|
|
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.slide-fade-enter-from,
|
|
|
|
|
.slide-fade-leave-to {
|
|
|
|
|
transform: translateX(20px);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 16:29:28 +01:00
|
|
|
.applink {
|
|
|
|
|
display: flex;
|
2025-02-22 18:38:00 +01:00
|
|
|
align-items: center;
|
2025-02-22 16:29:28 +01:00
|
|
|
color: var(--pankow-text-color);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 18:38:00 +01:00
|
|
|
.titlebar h2 {
|
2025-03-03 12:45:40 +01:00
|
|
|
flex-grow: 1;
|
2025-02-22 18:38:00 +01:00
|
|
|
padding: 0;
|
|
|
|
|
margin: 0;
|
2025-02-25 15:21:32 +01:00
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
justify-content: center;
|
2025-02-22 18:38:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.statelabel {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 16:29:28 +01:00
|
|
|
.applink:not([href]) {
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 12:12:35 +01:00
|
|
|
.configure-outer {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.configure-inner {
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 1200px;
|
|
|
|
|
margin: auto;
|
2025-02-22 16:29:28 +01:00
|
|
|
padding-right: 15px;
|
2025-02-22 12:12:35 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-21 23:20:02 +01:00
|
|
|
.titlebar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-03 12:45:40 +01:00
|
|
|
.apptask-progress {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 5px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
color: var(--pankow-text-color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.apptask-progress-filled {
|
|
|
|
|
background-color: var(--pankow-color-primary);
|
|
|
|
|
position: relative;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
height: 100%;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
|
|
|
|
|
background-size: 40px 40px;
|
|
|
|
|
animation: apptask-progress-bar-stripes 1s linear infinite;
|
|
|
|
|
transition: width 300ms;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes apptask-progress-bar-stripes {
|
|
|
|
|
from {
|
|
|
|
|
background-position: 40px 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
background-position: 0 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-22 12:12:35 +01:00
|
|
|
.configure-body {
|
2025-03-03 12:45:40 +01:00
|
|
|
margin-top: 20px;
|
2025-03-18 19:04:47 +01:00
|
|
|
margin-bottom: 10px;
|
2025-02-22 12:12:35 +01:00
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.configure-menu-item {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
cursor: pointer;
|
2025-02-22 16:29:28 +01:00
|
|
|
padding: 4px 60px 4px 4px;
|
2025-02-22 12:12:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.configure-menu-item[active] {
|
|
|
|
|
color: var(--pankow-color-primary-active);
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.configure-menu-item:hover {
|
|
|
|
|
color: var(--pankow-color-primary-hover);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.configure-content {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-21 23:20:02 +01:00
|
|
|
</style>
|