Schedule time of day when creating config (#85)
This commit is contained in:
parent
f58bc801fc
commit
ff77a92108
@ -12,6 +12,7 @@
|
||||
"@xstate/fsm": "^1.6.2",
|
||||
"axios": "^0.22.0",
|
||||
"color": "^4.0.1",
|
||||
"cron-parser": "^4.2.1",
|
||||
"lit": "^2.0.0",
|
||||
"path-parser": "^6.1.0",
|
||||
"tailwindcss": "^2.2.16"
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { msg, localized } from "@lit/localize";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronParser from "cron-parser";
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import { getLocaleTimeZone } from "../../utils/localization";
|
||||
|
||||
type CrawlTemplate = any; // TODO
|
||||
|
||||
const initialValues = {
|
||||
name: `Example crawl ${Date.now()}`, // TODO remove placeholder
|
||||
runNow: true,
|
||||
schedule: "@weekly",
|
||||
// crawlTimeoutMinutes: 0,
|
||||
seedUrls: "",
|
||||
scopeType: "prefix",
|
||||
// limit: 0,
|
||||
};
|
||||
const hours = Array.from({ length: 12 }).map((x, i) => ({
|
||||
value: i + 1,
|
||||
label: `${i + 1}`,
|
||||
}));
|
||||
const minutes = Array.from({ length: 60 }).map((x, i) => ({
|
||||
value: i,
|
||||
label: `${i}`.padStart(2, "0"),
|
||||
}));
|
||||
|
||||
@localized()
|
||||
export class CrawlTemplates extends LiteElement {
|
||||
@ -31,7 +40,27 @@ export class CrawlTemplates extends LiteElement {
|
||||
crawlTemplates?: CrawlTemplate[];
|
||||
|
||||
@state()
|
||||
isRunNow: boolean = initialValues.runNow;
|
||||
private isRunNow: boolean = initialValues.runNow;
|
||||
|
||||
@state()
|
||||
private scheduleInterval: "" | "daily" | "weekly" | "monthly" = "weekly";
|
||||
|
||||
/** Schedule local time */
|
||||
@state()
|
||||
private scheduleTime: { hour: number; minute: number; period: "AM" | "PM" } =
|
||||
{
|
||||
hour: 12,
|
||||
minute: 0,
|
||||
period: "AM",
|
||||
};
|
||||
|
||||
private get timeZone() {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
|
||||
private get timeZoneShortName() {
|
||||
return getLocaleTimeZone();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.isNew) {
|
||||
@ -42,6 +71,27 @@ export class CrawlTemplates extends LiteElement {
|
||||
}
|
||||
|
||||
private renderNew() {
|
||||
const utcSchedule = this.getUTCSchedule();
|
||||
const nextScheduledCrawlMessage = this.scheduleInterval
|
||||
? msg(html`Next scheduled crawl:
|
||||
<sl-format-date
|
||||
date="${cronParser
|
||||
.parseExpression(utcSchedule, {
|
||||
utc: true,
|
||||
})
|
||||
.next()
|
||||
.toString()}"
|
||||
weekday="long"
|
||||
month="long"
|
||||
day="numeric"
|
||||
year="numeric"
|
||||
hour="numeric"
|
||||
minute="numeric"
|
||||
time-zone-name="short"
|
||||
time-zone=${this.timeZone}
|
||||
></sl-format-date>`)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<h2 class="text-xl font-bold">${msg("New Crawl Template")}</h2>
|
||||
<p>
|
||||
@ -69,33 +119,96 @@ export class CrawlTemplates extends LiteElement {
|
||||
required
|
||||
></sl-input>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<!-- TODO schedule time -->
|
||||
<div>
|
||||
<sl-select
|
||||
name="schedule"
|
||||
label=${msg("Schedule")}
|
||||
value=${initialValues.schedule}
|
||||
>
|
||||
<sl-menu-item value="">None</sl-menu-item>
|
||||
<sl-menu-item value="@daily">Daily</sl-menu-item>
|
||||
<sl-menu-item value="@weekly">Weekly</sl-menu-item>
|
||||
<sl-menu-item value="@monthly">Monthly</sl-menu-item>
|
||||
</sl-select>
|
||||
<div>
|
||||
<div class="flex items-end">
|
||||
<div class="pr-2 flex-1">
|
||||
<sl-select
|
||||
name="schedule"
|
||||
label=${msg("Schedule")}
|
||||
value=${this.scheduleInterval}
|
||||
@sl-select=${(e: any) =>
|
||||
(this.scheduleInterval = e.target.value)}
|
||||
>
|
||||
<sl-menu-item value="">${msg("None")}</sl-menu-item>
|
||||
<sl-menu-item value="daily">${msg("Daily")}</sl-menu-item>
|
||||
<sl-menu-item value="weekly"
|
||||
>${msg("Weekly")}</sl-menu-item
|
||||
>
|
||||
<sl-menu-item value="monthly"
|
||||
>${msg("Monthly")}</sl-menu-item
|
||||
>
|
||||
</sl-select>
|
||||
</div>
|
||||
<div class="grid grid-flow-col gap-2 items-center">
|
||||
<span class="px-1">${msg("at")}</span>
|
||||
<sl-select
|
||||
name="scheduleHour"
|
||||
value=${this.scheduleTime.hour}
|
||||
class="w-24"
|
||||
?disabled=${!this.scheduleInterval}
|
||||
@sl-select=${(e: any) =>
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
hour: +e.target.value,
|
||||
})}
|
||||
>
|
||||
${hours.map(
|
||||
({ value, label }) =>
|
||||
html`<sl-menu-item value=${value}
|
||||
>${label}</sl-menu-item
|
||||
>`
|
||||
)}
|
||||
</sl-select>
|
||||
<span>:</span>
|
||||
<sl-select
|
||||
name="scheduleMinute"
|
||||
value=${this.scheduleTime.minute}
|
||||
class="w-24"
|
||||
?disabled=${!this.scheduleInterval}
|
||||
@sl-select=${(e: any) =>
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
minute: +e.target.value,
|
||||
})}
|
||||
>
|
||||
${minutes.map(
|
||||
({ value, label }) =>
|
||||
html`<sl-menu-item value=${value}
|
||||
>${label}</sl-menu-item
|
||||
>`
|
||||
)}
|
||||
</sl-select>
|
||||
<sl-select
|
||||
value="AM"
|
||||
class="w-24"
|
||||
?disabled=${!this.scheduleInterval}
|
||||
@sl-select=${(e: any) =>
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
period: e.target.value,
|
||||
})}
|
||||
>
|
||||
<sl-menu-item value="AM"
|
||||
>${msg("AM", { desc: "Time AM/PM" })}</sl-menu-item
|
||||
>
|
||||
<sl-menu-item value="PM"
|
||||
>${msg("PM", { desc: "Time AM/PM" })}</sl-menu-item
|
||||
>
|
||||
</sl-select>
|
||||
<span class="px-1">${this.timeZoneShortName}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500 mt-1">
|
||||
${nextScheduledCrawlMessage || msg("No crawls scheduled")}
|
||||
</div>
|
||||
<!-- <div>
|
||||
<btrix-input
|
||||
name="scheduleTime"
|
||||
type="time"
|
||||
></btrix-input>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<sl-switch
|
||||
name="runNow"
|
||||
?checked=${initialValues.runNow}
|
||||
@sl-change=${(e: any) => (this.isRunNow = e.target.checked)}
|
||||
>${msg("Run immediately")}</sl-switch
|
||||
>${msg("Run immediately on save")}</sl-switch
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -160,17 +273,22 @@ export class CrawlTemplates extends LiteElement {
|
||||
</section>
|
||||
|
||||
<div class="col-span-4 p-4 md:p-8 text-center">
|
||||
${this.isRunNow
|
||||
? html`
|
||||
<p class="text-sm mb-3">
|
||||
${msg("A crawl will start immediately on save.")}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<sl-button type="primary" submit
|
||||
>${msg("Save Crawl Template")}</sl-button
|
||||
>
|
||||
|
||||
${this.isRunNow || this.scheduleInterval
|
||||
? html`<div class="text-sm text-gray-500 mt-6">
|
||||
${this.isRunNow
|
||||
? html`
|
||||
<p class="mb-2">
|
||||
${msg("A crawl will start immediately on save.")}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
${nextScheduledCrawlMessage}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</sl-form>
|
||||
@ -208,7 +326,7 @@ export class CrawlTemplates extends LiteElement {
|
||||
const seedUrlsStr = formData.get("seedUrls");
|
||||
const params = {
|
||||
name: formData.get("name"),
|
||||
schedule: formData.get("schedule"),
|
||||
schedule: this.getUTCSchedule(),
|
||||
runNow: this.isRunNow,
|
||||
crawlTimeout: crawlTimeoutMinutes ? +crawlTimeoutMinutes * 60 : 0,
|
||||
config: {
|
||||
@ -237,4 +355,42 @@ export class CrawlTemplates extends LiteElement {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schedule as UTC cron job expression
|
||||
* https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#cron-schedule-syntax
|
||||
**/
|
||||
private getUTCSchedule(): string {
|
||||
if (!this.scheduleInterval) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { minute, hour, period } = this.scheduleTime;
|
||||
const localDate = new Date();
|
||||
|
||||
// Convert 12-hr to 24-hr time
|
||||
let periodOffset = 0;
|
||||
|
||||
if (hour === 12) {
|
||||
if (period === "AM") {
|
||||
periodOffset = -12;
|
||||
}
|
||||
} else if (hour === 1) {
|
||||
if (period === "PM") {
|
||||
periodOffset = 12;
|
||||
}
|
||||
}
|
||||
|
||||
localDate.setHours(+hour + periodOffset);
|
||||
localDate.setMinutes(+minute);
|
||||
const dayOfMonth =
|
||||
this.scheduleInterval === "monthly" ? localDate.getUTCDate() : "*";
|
||||
const dayOfWeek =
|
||||
this.scheduleInterval === "weekly" ? localDate.getUTCDay() : "*";
|
||||
const month = "*";
|
||||
|
||||
const schedule = `${localDate.getUTCMinutes()} ${localDate.getUTCHours()} ${dayOfMonth} ${month} ${dayOfWeek}`;
|
||||
|
||||
return schedule;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,9 @@ import(
|
||||
import(
|
||||
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/form/form"
|
||||
);
|
||||
import(
|
||||
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/format-date/format-date"
|
||||
);
|
||||
import(
|
||||
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/icon/icon"
|
||||
);
|
||||
|
||||
@ -14,3 +14,19 @@ export const setLocaleFromUrl = async () => {
|
||||
const locale = url.searchParams.get("locale") || sourceLocale;
|
||||
await setLocale(locale);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get time zone short name from locales
|
||||
* @param locales List of locale codes. Omit for browser default
|
||||
**/
|
||||
export const getLocaleTimeZone = (locales?: string[]) => {
|
||||
const date = new Date();
|
||||
|
||||
return date
|
||||
.toLocaleTimeString(locales || [], {
|
||||
timeZoneName: "short",
|
||||
hour: "2-digit",
|
||||
})
|
||||
.replace(date.toLocaleTimeString([], { hour: "2-digit" }), "")
|
||||
.trim();
|
||||
};
|
||||
|
||||
@ -1758,6 +1758,13 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
cron-parser@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.2.1.tgz#b43205d05ccd5c93b097dae64f3bd811f5993af3"
|
||||
integrity sha512-5sJBwDYyCp+0vU5b7POl8zLWfgV5fOHxlc45FWoWdHecGC7MQHCjx0CHivCMRnGFovghKhhyYM+Zm9DcY5qcHg==
|
||||
dependencies:
|
||||
luxon "^1.28.0"
|
||||
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@ -3560,6 +3567,11 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
luxon@^1.28.0:
|
||||
version "1.28.0"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
|
||||
integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
|
||||
|
||||
make-dir@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user