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",
|
"@xstate/fsm": "^1.6.2",
|
||||||
"axios": "^0.22.0",
|
"axios": "^0.22.0",
|
||||||
"color": "^4.0.1",
|
"color": "^4.0.1",
|
||||||
|
"cron-parser": "^4.2.1",
|
||||||
"lit": "^2.0.0",
|
"lit": "^2.0.0",
|
||||||
"path-parser": "^6.1.0",
|
"path-parser": "^6.1.0",
|
||||||
"tailwindcss": "^2.2.16"
|
"tailwindcss": "^2.2.16"
|
||||||
|
|||||||
@ -1,20 +1,29 @@
|
|||||||
import { state, property } from "lit/decorators.js";
|
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 type { AuthState } from "../../utils/AuthService";
|
||||||
import LiteElement, { html } from "../../utils/LiteElement";
|
import LiteElement, { html } from "../../utils/LiteElement";
|
||||||
|
import { getLocaleTimeZone } from "../../utils/localization";
|
||||||
|
|
||||||
type CrawlTemplate = any; // TODO
|
type CrawlTemplate = any; // TODO
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
name: `Example crawl ${Date.now()}`, // TODO remove placeholder
|
name: `Example crawl ${Date.now()}`, // TODO remove placeholder
|
||||||
runNow: true,
|
runNow: true,
|
||||||
schedule: "@weekly",
|
|
||||||
// crawlTimeoutMinutes: 0,
|
// crawlTimeoutMinutes: 0,
|
||||||
seedUrls: "",
|
seedUrls: "",
|
||||||
scopeType: "prefix",
|
scopeType: "prefix",
|
||||||
// limit: 0,
|
// 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()
|
@localized()
|
||||||
export class CrawlTemplates extends LiteElement {
|
export class CrawlTemplates extends LiteElement {
|
||||||
@ -31,7 +40,27 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
crawlTemplates?: CrawlTemplate[];
|
crawlTemplates?: CrawlTemplate[];
|
||||||
|
|
||||||
@state()
|
@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() {
|
render() {
|
||||||
if (this.isNew) {
|
if (this.isNew) {
|
||||||
@ -42,6 +71,27 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderNew() {
|
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`
|
return html`
|
||||||
<h2 class="text-xl font-bold">${msg("New Crawl Template")}</h2>
|
<h2 class="text-xl font-bold">${msg("New Crawl Template")}</h2>
|
||||||
<p>
|
<p>
|
||||||
@ -69,33 +119,96 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
required
|
required
|
||||||
></sl-input>
|
></sl-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end">
|
<div>
|
||||||
<!-- TODO schedule time -->
|
<div class="flex items-end">
|
||||||
<div>
|
<div class="pr-2 flex-1">
|
||||||
<sl-select
|
<sl-select
|
||||||
name="schedule"
|
name="schedule"
|
||||||
label=${msg("Schedule")}
|
label=${msg("Schedule")}
|
||||||
value=${initialValues.schedule}
|
value=${this.scheduleInterval}
|
||||||
>
|
@sl-select=${(e: any) =>
|
||||||
<sl-menu-item value="">None</sl-menu-item>
|
(this.scheduleInterval = e.target.value)}
|
||||||
<sl-menu-item value="@daily">Daily</sl-menu-item>
|
>
|
||||||
<sl-menu-item value="@weekly">Weekly</sl-menu-item>
|
<sl-menu-item value="">${msg("None")}</sl-menu-item>
|
||||||
<sl-menu-item value="@monthly">Monthly</sl-menu-item>
|
<sl-menu-item value="daily">${msg("Daily")}</sl-menu-item>
|
||||||
</sl-select>
|
<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>
|
||||||
<!-- <div>
|
|
||||||
<btrix-input
|
|
||||||
name="scheduleTime"
|
|
||||||
type="time"
|
|
||||||
></btrix-input>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<sl-switch
|
<sl-switch
|
||||||
name="runNow"
|
name="runNow"
|
||||||
?checked=${initialValues.runNow}
|
?checked=${initialValues.runNow}
|
||||||
@sl-change=${(e: any) => (this.isRunNow = e.target.checked)}
|
@sl-change=${(e: any) => (this.isRunNow = e.target.checked)}
|
||||||
>${msg("Run immediately")}</sl-switch
|
>${msg("Run immediately on save")}</sl-switch
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -160,17 +273,22 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="col-span-4 p-4 md:p-8 text-center">
|
<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
|
<sl-button type="primary" submit
|
||||||
>${msg("Save Crawl Template")}</sl-button
|
>${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>
|
||||||
</div>
|
</div>
|
||||||
</sl-form>
|
</sl-form>
|
||||||
@ -208,7 +326,7 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
const seedUrlsStr = formData.get("seedUrls");
|
const seedUrlsStr = formData.get("seedUrls");
|
||||||
const params = {
|
const params = {
|
||||||
name: formData.get("name"),
|
name: formData.get("name"),
|
||||||
schedule: formData.get("schedule"),
|
schedule: this.getUTCSchedule(),
|
||||||
runNow: this.isRunNow,
|
runNow: this.isRunNow,
|
||||||
crawlTimeout: crawlTimeoutMinutes ? +crawlTimeoutMinutes * 60 : 0,
|
crawlTimeout: crawlTimeoutMinutes ? +crawlTimeoutMinutes * 60 : 0,
|
||||||
config: {
|
config: {
|
||||||
@ -237,4 +355,42 @@ export class CrawlTemplates extends LiteElement {
|
|||||||
console.error(e);
|
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(
|
import(
|
||||||
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/form/form"
|
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/form/form"
|
||||||
);
|
);
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/format-date/format-date"
|
||||||
|
);
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/icon/icon"
|
/* webpackChunkName: "shoelace" */ "@shoelace-style/shoelace/dist/components/icon/icon"
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,3 +14,19 @@ export const setLocaleFromUrl = async () => {
|
|||||||
const locale = url.searchParams.get("locale") || sourceLocale;
|
const locale = url.searchParams.get("locale") || sourceLocale;
|
||||||
await setLocale(locale);
|
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"
|
path-type "^4.0.0"
|
||||||
yaml "^1.10.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:
|
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||||
@ -3560,6 +3567,11 @@ lru-cache@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^4.0.0"
|
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:
|
make-dir@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user