Add initial repair dialog

This commit is contained in:
Johannes Zellner
2025-03-22 11:19:06 +01:00
parent 8522775569
commit 64b730a22e
4 changed files with 233 additions and 27 deletions

View File

@@ -0,0 +1,187 @@
<script setup>
import { ref, useTemplateRef } from 'vue';
import { Dialog, FormGroup, SingleSelect, TextInput } from 'pankow';
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">
<input type="text" class="form-control" ng-model="repair.subdomain" name="location" placeholder="{{ 'Leave empty to use bare domain' }}" autofocus>
<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>