Files
cloudron-box/dashboard/src/models/AppsModel.js
T

373 lines
12 KiB
JavaScript
Raw Normal View History

2023-07-14 14:48:43 +02:00
2025-03-03 11:22:56 +01:00
import { API_ORIGIN, APP_TYPES, PROXY_APP_ID, HSTATES, ISTATES, RSTATES } from '../constants.js';
import { fetcher } from 'pankow';
2023-07-14 14:48:43 +02:00
import { sleep } from 'pankow/utils';
2025-01-02 19:04:07 +01:00
import moment from 'moment';
function installationStateLabel(app) {
if (!app) return '';
const waiting = app.progress === 0 ? ' (Queued)' : '';
switch (app.installationState) {
case ISTATES.PENDING_INSTALL:
return 'Installing' + waiting;
case ISTATES.PENDING_CLONE:
return 'Cloning' + waiting;
case ISTATES.PENDING_LOCATION_CHANGE:
case ISTATES.PENDING_CONFIGURE:
case ISTATES.PENDING_RECREATE_CONTAINER:
case ISTATES.PENDING_SERVICES_CHANGE:
case ISTATES.PENDING_DEBUG:
return 'Configuring' + waiting;
case ISTATES.PENDING_RESIZE:
return 'Resizing' + waiting;
case ISTATES.PENDING_DATA_DIR_MIGRATION:
return 'Migrating data' + waiting;
case ISTATES.PENDING_UNINSTALL: return 'Uninstalling' + waiting;
case ISTATES.PENDING_RESTORE: return 'Restoring' + waiting;
case ISTATES.PENDING_IMPORT: return 'Importing' + waiting;
case ISTATES.PENDING_UPDATE: return 'Updating' + waiting;
case ISTATES.PENDING_BACKUP: return 'Backing up' + waiting;
case ISTATES.PENDING_START: return 'Starting' + waiting;
case ISTATES.PENDING_STOP: return 'Stopping' + waiting;
case ISTATES.PENDING_RESTART: return 'Restarting' + waiting;
case ISTATES.ERROR: {
if (app.error && app.error.message === 'ETRYAGAIN') return 'DNS Error';
return 'Error';
}
case ISTATES.INSTALLED: {
if (app.debugMode) {
return 'Recovery Mode';
} else if (app.runState === RSTATES.RUNNING) {
if (!app.health) return 'Starting...'; // no data yet
if (app.type === APP_TYPES.LINK) return '';
if (app.health === HSTATES.HEALTHY) return 'Running';
return 'Not responding'; // dead/exit/unhealthy
} else if (app.runState === RSTATES.STOPPED) {
return 'Stopped';
} else {
return app.runState;
}
}
default: return app.installationState;
}
}
function installationActive(app) {
if (app.installationState === ISTATES.ERROR) return false;
if (app.installationState === ISTATES.INSTALLED) return false;
return true;
}
function appProgressMessage(app) {
return app.message || (app.error ? app.error.message : '');
}
2025-01-31 21:02:48 +01:00
function create() {
const accessToken = localStorage.token;
2025-01-02 19:04:07 +01:00
async function getTask(appId) {
let error, result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${appId}/task`, { access_token: accessToken });
2025-01-02 19:04:07 +01:00
} catch (e) {
error = e;
}
if (error || result.status !== 200) {
console.error('Failed to get task for app.', error || result.status);
return null;
}
return result.body;
}
2023-07-14 14:48:43 +02:00
return {
2024-12-29 00:36:48 +01:00
name: 'AppsModel',
2025-01-02 19:04:07 +01:00
getTask,
2025-02-21 14:07:07 +01:00
isStopped(app) {
if (app.installationState === ISTATES.PENDING_START || app.installationState === ISTATES.PENDING_STOP) {
return app.installationState === ISTATES.PENDING_START;
} else {
return app.runState === RSTATES.STOPPED;
}
},
2025-01-06 14:35:14 +01:00
async install(manifest, config) {
const data = {
appStoreId: manifest.id + '@' + manifest.version,
subdomain: config.subdomain,
domain: config.domain,
secondaryDomains: config.secondaryDomains,
ports: config.ports,
accessRestriction: config.accessRestriction,
cert: config.cert,
key: config.key,
sso: config.sso,
overwriteDns: config.overwriteDns,
upstreamUri: config.upstreamUri,
backupId: config.backupId // when restoring from archive
};
let error, result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps`, data, { access_token: accessToken });
2025-01-06 14:35:14 +01:00
} catch (e) {
error = e;
}
if (error || result.status !== 202) {
console.error('Failed to install app.', error || result.status);
return error || result.body;
}
return '';
},
2024-12-29 00:36:48 +01:00
async list() {
let error, result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps`, { access_token: accessToken });
2024-12-29 00:36:48 +01:00
} catch (e) {
error = e;
}
2025-01-25 17:09:53 +01:00
if (error || result.status !== 200) return [error || result];
2024-12-29 00:36:48 +01:00
2025-01-02 19:04:07 +01:00
for (const app of result.body.apps) {
app.ssoAuth = app.sso && (app.manifest.addons['ldap'] || app.manifest.addons['oidc'] || app.manifest.addons['proxyAuth']); // checking app.sso first ensures app.manifest.addons is not null
app.type = app.manifest.id === PROXY_APP_ID ? APP_TYPES.PROXIED : APP_TYPES.APP;
2025-03-03 11:22:56 +01:00
app.iconUrl = app.iconUrl ? `${API_ORIGIN}${app.iconUrl}?ts=${new Date(app.ts).getTime()}` : `${API_ORIGIN}/img/appicon_fallback.png`; // calculate full icon url with cache busting
2025-01-02 19:04:07 +01:00
// only fetch if we have permissions and a taskId is set/active
if (!app.taskId || (app.accessLevel !== 'operator' && app.accessLevel !== 'admin')) {
2025-01-02 19:04:07 +01:00
app.progress = 0;
app.message = '';
app.taskMinutesActive = 0;
continue;
}
const task = await getTask(app.id);
if (task) {
app.progress = task.percent;
app.message = task.message;
app.taskMinutesActive = moment.duration(moment.utc().diff(moment.utc(task.creationTime))).asMinutes();
} else {
app.progress = 0;
app.message = '';
app.taskMinutesActive = 0;
}
}
2025-01-25 17:09:53 +01:00
return [null, result.body.apps];
2024-12-29 00:36:48 +01:00
},
2025-01-21 16:54:56 +01:00
async get(id) {
2023-07-14 14:48:43 +02:00
let error, result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${id}`, { access_token: accessToken });
2023-07-14 14:48:43 +02:00
} catch (e) {
error = e;
}
2025-01-21 16:54:56 +01:00
if (error || result.status !== 200) return [error || result];
const app = result.body;
app.ssoAuth = app.sso && (app.manifest.addons['ldap'] || app.manifest.addons['oidc'] || app.manifest.addons['proxyAuth']); // checking app.sso first ensures app.manifest.addons is not null
app.type = app.manifest.id === PROXY_APP_ID ? APP_TYPES.PROXIED : APP_TYPES.APP;
2025-03-03 11:22:56 +01:00
app.iconUrl = app.iconUrl ? `${API_ORIGIN}${app.iconUrl}?ts=${new Date(app.ts).getTime()}` : `${API_ORIGIN}/img/appicon_fallback.png`; // calculate full icon url with cache busting
// only fetch if we have permissions and a taskId is set/active
if (!app.taskId || (app.accessLevel !== 'operator' && app.accessLevel !== 'admin')) {
app.progress = 0;
app.message = '';
app.taskMinutesActive = 0;
} else {
const task = await getTask(app.id);
if (task) {
app.progress = task.percent;
app.message = task.message;
app.taskMinutesActive = moment.duration(moment.utc().diff(moment.utc(task.creationTime))).asMinutes();
} else {
app.progress = 0;
app.message = '';
app.taskMinutesActive = 0;
}
}
return [null, app];
2023-07-14 14:48:43 +02:00
},
2025-01-27 12:18:15 +01:00
async restart(id) {
2025-02-22 18:31:21 +01:00
let result;
2023-07-14 14:48:43 +02:00
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/restart`, null, { access_token: accessToken });
2023-07-14 14:48:43 +02:00
} catch (e) {
2025-02-22 18:31:21 +01:00
return [e];
2023-07-14 14:48:43 +02:00
}
2025-02-22 18:31:21 +01:00
if (result.status !== 202) return [result];
2023-07-14 14:48:43 +02:00
while(true) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${id}`, { access_token: accessToken });
2023-07-14 14:48:43 +02:00
} catch (e) {
2025-02-22 18:31:21 +01:00
return [e];
2023-07-14 14:48:43 +02:00
}
2025-02-22 18:31:21 +01:00
if (result.status !== 200) return [result];
2023-07-14 14:48:43 +02:00
2025-02-22 18:31:21 +01:00
// are we done here?
if (result.body.installationState !== ISTATES.INSTALLED) await sleep(2000);
else return [null];
2023-07-14 14:48:43 +02:00
}
2025-02-20 16:12:36 +01:00
},
async start(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/start`, {}, { access_token: accessToken });
} catch (e) {
return [e];
}
if (result.status !== 202) return [result];
return [null, result.body.taskId];
},
async stop(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/stop`, {}, { access_token: accessToken });
} catch (e) {
return [e];
}
if (result.status !== 202) return [result];
return [null, result.body.taskId];
},
2025-02-20 16:12:36 +01:00
async configure(id, setting, data) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/configure/${setting}`, data, { access_token: accessToken });
2025-02-20 16:12:36 +01:00
} catch (e) {
return [e];
}
if (result.status !== 200 && result.status !== 202) return [result];
return [null];
},
2025-02-21 14:07:07 +01:00
async uninstall(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/uninstall`, {}, { access_token: accessToken });
2025-02-21 14:07:07 +01:00
} catch (e) {
return [e];
}
if (result.status !== 202) return [result];
return [null];
},
2025-02-21 16:30:59 +01:00
async getEvents(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${id}/eventlog`, { page: 1, per_page: 100, access_token: accessToken });
2025-02-21 16:30:59 +01:00
} catch (e) {
return [e];
}
if (result.status !== 200) return [result];
return [null, result.body.eventlogs];
},
2025-02-21 20:58:43 +01:00
async checkForUpdates(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/check_for_updates`, {}, { access_token: accessToken });
2025-02-21 20:58:43 +01:00
} catch (e) {
return [e];
}
if (result.status !== 200) return [result];
return [null, result.body.update];
},
async update(id, manifest, skipBackup = false) {
const data = {
appStoreId: `${manifest.id}@${manifest.version}`,
skipBackup: !!skipBackup,
};
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/update`, data, { access_token: accessToken });
2025-02-21 20:58:43 +01:00
} catch (e) {
return [e];
}
2025-02-26 17:22:28 +01:00
if (result.status !== 202) return [result];
return [null];
},
async backups(id) {
// we fetch probably enough to avoid pagination
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${id}/backups`, { page: 1, per_page: 100, access_token: accessToken });
2025-02-26 17:22:28 +01:00
} catch (e) {
return [e];
}
if (result.status !== 200) return [result];
return [null, result.body.backups];
},
async backup(id) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/backup`, {}, { access_token: accessToken });
2025-02-26 17:22:28 +01:00
} catch (e) {
return [e];
}
2025-02-21 20:58:43 +01:00
if (result.status !== 202) return [result];
return [null];
},
2025-02-26 20:30:33 +01:00
async updateBackup(id, backupId, label, preserveSecs) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/backups/${backupId}`, { label, preserveSecs }, { access_token: accessToken });
2025-02-26 20:30:33 +01:00
} catch (e) {
return [e];
}
if (result.status !== 200) return [result];
return [null];
},
2025-02-27 17:09:18 +01:00
async restore(id, backupId) {
let result;
try {
2025-03-03 11:22:56 +01:00
result = await fetcher.post(`${API_ORIGIN}/api/v1/apps/${id}/restore`, { backupId }, { access_token: accessToken });
2025-02-27 17:09:18 +01:00
} catch (e) {
return [e];
}
if (result.status !== 202) return [result];
return [null];
},
2025-03-07 11:54:43 +01:00
async graphs(id, fromMinutes) {
let result;
try {
result = await fetcher.get(`${API_ORIGIN}/api/v1/apps/${id}/graphs`, { fromMinutes, access_token: accessToken });
} catch (e) {
return [e];
}
if (result.status !== 200) return [result];
return [null, result.body];
},
2023-07-14 14:48:43 +02:00
};
}
export default {
2025-01-02 19:04:07 +01:00
create,
installationStateLabel,
installationActive,
appProgressMessage,
2023-07-14 14:48:43 +02:00
};