diff --git a/dashboard/public/js/index.js b/dashboard/public/js/index.js index ab95e9ab1..3dfd5165e 100644 --- a/dashboard/public/js/index.js +++ b/dashboard/public/js/index.js @@ -68,8 +68,8 @@ app.config(['$routeProvider', function ($routeProvider) { // controller: 'ProfileController', // templateUrl: 'views/profile.html?' + window.VITE_CACHE_ID }).when('/backups', { - controller: 'BackupsController', - templateUrl: 'views/backups.html?' + window.VITE_CACHE_ID + // controller: 'BackupsController', + // templateUrl: 'views/backups.html?' + window.VITE_CACHE_ID }).when('/branding', { controller: 'BrandingController', templateUrl: 'views/branding.html?' + window.VITE_CACHE_ID diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index 0b4af8cd2..ec4d6ca2d 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -4,6 +4,7 @@ import { onMounted, ref } from 'vue'; import { Notification } from 'pankow'; import AppsView from './views/AppsView.vue'; import AppstoreView from './views/AppstoreView.vue'; +import BackupsView from './views/BackupsView.vue'; import DomainsView from './views/DomainsView.vue'; import EventlogView from './views/EventlogView.vue'; import NetworkView from './views/NetworkView.vue'; @@ -17,6 +18,7 @@ import VolumesView from './views/VolumesView.vue'; const VIEWS = { APPS: 'apps', APPSTORE: 'appstore', + BACKUPS: 'backups', DOMAINS: 'domains', EVENTLOG: 'eventlog', NETWORK: 'network', @@ -37,6 +39,8 @@ function onHashChange() { view.value = VIEWS.APPS; } else if (v.indexOf(VIEWS.APPSTORE) === 0) { view.value = VIEWS.APPSTORE; + } else if (v === VIEWS.BACKUPS) { + view.value = VIEWS.BACKUPS; } else if (v === VIEWS.DOMAINS) { view.value = VIEWS.DOMAINS; } else if (v === VIEWS.EVENTLOG) { @@ -81,6 +85,7 @@ onMounted(async () => { + diff --git a/dashboard/src/components/BackupList.vue b/dashboard/src/components/BackupList.vue new file mode 100644 index 000000000..3f9569a24 --- /dev/null +++ b/dashboard/src/components/BackupList.vue @@ -0,0 +1,140 @@ + + + diff --git a/dashboard/src/components/BackupSchedule.vue b/dashboard/src/components/BackupSchedule.vue new file mode 100644 index 000000000..5940111e5 --- /dev/null +++ b/dashboard/src/components/BackupSchedule.vue @@ -0,0 +1,263 @@ + + + diff --git a/dashboard/src/components/SyncDns.vue b/dashboard/src/components/SyncDns.vue index 377e2971d..9e8a9b27d 100644 --- a/dashboard/src/components/SyncDns.vue +++ b/dashboard/src/components/SyncDns.vue @@ -8,7 +8,7 @@ import Section from '../components/Section.vue'; import TasksModel from '../models/TasksModel.js'; import DomainsModel from '../models/DomainsModel.js'; -const taskModel = TasksModel.create(); +const tasksModel = TasksModel.create(); const domainsModel = DomainsModel.create(); const lastTask = ref({}); @@ -17,7 +17,7 @@ const taskLogsMenu = ref([]); async function waitForLastTask() { if (!lastTask.value.id) return; - const [error, result] = await taskModel.get(lastTask.value.id); + const [error, result] = await tasksModel.get(lastTask.value.id); if (error) return console.error(error); lastTask.value = result; @@ -29,7 +29,7 @@ async function waitForLastTask() { } async function refreshTasks() { - const [error, result] = await taskModel.getByType(TASK_TYPES.TASK_SYNC_DNS_RECORDS); + const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_SYNC_DNS_RECORDS); if (error) return console.error(error); lastTask.value = result[0] || {}; diff --git a/dashboard/src/models/BackupsModel.js b/dashboard/src/models/BackupsModel.js new file mode 100644 index 000000000..4bee0f19f --- /dev/null +++ b/dashboard/src/models/BackupsModel.js @@ -0,0 +1,72 @@ + +import { fetcher } from 'pankow'; + +function create() { + const accessToken = localStorage.token; + const origin = import.meta.env.VITE_API_ORIGIN || window.location.origin; + + return { + async list() { + const page = 1; + const per_page = 1000; + + let error, result; + try { + result = await fetcher.get(`${origin}/api/v1/backups`, { page, per_page, access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error || result.status !== 200) return [error || result]; + return [null, result.body.backups]; + }, + async getConfig() { + let error, result; + try { + result = await fetcher.get(`${origin}/api/v1/backups/config`, { access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error || result.status !== 200) return [error || result]; + return [null, result.body]; + }, + async getPolicy() { + let error, result; + try { + result = await fetcher.get(`${origin}/api/v1/backups/policy`, { access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error || result.status !== 200) return [error || result]; + return [null, result.body.policy]; + }, + async setPolicy(policy) { + let error, result; + try { + result = await fetcher.post(`${origin}/api/v1/backups/policy`, policy, { access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error || result.status !== 200) return [error || result]; + return [null]; + }, + async cleanup() { + let error, result; + try { + result = await fetcher.post(`${origin}/api/v1/backups/cleanup`, {}, { access_token: accessToken }); + } catch (e) { + error = e; + } + + if (error || result.status !== 202) return [error || result]; + return [null, result.body.taskId]; + }, + }; +} + +export default { + create, +}; diff --git a/dashboard/src/models/ProfileModel.js b/dashboard/src/models/ProfileModel.js index 1270221fd..fea2c29d1 100644 --- a/dashboard/src/models/ProfileModel.js +++ b/dashboard/src/models/ProfileModel.js @@ -29,9 +29,12 @@ function create() { return []; } - result.body.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(result.body.role) !== -1; + if (error || result.status !== 200) return [error || result]; - return result.body; + result.body.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(result.body.role) !== -1; + result.body.isAtLeastOwner = [ ROLES.OWNER ].indexOf(result.body.role) !== -1; + + return [null, result.body]; }, async setPassword(password, newPassword) { let error, result; diff --git a/dashboard/src/views/AppsView.vue b/dashboard/src/views/AppsView.vue index 4472e6dbf..44a9edd3c 100644 --- a/dashboard/src/views/AppsView.vue +++ b/dashboard/src/views/AppsView.vue @@ -169,14 +169,17 @@ function toggleView() { } onMounted(async () => { - profile.value = await profileModel.get(); + let [error, result] = await profileModel.get(); + if (error) return console.error(error); + + profile.value = result; await refreshApps(); - const [error, domains] = await domainsModel.list(); + [error, result] = await domainsModel.list(); if (error) return console.error(error); - domainFilterOptions.value = domainFilterOptions.value.concat(domains.map(d => { d.id = d.domain; return d; })); + domainFilterOptions.value = domainFilterOptions.value.concat(result.map(d => { d.id = d.domain; return d; })); domainFilter.value = domainFilterOptions.value[0].id; stateFilter.value = stateFilterOptions[0].id; diff --git a/dashboard/src/views/BackupsView.vue b/dashboard/src/views/BackupsView.vue new file mode 100644 index 000000000..be5d31477 --- /dev/null +++ b/dashboard/src/views/BackupsView.vue @@ -0,0 +1,123 @@ + + + diff --git a/dashboard/src/views/ProfileView.vue b/dashboard/src/views/ProfileView.vue index ff1940ace..532428673 100644 --- a/dashboard/src/views/ProfileView.vue +++ b/dashboard/src/views/ProfileView.vue @@ -36,6 +36,12 @@ async function onSelectLanguage(lang) { // TODO dynamically change lange instead of reloading } +async function refreshProfile() { + const [error, result] = await profileModel.get(); + if (error) return console.error(error); + + user.value = result; +} // Profile edits async function onChangeDisplayName(currentDisplayName) { @@ -53,7 +59,7 @@ async function onChangeDisplayName(currentDisplayName) { const error = await profileModel.setDisplayName(displayName); if (error) return console.error('Failed to set displayName', error); - user.value = await profileModel.get(); + await refreshProfile(); } async function onChangeEmail(currentEmail) { @@ -72,7 +78,7 @@ async function onChangeEmail(currentEmail) { const error = await profileModel.setEmail(result[0], result[1]); if (error) return console.error('Failed to set email', error); - user.value = await profileModel.get(); + await refreshProfile(); } async function onChangeFallbackEmail(currentFallbackEmail) { @@ -91,7 +97,7 @@ async function onChangeFallbackEmail(currentFallbackEmail) { const error = await profileModel.setFallbackEmail(result[0], result[1]); if (error) return console.error('Failed to set fallback email', error); - user.value = await profileModel.get(); + await refreshProfile(); } const avatarFileInput = useTemplateRef('avatarFileInput'); @@ -173,7 +179,7 @@ async function onOpenTwoFASetupDialog() { async function onTwoFAEnable() { const [error] = await profileModel.enableTwoFA(twoFATotpToken.value); if (error) return twoFAEnableError.value = error.body ? error.body.message : 'Internal error'; - user.value = await profileModel.get(); + await refreshProfile(); twoFADialog.value.close(); } @@ -194,13 +200,13 @@ async function onTwoFADisable() { const [error] = await profileModel.disableTwoFA(password); if (error) return onTwoFADisable(); - user.value = await profileModel.get(); + await refreshProfile(); } // Init onMounted(async () => { - user.value = await profileModel.get(); + await refreshProfile(); let [error, result] = await cloudronModel.languages(); languages.value = result.map(l => {