2025-03-22 11:19:06 +01:00
|
|
|
<script setup>
|
|
|
|
|
|
|
|
|
|
import { ref, useTemplateRef } from 'vue';
|
2025-04-28 17:06:31 +02:00
|
|
|
import { Dialog, FormGroup, SingleSelect } from 'pankow';
|
2025-03-22 11:19:06 +01:00
|
|
|
import { prettyDate } from 'pankow/utils';
|
|
|
|
|
import { ISTATES } from '../constants.js';
|
|
|
|
|
import { taskNameFromInstallationState } from '../utils.js';
|
|
|
|
|
import AppsModel from '../models/AppsModel.js';
|
|
|
|
|
|
|
|
|
|
const appsModel = AppsModel.create();
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits([ 'success' ]);
|
|
|
|
|
|
|
|
|
|
const dialog = useTemplateRef('dialog');
|
|
|
|
|
|
|
|
|
|
const busy = ref(false);
|
|
|
|
|
const formError = ref('');
|
|
|
|
|
const id = ref('');
|
|
|
|
|
const fqdn = ref('');
|
|
|
|
|
const appError = ref(null);
|
|
|
|
|
const backups = ref([]);
|
|
|
|
|
const backupId = ref('');
|
|
|
|
|
|
|
|
|
|
async function onSubmit() {
|
|
|
|
|
busy.value = true;
|
|
|
|
|
formError.value = '';
|
|
|
|
|
|
|
|
|
|
const errorState = (appError.value && appError.value.installationState) || ISTATES.PENDING_CONFIGURE;
|
|
|
|
|
const data = {};
|
|
|
|
|
|
|
|
|
|
let repairFunc;
|
|
|
|
|
switch (errorState) {
|
|
|
|
|
case ISTATES.PENDING_INSTALL:
|
|
|
|
|
case ISTATES.PENDING_CLONE: // if manifest or bad image, use CLI to provide new manifest
|
|
|
|
|
repairFunc = appsModel.repair.bind(null, id.value, {}); // this will trigger a re-install
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// case ISTATES.PENDING_LOCATION_CHANGE:
|
|
|
|
|
// data.subdomain = $scope.repair.subdomain;
|
|
|
|
|
// data.domain = $scope.repair.domain.domain;
|
|
|
|
|
// data.aliasDomains = $scope.repair.aliasDomains.filter(function (a) { return a.enabled; })
|
|
|
|
|
// .map(function (d) { return { subdomain: d.subdomain, domain: d.domain.domain }; });
|
|
|
|
|
// data.redirectDomains = $scope.repair.redirectDomains.filter(function (a) { return a.enabled; })
|
|
|
|
|
// .map(function (d) { return { subdomain: d.subdomain, domain: d.domain.domain }; });
|
|
|
|
|
// data.overwriteDns = true; // always overwriteDns. user can anyway check and uncheck above
|
|
|
|
|
// repairFunc = Client.configureApp.bind(null, $scope.app.id, 'location', data);
|
|
|
|
|
// break;
|
|
|
|
|
|
|
|
|
|
case ISTATES.PENDING_DATA_DIR_MIGRATION:
|
|
|
|
|
repairFunc = appsModel.configure.bind(null, id.value, 'storage', { storageVolumeId: null, storageVolumePrefix: null });
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// this also happens for import faliures. this UI can only show backup listing. use CLI for arbit id/config
|
|
|
|
|
case ISTATES.PENDING_RESTORE:
|
|
|
|
|
case ISTATES.PENDING_IMPORT:
|
|
|
|
|
if (backups.value.length === 0) { // this can happen when you give some invalid backup via CLI and restore via UI
|
|
|
|
|
repairFunc = appsModel.repair.bind(null, id.value, {}); // this will trigger a re-install
|
|
|
|
|
} else {
|
|
|
|
|
repairFunc = appsModel.restore.bind(null, id.value, backupId.value);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ISTATES.PENDING_UNINSTALL:
|
|
|
|
|
repairFunc = appsModel.uninstall.bind(null, id.value);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ISTATES.PENDING_START:
|
|
|
|
|
case ISTATES.PENDING_STOP:
|
|
|
|
|
case ISTATES.PENDING_RESTART:
|
|
|
|
|
case ISTATES.PENDING_RESIZE:
|
|
|
|
|
case ISTATES.PENDING_DEBUG:
|
|
|
|
|
case ISTATES.PENDING_RECREATE_CONTAINER:
|
|
|
|
|
case ISTATES.PENDING_CONFIGURE:
|
|
|
|
|
case ISTATES.PENDING_BACKUP: // can happen if the backup task was killed/rebooted
|
|
|
|
|
case ISTATES.PENDING_UPDATE: // when update failed, just bring it back to current state and user can click update again
|
|
|
|
|
default:
|
|
|
|
|
repairFunc = appsModel.repair.bind(null, id.value, {});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [error] = await repairFunc();
|
|
|
|
|
if (error) {
|
|
|
|
|
busy.value = false;
|
|
|
|
|
return console.error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit('success');
|
|
|
|
|
dialog.value.close();
|
|
|
|
|
busy.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
async open(app) {
|
|
|
|
|
id.value = app.id;
|
|
|
|
|
fqdn.value = app.fqdn;
|
|
|
|
|
appError.value = app.error || null;
|
|
|
|
|
|
|
|
|
|
const errorState = (app.error && app.error.installationState) || ISTATES.PENDING_CONFIGURE;
|
|
|
|
|
|
|
|
|
|
if (errorState === ISTATES.PENDING_RESTORE || errorState === ISTATES.PENDING_IMPORT) {
|
|
|
|
|
const [error, result] = await appsModel.backups(app.id);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
result.forEach(b => {
|
|
|
|
|
b.label = prettyDate(b.creationTime) + ` - v${b.packageVersion}`;
|
|
|
|
|
});
|
|
|
|
|
backups.value = result;
|
|
|
|
|
backupId.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dialog.value.open();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div>
|
|
|
|
|
<Dialog ref="dialog" :title="$t('app.repairDialog.title', { app: fqdn })"
|
|
|
|
|
:confirm-label="$t('app.repairDialog.retryAction', { task: taskNameFromInstallationState(appError ? appError.installationState : '') })"
|
|
|
|
|
:confirm-active="!busy"
|
|
|
|
|
:confirm-busy="busy"
|
|
|
|
|
:reject-label="busy ? '' : $t('main.dialog.cancel')"
|
|
|
|
|
reject-style="secondary"
|
|
|
|
|
@confirm="onSubmit()"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="text-danger" v-if="formError">{{ formError }}</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="appError">
|
|
|
|
|
<p v-html="$t('app.repairDialog.taskError', { task: taskNameFromInstallationState(appError ? appError.installationState : '') })"></p>
|
|
|
|
|
<p class="text-danger">{{ appError.reason + ': ' + appError.message }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else>
|
|
|
|
|
<p>{{ $t('app.repairDialog.description') }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form @submit.prevent="onSubmit()" autocomplete="off">
|
|
|
|
|
<fieldset :disabled="busy">
|
|
|
|
|
<FormGroup v-if="backups.length">
|
|
|
|
|
<label for="backupInput">{{ $t('app.repairDialog.fromBackup') }}</label>
|
|
|
|
|
<SingleSelect :options="backups" v-model="backupId" option-key="id" option-label="label"/>
|
|
|
|
|
</FormGroup>
|
|
|
|
|
<!--
|
|
|
|
|
<div class="form-group" ng-show="repair.subdomain && repair.domain">
|
|
|
|
|
<p>{{ 'app.repairDialog.domainDescription' | tr }}</p>
|
|
|
|
|
<label class="control-label">{{ 'app.repairDialog.location' | tr }}</label>
|
|
|
|
|
<div class="input-group form-inline">
|
2025-03-28 20:54:57 +01:00
|
|
|
<input type="text" class="form-control" ng-model="repair.subdomain" name="location" placeholder="{{ 'Leave empty to use bare domain' }}">
|
2025-03-22 11:19:06 +01:00
|
|
|
|
|
|
|
|
<div class="input-group-btn">
|
|
|
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
|
|
|
|
<span>{{ '.' + repair.domain.domain }}</span>
|
|
|
|
|
<span class="caret"></span>
|
|
|
|
|
</button>
|
|
|
|
|
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
|
|
|
|
<li ng-repeat="domain in domains">
|
|
|
|
|
<a href="" ng-click="repair.domain = domain">{{ domain.domain }}</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div ng-show="repair.aliasDomains.length">
|
|
|
|
|
<p ng-repeat="aliasDomain in repair.aliasDomains">
|
|
|
|
|
<label class="control-label"><input type="checkbox" ng-model="aliasDomain.enabled">
|
|
|
|
|
{{ aliasDomain.subdomain + (!aliasDomain.subdomain ? '' : '.') + aliasDomain.domain.domain }}
|
|
|
|
|
</label>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div ng-show="repair.redirectDomains.length">
|
|
|
|
|
<p ng-repeat="redirectDomain in repair.redirectDomains">
|
|
|
|
|
<label class="control-label"><input type="checkbox" ng-model="redirectDomain.enabled">
|
|
|
|
|
{{ redirectDomain.subdomain + (!redirectDomain.subdomain ? '' : '.') + redirectDomain.domain.domain }}
|
|
|
|
|
</label>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
-->
|
|
|
|
|
</fieldset>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|