2025-01-23 18:36:30 +01:00
|
|
|
<script setup>
|
|
|
|
|
|
2025-01-24 14:00:33 +01:00
|
|
|
import { ref, onMounted, useTemplateRef } from 'vue';
|
|
|
|
|
import { Button, Dialog, ProgressBar, Radiobutton, MultiSelect } from 'pankow';
|
2025-01-24 14:09:30 +01:00
|
|
|
import { prettyLongDate } from 'pankow/utils';
|
|
|
|
|
import { TASK_TYPES } from '../constants.js';
|
2025-01-23 18:36:30 +01:00
|
|
|
import Section from '../components/Section.vue';
|
2025-01-24 14:00:33 +01:00
|
|
|
import UpdaterModel from '../models/UpdaterModel.js';
|
2025-01-24 14:09:30 +01:00
|
|
|
import TasksModel from '../models/TasksModel.js';
|
2025-01-24 14:00:33 +01:00
|
|
|
import DashboardModel from '../models/DashboardModel.js';
|
|
|
|
|
|
2025-01-31 21:02:48 +01:00
|
|
|
const tasksModel = TasksModel.create();
|
|
|
|
|
const updaterModel = UpdaterModel.create();
|
|
|
|
|
const dashboardModel = DashboardModel.create();
|
2025-01-24 14:00:33 +01:00
|
|
|
|
|
|
|
|
// values correspond to cron days
|
|
|
|
|
const cronDays = [
|
|
|
|
|
{ id: 0, name: 'Sunday', value: 0 },
|
|
|
|
|
{ id: 1, name: 'Monday', value: 1 },
|
|
|
|
|
{ id: 2, name: 'Tuesday', value: 2 },
|
|
|
|
|
{ id: 3, name: 'Wednesday', value: 3 },
|
|
|
|
|
{ id: 4, name: 'Thursday', value: 4 },
|
|
|
|
|
{ id: 5, name: 'Friday', value: 5 },
|
|
|
|
|
{ id: 6, name: 'Saturday', value: 6 },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// generates 24h time sets (instead of american 12h) to avoid having to translate everything to locales eg. 12:00
|
|
|
|
|
const cronHours = Array.from({ length: 24 }).map((v, i) => { return { name: (i < 10 ? '0' : '') + i + ':00', value: i, id: i }; });
|
|
|
|
|
|
|
|
|
|
function prettyAutoUpdateSchedule(pattern) {
|
|
|
|
|
if (!pattern) return '';
|
|
|
|
|
const tmp = pattern.split(' ');
|
|
|
|
|
|
|
|
|
|
if (tmp.length === 1) return tmp[0];
|
|
|
|
|
|
|
|
|
|
const hours = tmp[2].split(',');
|
|
|
|
|
const days = tmp[5].split(',');
|
|
|
|
|
const prettyDay = (days.length === 7 || days[0] === '*') ? 'Everyday' : days.map((day) => { return cronDays[parseInt(day, 10)].name.substr(0, 3); }).join(',');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const prettyHour = hours.map((hour) => { return cronHours[parseInt(hour, 10)].name; }).join(',');
|
|
|
|
|
return prettyDay + ' at ' + prettyHour;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Unable to build pattern.', error);
|
|
|
|
|
return 'Custom pattern';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-23 18:36:30 +01:00
|
|
|
|
|
|
|
|
const taskLogsMenu = ref([]);
|
2025-01-24 14:00:33 +01:00
|
|
|
const version = ref('');
|
|
|
|
|
const ubuntuVersion = ref('');
|
|
|
|
|
const currentPattern = ref('');
|
|
|
|
|
const percent = ref(0);
|
|
|
|
|
const message = ref('');
|
|
|
|
|
const updateBusy = ref(false);
|
|
|
|
|
const checkingBusy = ref(false);
|
|
|
|
|
const pendingUpdate = ref(null);
|
|
|
|
|
|
|
|
|
|
const configureDialog = useTemplateRef('configureDialog');
|
|
|
|
|
const configureBusy = ref(false);
|
|
|
|
|
const configureError = ref('');
|
|
|
|
|
const configureType = ref('');
|
|
|
|
|
const configurePattern = ref('');
|
|
|
|
|
const configureDays = ref([]);
|
|
|
|
|
const configureHours = ref([]);
|
|
|
|
|
|
|
|
|
|
async function refreshAutoupdatePattern() {
|
|
|
|
|
const [error, result] = await updaterModel.getAutoupdatePattern();
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
// just keep the UI sane by supporting previous default pattern
|
|
|
|
|
if (result.pattern === '00 30 1,3,5,23 * * *') result.pattern = '00 15 1,3,5,23 * * *';
|
|
|
|
|
|
|
|
|
|
currentPattern.value = result.pattern;
|
|
|
|
|
configurePattern.value = result.pattern;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshInfo() {
|
|
|
|
|
const [error, result] = await updaterModel.info();
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
pendingUpdate.value = result.box || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onShowConfigure() {
|
|
|
|
|
configureType.value = configurePattern.value === 'never' ? 'never' : 'pattern';
|
|
|
|
|
const tmp = currentPattern.value.split(' ');
|
|
|
|
|
const hours = tmp[2] ? tmp[2].split(',') : [];
|
|
|
|
|
const days = tmp[5] ? tmp[5].split(',') : [];
|
|
|
|
|
if (days[0] === '*') configureDays.value = cronDays;
|
|
|
|
|
else configureDays.value = days.map(day => { return cronDays[parseInt(day, 10)]; });
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
configureHours.value = hours.map(hour => { return cronHours[parseInt(hour, 10)]; });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error parsing hour', error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configureDialog.value.open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onSubmitConfigure() {
|
|
|
|
|
let pattern = 'never';
|
|
|
|
|
if (configureType.value === 'pattern') {
|
|
|
|
|
let daysPattern;
|
|
|
|
|
if (configureDays.value.length === 7) daysPattern = '*';
|
|
|
|
|
else daysPattern = configureDays.value.map(d => { return d.value; });
|
|
|
|
|
|
|
|
|
|
let hoursPattern;
|
|
|
|
|
if (configureHours.value.length === 24) hoursPattern = '*';
|
|
|
|
|
else hoursPattern = configureHours.value.map(h => { return h.value; });
|
|
|
|
|
|
|
|
|
|
pattern ='00 00 ' + hoursPattern + ' * * ' + daysPattern;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configureBusy.value = true;
|
|
|
|
|
const [error] = await updaterModel.setAutoupdatePattern(pattern);
|
|
|
|
|
if (error) {
|
|
|
|
|
configureError.value = error.body ? error.body.message : 'Internal error';
|
|
|
|
|
configureBusy.value = false;
|
|
|
|
|
return console.error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await refreshAutoupdatePattern();
|
|
|
|
|
|
|
|
|
|
configureBusy.value = false;
|
|
|
|
|
configureDialog.value.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onShowUpdate() {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onCheck() {
|
|
|
|
|
checkingBusy.value = true;
|
|
|
|
|
|
|
|
|
|
const [error] = await updaterModel.check();
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
await refreshInfo();
|
|
|
|
|
checkingBusy.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-24 14:09:30 +01:00
|
|
|
async function refreshTasks() {
|
|
|
|
|
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_UPDATE);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
taskLogsMenu.value = result.map(t => {
|
|
|
|
|
return {
|
|
|
|
|
icon: 'fa-solid ' + ((!t.active && t.success) ? 'status-active fa-check-circle' : (t.active ? 'fa-circle-notch fa-spin' : 'status-error fa-times-circle')),
|
|
|
|
|
label: prettyLongDate(t.ts),
|
|
|
|
|
action: () => { window.open(`/logs.html?taskId=${t.id}`); }
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-24 14:00:33 +01:00
|
|
|
onMounted(async () => {
|
2025-03-12 13:41:07 +01:00
|
|
|
const [error, result] = await dashboardModel.config();
|
2025-01-24 14:00:33 +01:00
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
version.value = result.version;
|
|
|
|
|
ubuntuVersion.value = result.ubuntuVersion;
|
|
|
|
|
|
|
|
|
|
await refreshInfo();
|
|
|
|
|
await refreshAutoupdatePattern();
|
2025-01-24 14:09:30 +01:00
|
|
|
await refreshTasks();
|
2025-01-24 14:00:33 +01:00
|
|
|
});
|
2025-01-23 18:36:30 +01:00
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div>
|
2025-01-24 14:00:33 +01:00
|
|
|
<Dialog ref="configureDialog"
|
|
|
|
|
:title="$t('settings.updateScheduleDialog.title')"
|
|
|
|
|
:confirm-label="$t('main.dialog.save')"
|
|
|
|
|
:reject-label="$t('main.dialog.cancel')"
|
|
|
|
|
reject-style="secondary"
|
|
|
|
|
@confirm="onSubmitConfigure()"
|
|
|
|
|
>
|
|
|
|
|
<p v-html="$t('settings.updateScheduleDialog.description')"></p>
|
|
|
|
|
|
|
|
|
|
<p class="has-error text-center" v-show="configureError">{{ configureError }}</p>
|
|
|
|
|
|
|
|
|
|
<Radiobutton v-model="configureType" value="never" :label="$t('settings.updateScheduleDialog.disableCheckbox')" />
|
|
|
|
|
<br/>
|
|
|
|
|
<Radiobutton v-model="configureType" value="pattern" :label="$t('settings.updateScheduleDialog.enableCheckbox')" />
|
|
|
|
|
|
|
|
|
|
<div v-show="configureType === 'pattern'" style="display: flex; gap: 10px;">
|
|
|
|
|
<div>{{ $t('settings.updateScheduleDialog.days') }}: <MultiSelect v-model="configureDays" :options="cronDays" option-label="name" /></div>
|
|
|
|
|
<div>{{ $t('settings.updateScheduleDialog.hours') }}: <MultiSelect v-model="configureHours" :options="cronHours" option-label="name" /></div>
|
|
|
|
|
</div>
|
2025-01-24 14:09:30 +01:00
|
|
|
<!-- TODO <span class="label label-danger" ng-show="updateSchedule.type === 'pattern' && !updateSchedule.isScheduleValid()">{{ 'settings.updateScheduleDialog.selectOne' | tr }}</span> -->
|
2025-01-24 14:00:33 +01:00
|
|
|
</Dialog>
|
|
|
|
|
|
2025-01-23 18:36:30 +01:00
|
|
|
<Section :title="$t('settings.updates.title')">
|
|
|
|
|
<template #header-buttons>
|
|
|
|
|
<Button tool icon="fas fa-align-left" v-tooltip="$t('settings.updates.showLogsAction')" :menu="taskLogsMenu" :disabled="!taskLogsMenu.length"/>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<p v-html="$t('settings.updates.description')"></p>
|
2025-01-24 14:00:33 +01:00
|
|
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
<div class="info-label">{{ $t('settings.updates.version') }}</div>
|
|
|
|
|
<div class="info-value">v{{ version }} ({{ ubuntuVersion }})
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
<div class="info-label">{{ $t('settings.updates.schedule') }}</div>
|
|
|
|
|
<div class="info-value actionable" @click="onShowConfigure()">
|
|
|
|
|
<span v-show="currentPattern !== 'never'">{{ prettyAutoUpdateSchedule(currentPattern) }}</span>
|
|
|
|
|
<span v-show="currentPattern === 'never'">{{ $t('settings.updates.disabled') }}</span>
|
2025-01-24 14:09:30 +01:00
|
|
|
<i class="fa-solid fa-edit text-small" style="margin-left: 10px;"></i>
|
2025-01-24 14:00:33 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-01-24 17:38:18 +01:00
|
|
|
<ProgressBar :value="percent" v-if="updateBusy" />
|
|
|
|
|
<p v-if="updateBusy">{{ message }}</p>
|
2025-01-24 14:00:33 +01:00
|
|
|
|
|
|
|
|
<Button danger v-show="updateBusy" @click="onStop()">{{ $t('settings.updates.stopUpdateAction') }}</Button>
|
|
|
|
|
<Button v-show="!pendingUpdate" :disabled="checkingBusy" :loading="checkingBusy" @click="onCheck()">{{ $t('settings.updates.checkForUpdatesAction') }}</Button>
|
|
|
|
|
<Button :danger="(pendingUpdate && pendingUpdate.unstable) ? true : undefined" :success="(pendingUpdate && !pendingUpdate.unstable) ? true : undefined" v-show="pendingUpdate && pendingUpdate.version !== version && !updateBusy" @click="onShowUpdate()">{{ $t('settings.updates.updateAvailableAction') }}</Button>
|
2025-01-23 18:36:30 +01:00
|
|
|
</Section>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|