Files
cloudron-box/dashboard/src/Index.vue
T

316 lines
13 KiB
Vue
Raw Normal View History

2025-02-03 19:14:01 +01:00
<script setup>
2024-11-01 14:16:09 +01:00
import { onMounted, ref, useTemplateRef, provide } from 'vue';
import { Notification, fetcher } from '@cloudron/pankow';
import { setLanguage } from './i18n.js';
2025-03-17 16:53:53 +01:00
import { API_ORIGIN, TOKEN_TYPES } from './constants.js';
2025-04-02 15:18:40 +02:00
import { redirectIfNeeded } from './utils.js';
2025-03-16 11:12:49 +01:00
import ProfileModel from './models/ProfileModel.js';
2025-04-02 15:18:40 +02:00
import ProvisionModel from './models/ProvisionModel.js';
2025-03-16 11:12:49 +01:00
import DashboardModel from './models/DashboardModel.js';
2025-04-07 15:48:43 +02:00
import BrandingModel from './models/BrandingModel.js';
2025-03-16 11:12:49 +01:00
import Headerbar from './components/Headerbar.vue';
import SubscriptionRequiredDialog from './components/SubscriptionRequiredDialog.vue';
2025-12-01 17:55:52 +01:00
import RequestErrorDialog from './components/RequestErrorDialog.vue';
import OfflineOverlay from './components/OfflineOverlay.vue';
import SideBar from './components/SideBar.vue';
2025-01-21 17:08:09 +01:00
import AppsView from './views/AppsView.vue';
2025-02-20 10:54:43 +01:00
import AppConfigureView from './views/AppConfigureView.vue';
import AppearanceView from './views/AppearanceView.vue';
2025-01-21 17:08:09 +01:00
import AppstoreView from './views/AppstoreView.vue';
import BackupSitesView from './views/BackupSitesView.vue';
2025-10-05 10:45:38 +02:00
import AppArchiveView from './views/AppArchiveView.vue';
import CloudronAccountView from './views/CloudronAccountView.vue';
2025-01-27 22:20:26 +01:00
import DomainsView from './views/DomainsView.vue';
import EmailDomainView from './views/EmailDomainView.vue';
import EmailDomainsView from './views/EmailDomainsView.vue';
2025-04-16 17:59:03 +02:00
import EmailMailboxesView from './views/EmailMailboxesView.vue';
import EmailMailinglistsView from './views/EmailMailinglistsView.vue';
import EmailSettingsView from './views/EmailSettingsView.vue';
import EmailEventlogView from './views/EmailEventlogView.vue';
2025-01-25 17:09:53 +01:00
import EventlogView from './views/EventlogView.vue';
2025-01-22 14:46:31 +01:00
import NetworkView from './views/NetworkView.vue';
2025-01-21 17:08:09 +01:00
import ProfileView from './views/ProfileView.vue';
import ServicesView from './views/ServicesView.vue';
2025-10-05 10:45:38 +02:00
import SystemSettingsView from './views/SystemSettingsView.vue';
2025-07-03 12:15:06 +02:00
import SystemUpdateView from './views/SystemUpdateView.vue';
2025-10-05 10:45:38 +02:00
import DockerView from './views/DockerView.vue';
import ServerView from './views/ServerView.vue';
2025-04-21 12:48:22 +02:00
import UserDirectorySettingsView from './views/UserDirectorySettingsView.vue';
import LdapView from './views/LdapView.vue';
2025-10-05 10:45:38 +02:00
import OpenIdView from './views/OpenIdView.vue';
2025-02-11 18:50:10 +01:00
import UsersView from './views/UsersView.vue';
2025-09-16 12:53:43 +02:00
import GroupsView from './views/GroupsView.vue';
2025-01-21 17:08:09 +01:00
import VolumesView from './views/VolumesView.vue';
2024-11-01 14:16:09 +01:00
2025-10-04 23:07:00 +02:00
const VIEWS = Object.freeze({
APP: '#/app', // this is a prefix
APPEARANCE: '#/appearance',
APPS: '#/apps',
APPSTORE: '#/appstore', // this is a prefix
BACKUP_SITES: '#/backup-sites',
2025-10-04 23:11:30 +02:00
APP_ARCHIVE: '#/app-archive',
CLOUDRON_ACCOUNT: '#/cloudron-account',
DOMAINS: '#/domains',
EMAIL_DOMAIN: '#/email-domain',
EMAIL_DOMAINS: '#/email-domains',
2025-10-04 23:11:30 +02:00
MAILBOXES: '#/mailboxes',
MAILINGLISTS: '#/mailinglists',
EMAIL_SETTINGS: '#/email-settings',
EMAIL_EVENTLOG: '#/email-eventlog',
2025-10-04 23:18:59 +02:00
SERVER: '#/server',
NETWORK: '#/network',
PROFILE: '#/profile',
SERVICES: '#/services',
2025-10-04 23:32:59 +02:00
SYSTEM_SETTINGS: '#/system-settings',
2025-10-04 23:11:30 +02:00
DOCKER: '#/docker',
SYSTEM_EVENTLOG: '#/system-eventlog',
SYSTEM_UPDATE: '#/system-update',
USER_DIRECTORY_SETTINGS: '#/user-directory-settings',
2025-10-05 11:34:48 +02:00
LDAP: '#/ldap',
2025-10-05 10:39:40 +02:00
OPENID: '#/openid',
USERS: '#/users',
2025-10-04 23:11:30 +02:00
GROUPS: '#/groups',
VOLUMES: '#/volumes',
2025-10-04 23:07:00 +02:00
});
2024-11-01 14:16:09 +01:00
const offlineOverlay = useTemplateRef('offlineOverlay');
fetcher.globalOptions.errorHook = (error) => {
// network error, request killed by browser
if (error instanceof TypeError) {
return offlineOverlay.value.open();
}
2025-03-25 18:05:29 +01:00
// re-login will make the code get a new token
if (error.status === 401) return profileModel.logout();
if (error.status === 500 || error.status === 501) {
// actual internal server error, most likely a bug or timeout log to console only to not alert the user
console.error(error);
console.log('------\nCloudron Internal Error\n\nIf you see this, please send a mail with above log to support@cloudron.io\n------\n');
}
2025-03-22 20:53:40 +01:00
if (error.status >= 502) {
// This means the box service is not reachable. We just show offline banner for now
ready.value = false;
return offlineOverlay.value.open();
}
};
2025-03-16 11:12:49 +01:00
const dashboardModel = DashboardModel.create();
const profileModel = ProfileModel.create();
2025-04-02 15:18:40 +02:00
const provisionModel = ProvisionModel.create();
2025-03-16 11:12:49 +01:00
const subscriptionRequiredDialog = useTemplateRef('subscriptionRequiredDialog');
const ready = ref(false);
2025-02-03 19:14:01 +01:00
const view = ref('');
2025-03-16 11:12:49 +01:00
const profile = ref({});
2025-10-21 14:30:25 +02:00
const dashboardDomain = ref('');
2025-03-16 11:12:49 +01:00
const subscription = ref({
plan: {},
});
const config = ref({});
2025-04-07 15:48:43 +02:00
const avatarUrl = ref('');
const features = ref({});
2025-01-03 15:06:41 +01:00
2025-02-03 19:14:01 +01:00
function onHashChange() {
2025-12-01 19:54:19 +01:00
const v = window.location.hash.split('?')[0];
2025-03-23 14:29:42 +01:00
2025-02-03 19:14:01 +01:00
if (v === VIEWS.APPS) {
view.value = VIEWS.APPS;
} else if (v.indexOf(VIEWS.APPSTORE) === 0 && profile.value.isAtLeastAdmin) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.APPSTORE;
} else if (v.indexOf(VIEWS.APP+'/') === 0) { // this checks permissions within the view as we may have an app operator
2025-02-20 10:54:43 +01:00
view.value = VIEWS.APP;
2025-05-22 16:26:24 +02:00
} else if (v === VIEWS.APPEARANCE && profile.value.isAtLeastAdmin) {
view.value = VIEWS.APPEARANCE;
} else if (v === VIEWS.BACKUP_SITES && profile.value.isAtLeastAdmin) {
view.value = VIEWS.BACKUP_SITES;
2025-10-04 23:11:30 +02:00
} else if (v === VIEWS.APP_ARCHIVE && profile.value.isAtLeastAdmin) {
view.value = VIEWS.APP_ARCHIVE;
} else if (v === VIEWS.CLOUDRON_ACCOUNT && profile.value.isAtLeastOwner) {
view.value = VIEWS.CLOUDRON_ACCOUNT;
} else if (v === VIEWS.DOMAINS && profile.value.isAtLeastAdmin) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.DOMAINS;
} else if (v.indexOf(VIEWS.EMAIL_DOMAIN+'/') === 0 && profile.value.isAtLeastMailManager) {
view.value = VIEWS.EMAIL_DOMAIN;
} else if (v === VIEWS.EMAIL_DOMAINS && profile.value.isAtLeastMailManager) {
view.value = VIEWS.EMAIL_DOMAINS;
2025-10-04 23:11:30 +02:00
} else if (v === VIEWS.MAILBOXES && profile.value.isAtLeastMailManager) {
view.value = VIEWS.MAILBOXES;
} else if (v === VIEWS.MAILINGLISTS && profile.value.isAtLeastMailManager) {
view.value = VIEWS.MAILINGLISTS;
2025-04-16 17:59:03 +02:00
} else if (v === VIEWS.EMAIL_SETTINGS && profile.value.isAtLeastMailManager) {
view.value = VIEWS.EMAIL_SETTINGS;
} else if (v === VIEWS.EMAIL_EVENTLOG && profile.value.isAtLeastMailManager) {
view.value = VIEWS.EMAIL_EVENTLOG;
2025-10-04 23:18:59 +02:00
} else if (v === VIEWS.SERVER && profile.value.isAtLeastAdmin) {
view.value = VIEWS.SERVER;
} else if (v === VIEWS.NETWORK && profile.value.isAtLeastAdmin) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.NETWORK;
} else if (v === VIEWS.PROFILE) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.PROFILE;
} else if (v === VIEWS.SERVICES && profile.value.isAtLeastAdmin) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.SERVICES;
2025-10-04 23:32:59 +02:00
} else if (v === VIEWS.SYSTEM_SETTINGS && profile.value.isAtLeastAdmin) {
view.value = VIEWS.SYSTEM_SETTINGS;
2025-10-04 23:11:30 +02:00
} else if (v === VIEWS.DOCKER && profile.value.isAtLeastAdmin) {
view.value = VIEWS.DOCKER;
} else if (v === VIEWS.SYSTEM_EVENTLOG && profile.value.isAtLeastAdmin) {
2025-07-03 12:15:06 +02:00
view.value = VIEWS.SYSTEM_EVENTLOG;
} else if (v === VIEWS.SYSTEM_UPDATE && profile.value.isAtLeastAdmin) {
view.value = VIEWS.SYSTEM_UPDATE;
2025-04-21 12:48:22 +02:00
} else if (v === VIEWS.USER_DIRECTORY_SETTINGS && profile.value.isAtLeastAdmin) {
view.value = VIEWS.USER_DIRECTORY_SETTINGS;
2025-10-05 10:39:40 +02:00
} else if (v === VIEWS.LDAP && profile.value.isAtLeastAdmin) {
view.value = VIEWS.LDAP;
} else if (v === VIEWS.OPENID && profile.value.isAtLeastAdmin) {
view.value = VIEWS.OPENID;
} else if (v === VIEWS.USERS && profile.value.isAtLeastUserManager) {
2025-02-11 18:50:10 +01:00
view.value = VIEWS.USERS;
2025-09-16 12:53:43 +02:00
} else if (v === VIEWS.GROUPS && profile.value.isAtLeastUserManager) {
view.value = VIEWS.GROUPS;
} else if (v === VIEWS.VOLUMES && profile.value.isAtLeastAdmin) {
2025-02-03 19:14:01 +01:00
view.value = VIEWS.VOLUMES;
} else {
window.location.href = VIEWS.APPS;
2025-02-03 19:14:01 +01:00
}
}
2024-11-01 14:16:09 +01:00
2025-04-07 15:48:43 +02:00
BrandingModel.onChange(BrandingModel.KEYS.NAME, (value) => {
window.document.title = value;
config.value.cloudronName = value;
});
BrandingModel.onChange(BrandingModel.KEYS.AVATAR, (value) => {
avatarUrl.value = value;
if (document.querySelector('link[rel="icon"]')) document.querySelector('link[rel="icon"]').href = value;
2025-04-07 15:48:43 +02:00
});
ProfileModel.onChange(ProfileModel.KEYS.AVATAR, (value) => {
profile.value.avatarUrl = value;
});
async function refreshProfile() {
const [error, result] = await profileModel.get();
if (error) return window.cloudron.onError(error);
profile.value = result;
}
async function refreshConfigAndFeatures() {
const [error, result] = await dashboardModel.config();
if (error) return window.cloudron.onError(error);
2025-10-15 13:37:37 +02:00
const currentVersion = localStorage.getItem('version');
if (currentVersion === null) {
localStorage.setItem('version', result.version);
} else if (result.version !== currentVersion) {
2025-10-15 13:37:37 +02:00
console.log('Dashboard version changed, reloading');
localStorage.setItem('version', result.version);
window.location.reload(true);
}
config.value = result;
features.value = result.features;
2025-10-21 14:30:25 +02:00
dashboardDomain.value = result.adminDomain;
}
2025-10-15 13:37:37 +02:00
async function onOnline() {
ready.value = true;
await refreshConfigAndFeatures(); // reload dashboard if needed after an update
}
provide('subscriptionRequiredDialog', subscriptionRequiredDialog);
provide('features', features);
provide('profile', profile);
provide('refreshProfile', refreshProfile);
provide('refreshFeatures', refreshConfigAndFeatures);
2025-10-21 14:30:25 +02:00
provide('dashboardDomain', dashboardDomain);
2025-02-03 19:14:01 +01:00
onMounted(async () => {
2025-10-01 11:51:31 +02:00
const [error, result] = await provisionModel.status();
if (error) return window.cloudron.onError(error);
2025-04-02 15:18:40 +02:00
if (redirectIfNeeded(result, 'dashboard')) return; // redirected to some other view...
2025-02-03 19:14:01 +01:00
if (!localStorage.token) {
2025-03-17 16:53:53 +01:00
localStorage.setItem('redirectToHash', window.location.hash);
// start oidc flow
window.location.href = `${API_ORIGIN}/openid/auth?client_id=` + (API_ORIGIN ? TOKEN_TYPES.ID_DEVELOPMENT : TOKEN_TYPES.ID_WEBADMIN) + '&scope=openid email profile&response_type=code token&redirect_uri=' + window.location.origin + '/authcallback.html';
2025-02-03 19:14:01 +01:00
return;
2024-11-01 14:16:09 +01:00
}
2025-02-03 19:14:01 +01:00
await refreshProfile();
2025-03-16 11:12:49 +01:00
// ensure language from profile if set
if (profile.value.language) await setLanguage(profile.value.language, true);
await refreshConfigAndFeatures();
2025-03-16 11:12:49 +01:00
2025-04-07 15:48:43 +02:00
avatarUrl.value = `https://${config.value.adminFqdn}/api/v1/cloudron/avatar`;
if (config.value.mandatory2FA && !profile.value.twoFactorAuthenticationEnabled) window.location.href = VIEWS.PROFILE;
2025-03-25 18:05:29 +01:00
window.addEventListener('hashchange', onHashChange);
onHashChange();
2025-10-15 13:37:37 +02:00
console.log(`Cloudron dashboard v${config.value.version}`);
ready.value = true;
2025-02-03 19:14:01 +01:00
});
2024-11-01 14:16:09 +01:00
</script>
<template>
2025-03-16 11:12:49 +01:00
<div style="overflow: hidden; height: 100%;">
<Notification />
<OfflineOverlay ref="offlineOverlay" @online="onOnline()" :href="'https://docs.cloudron.io/troubleshooting/'" :label="$t('main.offline')" />
<SubscriptionRequiredDialog ref="subscriptionRequiredDialog"/>
2025-12-01 17:55:52 +01:00
<RequestErrorDialog/>
2025-01-19 19:56:31 +01:00
2025-06-03 12:43:14 +02:00
<div v-if="ready" style="display: flex; flex-direction: row; overflow: hidden; height: 100%;">
<SideBar v-if="profile.isAtLeastUserManager" :profile="profile" :active-view="view" :VIEWS="VIEWS" :cloudron-name="config.cloudronName" :cloudron-avatar-url="avatarUrl"/>
2025-06-03 12:43:14 +02:00
<div style="flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; height: 100%;">
<Headerbar :config="config" :subscription="subscription"/>
2025-06-03 12:43:14 +02:00
2025-09-19 18:55:43 +02:00
<div style="display: flex; justify-content: center; overflow: auto; flex-grow: 1; padding: 0; margin: 0 10px; position: relative;">
<KeepAlive>
<AppsView v-if="view === VIEWS.APPS" />
<AppstoreView v-else-if="view === VIEWS.APPSTORE" />
</KeepAlive>
<AppConfigureView v-if="view === VIEWS.APP" />
2025-06-03 12:43:14 +02:00
<AppearanceView v-else-if="view === VIEWS.APPEARANCE" />
<BackupSitesView v-else-if="view === VIEWS.BACKUP_SITES" />
2025-10-05 10:45:38 +02:00
<AppArchiveView v-else-if="view === VIEWS.APP_ARCHIVE" />
2025-06-03 12:43:14 +02:00
<CloudronAccountView v-else-if="view === VIEWS.CLOUDRON_ACCOUNT" />
<DomainsView v-else-if="view === VIEWS.DOMAINS" />
<EmailDomainsView v-else-if="view === VIEWS.EMAIL_DOMAINS" />
2025-06-03 12:43:14 +02:00
<EmailDomainView v-else-if="view === VIEWS.EMAIL_DOMAIN" />
2025-10-04 23:11:30 +02:00
<EmailMailboxesView v-else-if="view === VIEWS.MAILBOXES" />
<EmailMailinglistsView v-else-if="view === VIEWS.MAILINGLISTS" />
2025-06-03 12:43:14 +02:00
<EmailSettingsView v-else-if="view === VIEWS.EMAIL_SETTINGS" />
<EmailEventlogView v-else-if="view === VIEWS.EMAIL_EVENTLOG" />
2025-07-03 12:15:06 +02:00
<EventlogView v-else-if="view === VIEWS.SYSTEM_EVENTLOG" />
2025-10-05 10:45:38 +02:00
<ServerView v-else-if="view === VIEWS.SERVER" />
2025-06-03 12:43:14 +02:00
<NetworkView v-else-if="view === VIEWS.NETWORK" />
<ProfileView v-else-if="view === VIEWS.PROFILE" />
<ServicesView v-else-if="view === VIEWS.SERVICES" />
2025-10-05 10:45:38 +02:00
<SystemSettingsView v-else-if="view === VIEWS.SYSTEM_SETTINGS" />
<DockerView v-else-if="view === VIEWS.DOCKER" />
2025-07-03 12:15:06 +02:00
<SystemUpdateView v-else-if="view === VIEWS.SYSTEM_UPDATE" />
2025-06-03 12:43:14 +02:00
<UserDirectorySettingsView v-else-if="view === VIEWS.USER_DIRECTORY_SETTINGS" />
<LdapView v-else-if="view === VIEWS.LDAP" />
<OpenIdView v-else-if="view === VIEWS.OPENID" />
2025-06-03 12:43:14 +02:00
<UsersView v-else-if="view === VIEWS.USERS" />
2025-09-16 12:53:43 +02:00
<GroupsView v-else-if="view === VIEWS.GROUPS" />
2025-06-03 12:43:14 +02:00
<VolumesView v-else-if="view === VIEWS.VOLUMES" />
2025-03-16 11:12:49 +01:00
</div>
</div>
2025-06-03 12:43:14 +02:00
</div>
</div>
</template>