feat: add UI for CRON Scheduler
This commit is contained in:
@@ -45,6 +45,10 @@
|
|||||||
<li :class="{active: route.name === 'logs'}">
|
<li :class="{active: route.name === 'logs'}">
|
||||||
<RouterLink :to="{name: 'logs'}">Журнал событий</RouterLink>
|
<RouterLink :to="{name: 'logs'}">Журнал событий</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li :class="{active: route.name === 'cron'}">
|
||||||
|
<RouterLink :to="{name: 'cron'}">CRON</RouterLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<section class="form-horizontal tab-content">
|
<section class="form-horizontal tab-content">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import LogsView from "@/views/LogsView.vue";
|
|||||||
import FormBuilderView from "@/views/FormBuilderView.vue";
|
import FormBuilderView from "@/views/FormBuilderView.vue";
|
||||||
import CustomersView from "@/views/CustomersView.vue";
|
import CustomersView from "@/views/CustomersView.vue";
|
||||||
import TeleCartPulseView from "@/views/TeleCartPulseView.vue";
|
import TeleCartPulseView from "@/views/TeleCartPulseView.vue";
|
||||||
|
import CronView from "@/views/CronView.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createMemoryHistory(),
|
history: createMemoryHistory(),
|
||||||
@@ -25,6 +26,7 @@ const router = createRouter({
|
|||||||
{path: '/store', name: 'store', component: StoreView},
|
{path: '/store', name: 'store', component: StoreView},
|
||||||
{path: '/telegram', name: 'telegram', component: TelegramView},
|
{path: '/telegram', name: 'telegram', component: TelegramView},
|
||||||
{path: '/texts', name: 'texts', component: TextsView},
|
{path: '/texts', name: 'texts', component: TextsView},
|
||||||
|
{path: '/cron', name: 'cron', component: CronView},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
pulse: {
|
pulse: {
|
||||||
api_key: '',
|
api_key: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cron: {
|
||||||
|
mode: 'disabled',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
108
frontend/admin/src/views/CronView.vue
Normal file
108
frontend/admin/src/views/CronView.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<SettingsItem label="Режим работы планировщика">
|
||||||
|
<template #default>
|
||||||
|
<SelectButton
|
||||||
|
v-model="settings.items.cron.mode"
|
||||||
|
:options="cronModes"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
:allowEmpty="false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #help>
|
||||||
|
<div v-if="settings.items.cron.mode === 'disabled'" class="tw:text-red-600 tw:font-bold">
|
||||||
|
Все фоновые задачи отключены.
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
Рекомендуемый режим. Использует системный планировщик задач Linux.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw:mt-2">
|
||||||
|
<p>
|
||||||
|
<strong>Системный CRON (рекомендуется):</strong> Стабильное выполнение задач по расписанию, независимо от
|
||||||
|
посещаемости сайта. Добавьте команду в CRON для автоматического выполнения каждые 5 минут.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Выключено:</strong> Все фоновые задачи отключены. Планировщик не будет выполнять никаких задач.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
|
<SettingsItem label="Последний запуск CRON">
|
||||||
|
<template #default>
|
||||||
|
<div v-if="lastRunDate" class="tw:text-green-600 tw:font-bold tw:py-2">
|
||||||
|
{{ lastRunDate }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="tw:text-gray-500 tw:py-2">
|
||||||
|
Еще не запускался
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #help>
|
||||||
|
Время последнего успешного выполнения планировщика задач.
|
||||||
|
</template>
|
||||||
|
</SettingsItem>
|
||||||
|
|
||||||
|
<SettingsItem
|
||||||
|
v-if="settings.items.cron.mode === 'system'"
|
||||||
|
label="Команда для CRON"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<InputGroup>
|
||||||
|
<Button icon="fa fa-copy" severity="secondary" @click="copyToClipboard(cronCommand)"/>
|
||||||
|
<InputText readonly :model-value="cronCommand" class="tw:w-full"/>
|
||||||
|
</InputGroup>
|
||||||
|
</template>
|
||||||
|
<template #help>
|
||||||
|
Добавьте эту строку в конфигурацию CRON на вашем сервере (обычно `crontab -e`), чтобы запускать планировщик каждые
|
||||||
|
5 минут.
|
||||||
|
</template>
|
||||||
|
</SettingsItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {computed} from 'vue';
|
||||||
|
import {useSettingsStore} from "@/stores/settings.js";
|
||||||
|
import SettingsItem from "@/components/SettingsItem.vue";
|
||||||
|
import SelectButton from "primevue/selectbutton";
|
||||||
|
import InputText from "primevue/inputtext";
|
||||||
|
import Button from "primevue/button";
|
||||||
|
import InputGroup from 'primevue/inputgroup';
|
||||||
|
import {toastBus} from "@/utils/toastHelper.js";
|
||||||
|
|
||||||
|
const settings = useSettingsStore();
|
||||||
|
|
||||||
|
const cronModes = [
|
||||||
|
{value: 'system', label: 'Системный CRON (Linux)'},
|
||||||
|
{value: 'disabled', label: 'Выключено'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const cronCommand = computed(() => {
|
||||||
|
const cliPath = settings.items.cron?.cli_path;
|
||||||
|
|
||||||
|
return cliPath
|
||||||
|
? `*/5 * * * * php ${cliPath} schedule:run`
|
||||||
|
: 'Путь не определен. Проверьте конфигурацию модуля.';
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastRunDate = computed(() => settings.items.cron?.last_run);
|
||||||
|
|
||||||
|
async function copyToClipboard(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
toastBus.emit('show', {
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Скопировано',
|
||||||
|
detail: 'Команда скопирована в буфер обмена',
|
||||||
|
life: 2000,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
toastBus.emit('show', {
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Ошибка',
|
||||||
|
detail: 'Не удалось скопировать текст',
|
||||||
|
life: 2000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -69,10 +69,19 @@ class SettingsHandler
|
|||||||
'sliders',
|
'sliders',
|
||||||
'mainpage_blocks',
|
'mainpage_blocks',
|
||||||
'pulse',
|
'pulse',
|
||||||
|
'cron',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!isset($data['cron']['mode'])) {
|
||||||
|
$data['cron']['mode'] = 'disabled';
|
||||||
|
}
|
||||||
|
|
||||||
$data['forms'] = [];
|
$data['forms'] = [];
|
||||||
|
|
||||||
|
// Add CRON system details (read-only)
|
||||||
|
$data['cron']['cli_path'] = realpath(DIR_SYSTEM . '../cli.php');
|
||||||
|
$data['cron']['last_run'] = $this->getLastCronRunDate();
|
||||||
|
|
||||||
$forms = $this->builder->newQuery()
|
$forms = $this->builder->newQuery()
|
||||||
->from('telecart_forms')
|
->from('telecart_forms')
|
||||||
->get();
|
->get();
|
||||||
@@ -103,6 +112,12 @@ class SettingsHandler
|
|||||||
|
|
||||||
$this->validate($input);
|
$this->validate($input);
|
||||||
|
|
||||||
|
// Remove dynamic properties before saving
|
||||||
|
if (isset($input['cron'])) {
|
||||||
|
unset($input['cron']['cli_path']);
|
||||||
|
unset($input['cron']['last_run']);
|
||||||
|
}
|
||||||
|
|
||||||
$this->settingsUpdateService->update(
|
$this->settingsUpdateService->update(
|
||||||
Arr::getWithKeys($input, [
|
Arr::getWithKeys($input, [
|
||||||
'app',
|
'app',
|
||||||
@@ -114,6 +129,7 @@ class SettingsHandler
|
|||||||
'sliders',
|
'sliders',
|
||||||
'mainpage_blocks',
|
'mainpage_blocks',
|
||||||
'pulse',
|
'pulse',
|
||||||
|
'cron',
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -145,4 +161,25 @@ class SettingsHandler
|
|||||||
|
|
||||||
return new JsonResponse([], Response::HTTP_ACCEPTED);
|
return new JsonResponse([], Response::HTTP_ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getLastCronRunDate(): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Since we are in SettingsHandler, we already have access to container or we can inject SchedulerService
|
||||||
|
// But SettingsHandler is constructed via DI. Let's add SchedulerService to constructor.
|
||||||
|
// For now, let's use global retrieval via cache if possible, or assume it's injected.
|
||||||
|
// But wait, getLastCronRunDate logic was in controller.
|
||||||
|
// SchedulerService stores last run in cache. We have $this->cache here.
|
||||||
|
|
||||||
|
$lastRunTimestamp = $this->cache->get("scheduler.global_last_run");
|
||||||
|
|
||||||
|
if ($lastRunTimestamp) {
|
||||||
|
return date('d.m.Y H:i:s', (int)$lastRunTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user