Support for doing both auto backup on backend creation and force export
This commit is contained in:
@@ -269,34 +269,30 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-if="backends.length > 0">
|
||||
<div class="field">
|
||||
<hr>
|
||||
<label class="label has-text-danger" for="force_export">
|
||||
Export current local database state to this backend?
|
||||
<label class="label has-text-danger" for="backup_data">
|
||||
Create backup for this backend data?
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="force_export" type="checkbox" class="switch is-success"
|
||||
v-model="forceExport">
|
||||
<label for="force_export">Yes</label>
|
||||
<input id="backup_data" type="checkbox" class="switch is-success" v-model="backup_data">
|
||||
<label for="backup_data">Yes</label>
|
||||
<p class="help">
|
||||
<span class="icon has-text-danger"><i class="fas fa-exclamation-triangle"></i></span>
|
||||
If this is a new backend, you need to get it in sync with your current database,
|
||||
enabling this option will initiate a one time force export the current local database state to the
|
||||
backend. This will override the backend state to be inline with the local database state.
|
||||
This will run a one time backup for the backend data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" v-else>
|
||||
<div class="field" v-if="backends.length > 0">
|
||||
<hr>
|
||||
<label class="label has-text-danger" for="run_import">
|
||||
Export current local database state to this backend?
|
||||
<label class="label has-text-danger" for="force_export">
|
||||
Force Export local data to this backend?
|
||||
</label>
|
||||
<div class="control">
|
||||
<input id="run_import" type="checkbox" class="switch is-success" v-model="runImport">
|
||||
<label for="run_import">Yes</label>
|
||||
<p class="help">
|
||||
<span class="icon has-text-danger"><i class="fas fa-info-circle"></i></span>
|
||||
Do you want to run a one time import for this backend after adding this backend?
|
||||
<input id="force_export" type="checkbox" class="switch is-success" v-model="force_export">
|
||||
<label for="force_export">Yes</label>
|
||||
<p class="help has-text-danger">
|
||||
<span class="icon"><i class="fas fa-info-circle"></i></span>
|
||||
THIS OPTION WILL OVERRIDE THE BACKEND DATA with locally stored data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -335,7 +331,7 @@ import request from '~/utils/request'
|
||||
import {awaitElement, explode, notification, ucFirst} from '~/utils/index'
|
||||
import {useStorage} from "@vueuse/core";
|
||||
|
||||
const emit = defineEmits(['addBackend', 'forceExport', 'runImport'])
|
||||
const emit = defineEmits(['addBackend', 'backupData', 'forceExport'])
|
||||
|
||||
const props = defineProps({
|
||||
backends: {
|
||||
@@ -377,8 +373,8 @@ const uuidLoading = ref(false)
|
||||
const serversLoading = ref(false)
|
||||
const exposeToken = ref(false)
|
||||
const error = ref()
|
||||
const forceExport = ref(false)
|
||||
const runImport = ref(false)
|
||||
const backup_data = ref(true)
|
||||
const force_export = ref(false)
|
||||
|
||||
const isLimited = ref(false)
|
||||
const accessTokenResponse = ref({})
|
||||
@@ -674,16 +670,15 @@ const addBackend = async () => {
|
||||
|
||||
notification('success', 'Information', `Backend ${backend.value.name} added successfully.`)
|
||||
|
||||
let event
|
||||
if (true === Boolean(forceExport?.value ?? false)) {
|
||||
event = 'forceExport'
|
||||
} else if (true === Boolean(runImport?.value ?? false)) {
|
||||
event = 'runImport'
|
||||
} else {
|
||||
event = 'addBackend'
|
||||
if (true === Boolean(backup_data?.value ?? false)) {
|
||||
emit('backupData', backend)
|
||||
}
|
||||
|
||||
emit(event, backend)
|
||||
if (true === Boolean(force_export?.value ?? false)) {
|
||||
emit('forceExport', backend)
|
||||
}
|
||||
|
||||
emit('addBackend')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@
|
||||
</div>
|
||||
|
||||
<div class="column is-12" v-if="toggleForm">
|
||||
<BackendAdd @forceExport="e => handleEvents('forceExport', e)" :backends="backends"
|
||||
@runImport="e => handleEvents('runImport', e)" @addBackend="e => handleEvents('addBackend', e)"/>
|
||||
<BackendAdd @backupData="e => handleEvents('backupData', e)" :backends="backends"
|
||||
@forceExport="e => handleEvents('forceExport', e)"
|
||||
@addBackend="e => handleEvents('addBackend', e)"/>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="column is-12" v-if="backends.length<1">
|
||||
@@ -188,7 +189,7 @@ import 'assets/css/bulma-switch.css'
|
||||
import moment from 'moment'
|
||||
import request from '~/utils/request'
|
||||
import BackendAdd from '~/components/BackendAdd'
|
||||
import {ag, copyText, makeConsoleCommand, notification, r, TOOLTIP_DATE_FORMAT} from '~/utils/index'
|
||||
import {ag, copyText, makeConsoleCommand, notification, queue_event, r, TOOLTIP_DATE_FORMAT} from '~/utils/index'
|
||||
import {useStorage} from '@vueuse/core'
|
||||
import Message from '~/components/Message'
|
||||
|
||||
@@ -206,31 +207,31 @@ const usefulCommands = {
|
||||
export_now: {
|
||||
id: 1,
|
||||
title: "Run normal export.",
|
||||
command: 'state:export -v -s {name}',
|
||||
command: 'state:export -v -u {user} -s {name}',
|
||||
state_key: 'export.enabled',
|
||||
},
|
||||
import_now: {
|
||||
id: 2,
|
||||
title: "Run normal import.",
|
||||
command: 'state:import -v -s {name}',
|
||||
command: 'state:import -v -u {user} -s {name}',
|
||||
state_key: 'import.enabled'
|
||||
},
|
||||
force_export: {
|
||||
id: 3,
|
||||
title: "Force export local play state to this backend.",
|
||||
command: 'state:export -fi -v -s {name}',
|
||||
command: 'state:export -fi -v -u {user} -s {name}',
|
||||
state_key: 'export.enabled',
|
||||
},
|
||||
backup_now: {
|
||||
id: 4,
|
||||
title: "Backup this backend play state.",
|
||||
command: "state:backup -v -s {name} --file '{date}.manual_{name}.json'",
|
||||
command: "state:backup -v -u {user} -s {name} --file '{date}.manual_{name}.json'",
|
||||
state_key: 'import.enabled',
|
||||
},
|
||||
metadata_only: {
|
||||
id: 5,
|
||||
title: "Import this backend metadata.",
|
||||
command: "state:import -v --metadata-only -s {name}",
|
||||
command: "state:import -v --metadata-only -u {user} -s {name}",
|
||||
state_key: 'import.enabled',
|
||||
},
|
||||
}
|
||||
@@ -247,7 +248,7 @@ const forwardCommand = async backend => {
|
||||
date: moment().format('YYYYMMDD'),
|
||||
}
|
||||
|
||||
await navigateTo(makeConsoleCommand(r(usefulCommands[index].command, {...backend, ...util})));
|
||||
await navigateTo(makeConsoleCommand(r(usefulCommands[index].command, {...backend, ...util, user: api_user.value})));
|
||||
}
|
||||
|
||||
const loadContent = async () => {
|
||||
@@ -285,13 +286,48 @@ const updateValue = async (backend, key, newValue) => {
|
||||
|
||||
const handleEvents = async (event, backend) => {
|
||||
switch (event) {
|
||||
case 'forceExport':
|
||||
notification('warning', 'Warning', `We are going to sync '${backend.value.name}' play state to match the current local database.`, 10000)
|
||||
await navigateTo(makeConsoleCommand(`state:export -fi -v -s ${backend.value.name}`, true))
|
||||
case 'backupData':
|
||||
try {
|
||||
const backup_status = await queue_event('run_console', {
|
||||
command: 'state:backup',
|
||||
args: [
|
||||
'-v',
|
||||
'--user',
|
||||
api_user.value,
|
||||
'--select-backend',
|
||||
backend.value.name,
|
||||
'--file',
|
||||
'{user}.{backend}.{date}.initial_backup.json',
|
||||
]
|
||||
})
|
||||
console.log(backup_status);
|
||||
|
||||
notification('info', 'Info', `We are going to initiate a backup for '${backend.value.name}' in little bit.`, 5000)
|
||||
} catch (e) {
|
||||
notification('error', 'Error', `Failed to queue backup request. ${e.message}`)
|
||||
}
|
||||
break
|
||||
case 'runImport':
|
||||
notification('info', 'Info', `We are going to import '${backend.value.name}' play state to the local database.`, 10000)
|
||||
await navigateTo(makeConsoleCommand(`state:import -v -s ${backend.value.name}`, true))
|
||||
case 'forceExport':
|
||||
try {
|
||||
const export_status = await queue_event('run_console', {
|
||||
command: 'state:export',
|
||||
args: [
|
||||
'-fi',
|
||||
'-v',
|
||||
'--user',
|
||||
api_user.value,
|
||||
'--dry-run',
|
||||
'--select-backend',
|
||||
backend.value.name,
|
||||
]
|
||||
}, 180)
|
||||
|
||||
console.log(export_status);
|
||||
|
||||
notification('info', 'Info', `Soon we are going to force export the local data to '${backend.value.name}'.`, 5000)
|
||||
} catch (e) {
|
||||
notification('error', 'Error', `Failed to queue force export request. ${e.message}`)
|
||||
}
|
||||
break
|
||||
case 'addBackend':
|
||||
toggleForm.value = false
|
||||
@@ -299,4 +335,5 @@ const handleEvents = async (event, backend) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -253,7 +253,7 @@ const makeGUIDLink = (type, source, guid, data) => {
|
||||
|
||||
const link = ag(guid_links, `${type}.${source}`, null)
|
||||
|
||||
return null == link ? '' : r(link, { _guid: guid, ...toRaw(data) })
|
||||
return null == link ? '' : r(link, {_guid: guid, ...toRaw(data)})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,7 +339,7 @@ const makeSearchLink = (type, query) => {
|
||||
* @param detail
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const dEvent = (eventName, detail = {}) => window.dispatchEvent(new CustomEvent(eventName, { detail }))
|
||||
const dEvent = (eventName, detail = {}) => window.dispatchEvent(new CustomEvent(eventName, {detail}))
|
||||
|
||||
/**
|
||||
* Make name
|
||||
@@ -358,7 +358,7 @@ const makeName = (item, asMovie = false) => {
|
||||
const type = ag(item, 'type', 'movie');
|
||||
|
||||
if (['show', 'movie'].includes(type) || asMovie) {
|
||||
return r('{title} ({year})', { title, year })
|
||||
return r('{title} ({year})', {title, year})
|
||||
}
|
||||
|
||||
return r('{title} ({year}) - {season}x{episode}', {
|
||||
@@ -470,7 +470,7 @@ const parse_api_response = async r => {
|
||||
try {
|
||||
return await r.json()
|
||||
} catch (e) {
|
||||
return { error: { code: r.status, message: r.statusText } }
|
||||
return {error: {code: r.status, message: r.statusText}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,6 +493,39 @@ const goto_history_item = async item => {
|
||||
await navigateTo(`/history/${item.item_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue event.
|
||||
*
|
||||
* @param {string} event The event name.
|
||||
* @param {object} event_data The event data.
|
||||
* @param {int} delay delay running the event in XXX seconds.
|
||||
* @param {object} opts additional options.
|
||||
*
|
||||
* @returns {Promise<number>} The status code of the response.
|
||||
*/
|
||||
const queue_event = async (event, event_data = {}, delay = 0, opts = {}) => {
|
||||
let reqData = {event}
|
||||
if (event_data) {
|
||||
reqData.event_data = event_data
|
||||
}
|
||||
|
||||
delay = parseInt(delay)
|
||||
|
||||
if (0 !== delay) {
|
||||
reqData.DELAY_BY = delay
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
reqData = {...reqData, ...opts}
|
||||
}
|
||||
|
||||
const resp = await request(`/system/events`, {
|
||||
method: 'POST', body: JSON.stringify(reqData)
|
||||
})
|
||||
|
||||
return resp.status
|
||||
}
|
||||
|
||||
export {
|
||||
r,
|
||||
ag_set,
|
||||
@@ -515,5 +548,6 @@ export {
|
||||
explode,
|
||||
basename,
|
||||
parse_api_response,
|
||||
goto_history_item
|
||||
goto_history_item,
|
||||
queue_event
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@ use App\API\Backends\Index;
|
||||
use App\Command;
|
||||
use App\Libs\Attributes\Route\Cli;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Database\DatabaseInterface as iDB;
|
||||
use App\Libs\Entity\StateEntity;
|
||||
use App\Libs\Extends\ConsoleOutput;
|
||||
use App\Libs\Extends\Date;
|
||||
use App\Libs\UserContext;
|
||||
use App\Libs\Mappers\ImportInterface as iImport;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\UserContext;
|
||||
use Cron\CronExpression;
|
||||
use LimitIterator;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use RuntimeException;
|
||||
use SplFileObject;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use Symfony\Component\Console\Input\InputInterface as iInput;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface as iOutput;
|
||||
@@ -57,7 +57,13 @@ final class ReportCommand extends Command
|
||||
{
|
||||
$this->setName(self::ROUTE)
|
||||
->setDescription('Show basic information for diagnostics.')
|
||||
->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, 'Show last X number of log lines.', self::DEFAULT_LIMIT)
|
||||
->addOption(
|
||||
'limit',
|
||||
'l',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Show last X number of log lines.',
|
||||
self::DEFAULT_LIMIT
|
||||
)
|
||||
->addOption(
|
||||
'include-db-sample',
|
||||
's',
|
||||
@@ -153,7 +159,7 @@ final class ReportCommand extends Command
|
||||
if (count($usersContext) > 1) {
|
||||
$output->writeln(
|
||||
r('Users? {users}' . PHP_EOL, [
|
||||
'users' => count($usersContext) >= 1 ? implode(', ', array_keys($usersContext)) : 'None',
|
||||
'users' => implode(', ', array_keys($usersContext)),
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -392,12 +398,12 @@ final class ReportCommand extends Command
|
||||
private function handleLog(iOutput $output, string $type, string|int $date, int|string $limit): void
|
||||
{
|
||||
$logFile = Config::get('tmpDir') . '/logs/' . r(
|
||||
'{type}.{date}.log',
|
||||
[
|
||||
'{type}.{date}.log',
|
||||
[
|
||||
'type' => $type,
|
||||
'date' => $date
|
||||
]
|
||||
);
|
||||
);
|
||||
|
||||
$output->writeln(r('[ <value>{logFile}</value> ]' . PHP_EOL, ['logFile' => $logFile]));
|
||||
|
||||
|
||||
@@ -69,10 +69,13 @@ final class TasksCommand extends Command
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$tasksName = implode(', ', array_map(
|
||||
fn ($val) => '<comment>' . strtoupper($val) . '</comment>',
|
||||
array_keys(Config::get('tasks.list', []))
|
||||
));
|
||||
$tasksName = implode(
|
||||
', ',
|
||||
array_map(
|
||||
fn($val) => '<comment>' . strtoupper($val) . '</comment>',
|
||||
array_keys(Config::get('tasks.list', []))
|
||||
)
|
||||
);
|
||||
|
||||
$this->setName(self::ROUTE)
|
||||
->addOption('run', null, InputOption::VALUE_NONE, 'Run scheduled tasks.')
|
||||
@@ -194,7 +197,8 @@ final class TasksCommand extends Command
|
||||
$eventName = $event->getEvent()->event;
|
||||
|
||||
switch ($eventName) {
|
||||
case self::NAME: {
|
||||
case self::NAME:
|
||||
{
|
||||
if (null === ($name = ag($event->getData(), 'name'))) {
|
||||
$event->addLog(r('No task name was specified.'));
|
||||
return $event;
|
||||
@@ -202,11 +206,14 @@ final class TasksCommand extends Command
|
||||
|
||||
$task = self::getTasks($name);
|
||||
if (empty($task)) {
|
||||
$event->addLog(r("Invalid task '{name}'. There are no task with that name registered.", ['name' => $name]));
|
||||
$event->addLog(
|
||||
r("Invalid task '{name}'. There are no task with that name registered.", ['name' => $name])
|
||||
);
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
case self::CNAME: {
|
||||
case self::CNAME:
|
||||
{
|
||||
if (null === ag($event->getData(), 'command')) {
|
||||
$event->addLog(r('No command name was specified.'));
|
||||
return $event;
|
||||
@@ -223,9 +230,9 @@ final class TasksCommand extends Command
|
||||
$input->setOption('save-log', true);
|
||||
$input->setOption('live', false);
|
||||
|
||||
$this->clear = fn () => $event->clearLogs();
|
||||
$this->clear = fn() => $event->clearLogs();
|
||||
|
||||
$this->save = fn () => $this->eventsRepo->save($event->getEvent());
|
||||
$this->save = fn() => $this->eventsRepo->save($event->getEvent());
|
||||
|
||||
$this->writer = function ($msg) use (&$event) {
|
||||
static $lastSave = null;
|
||||
@@ -260,8 +267,9 @@ final class TasksCommand extends Command
|
||||
'name' => $eventName,
|
||||
'code' => $exitCode,
|
||||
]));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (self::NAME === $eventName && !empty($task)) {
|
||||
$event->addLog(r("Task: Run '{command}'.", ['command' => ag($task, 'command')]));
|
||||
$exitCode = $this->runTask($task, $input, Container::get(iOutput::class));
|
||||
$event->addLog(r("Task: End '{command}' (Exit Code: {code})", [
|
||||
|
||||
Reference in New Issue
Block a user