diff --git a/dashboard/src/components/AppArchive.vue b/dashboard/src/components/AppArchive.vue
new file mode 100644
index 000000000..a5529494a
--- /dev/null
+++ b/dashboard/src/components/AppArchive.vue
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ archive.appConfig ? archive.appConfig.fqdn : '-' }}
+
+
+ {{ archive.manifest.title }}
+
+
+ {{ prettyLongDate(archive.creationTime) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/models/ArchivesModel.js b/dashboard/src/models/ArchivesModel.js
new file mode 100644
index 000000000..28bfa5166
--- /dev/null
+++ b/dashboard/src/models/ArchivesModel.js
@@ -0,0 +1,81 @@
+
+import { fetcher } from 'pankow';
+import DomainsModel from './DomainsModel.js';
+
+function create() {
+ const accessToken = localStorage.token;
+ const origin = import.meta.env.VITE_API_ORIGIN || window.location.origin;
+
+ const domainsModel = DomainsModel.create();
+
+ return {
+ async list() {
+ const page = 1;
+ const per_page = 1000;
+
+ let error, result;
+ try {
+ result = await fetcher.get(`${origin}/api/v1/archives`, { page, per_page, access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 200) return [error || result];
+ return [null, result.body.archives];
+ },
+ async remove(id) {
+ let error, result;
+ try {
+ result = await fetcher.del(`${origin}/api/v1/archives/${id}`, { access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 204) return [error || result];
+ return [null];
+ },
+ async restore(id, data) {
+ // preflight domain checks
+ if (!data.overwriteDns) {
+ const allDomains = [{ domain: data.domain, subdomain: data.subdomain }].concat(Object.keys(data.secondaryDomains).map(function (k) {
+ return {
+ domain: data.secondaryDomains[k].domain,
+ subdomain: data.secondaryDomains[k].subdomain
+ };
+ }));
+
+ for (const domain in allDomains) {
+ const [error, result] = await domainsModel.checkRecords(domain.domain, domain.subdomain);
+ if (error) return [error];
+
+ const fqdn = domain.subdomain + '.' + domain.domain;
+
+ if (result.error) {
+ if (result.error.reason === 'Access Denied') return [{ type: 'provider', fqdn, message: 'DNS credentials for ' + domain.domain + ' are invalid. Update it in Domains & Certs view' }];
+ return [{ type: 'provider', fqdn, message: result.error.message }];
+ }
+
+ if (result.needsOverwrite) {
+ // $scope.archiveRestore.needsOverwrite = true;
+ // $scope.archiveRestore.overwriteDns = true;
+ return [{ type: 'externally_exists', fqdn, message: 'DNS Record already exists. Confirm that the domain is not in use for services external to Cloudron' }];
+ }
+ }
+ }
+
+ let error, result;
+ try {
+ result = await fetcher.post(`${origin}/api/v1/archives/${id}/unarchive`, data, { access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 202) return [error || result];
+ return [null, result.body];
+ },
+ };
+}
+
+export default {
+ create,
+};
diff --git a/dashboard/src/models/DomainsModel.js b/dashboard/src/models/DomainsModel.js
index 5dc40dc74..f8227fa3c 100644
--- a/dashboard/src/models/DomainsModel.js
+++ b/dashboard/src/models/DomainsModel.js
@@ -98,6 +98,16 @@ function create() {
if (error || result.status !== 204) return [error || result];
return [null];
},
+ async checkRecords(domain, subdomain) {
+ let error, result;
+ try {
+ result = await fetcher.get(`${origin}/api/v1/domains/${domain}/dns_check`, { subdomain, access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 200) return [error || result];
+ },
};
}
diff --git a/dashboard/src/views/BackupsView.vue b/dashboard/src/views/BackupsView.vue
index 60425032c..2fada07db 100644
--- a/dashboard/src/views/BackupsView.vue
+++ b/dashboard/src/views/BackupsView.vue
@@ -6,6 +6,7 @@ import { Button } from 'pankow';
import Section from '../components/Section.vue';
import BackupSchedule from '../components/BackupSchedule.vue';
import BackupList from '../components/BackupList.vue';
+import AppArchive from '../components/AppArchive.vue';
import BackupsModel from '../models/BackupsModel.js';
import ProfileModel from '../models/ProfileModel.js';
@@ -119,5 +120,6 @@ onMounted(async () => {
+
diff --git a/dashboard/src/views/VolumesView.vue b/dashboard/src/views/VolumesView.vue
index fa7196ff7..22132a82e 100644
--- a/dashboard/src/views/VolumesView.vue
+++ b/dashboard/src/views/VolumesView.vue
@@ -292,7 +292,7 @@ onMounted(async () =>{
-
+