parent
ee68a2f64e
commit
f30b398fea
@ -1,10 +1,10 @@
|
||||
import type { HTMLTemplateResult } from "lit";
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronstrue from "cronstrue"; // TODO localize
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import { getLocaleTimeZone } from "../../utils/localization";
|
||||
import type { CrawlTemplate } from "./types";
|
||||
import { getUTCSchedule } from "./utils";
|
||||
import "../../components/crawl-scheduler";
|
||||
@ -72,10 +72,16 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<h2 class="text-xl font-bold mb-4 h-7">
|
||||
${this.crawlTemplate?.name ||
|
||||
html`<sl-skeleton class="h-7" style="width: 20em"></sl-skeleton>`}
|
||||
</h2>
|
||||
${this.renderInactiveNotice()}
|
||||
|
||||
<header class="flex justify-between">
|
||||
<h2 class="text-xl font-bold mb-4 md:h-7">
|
||||
${this.crawlTemplate?.name ||
|
||||
html`<sl-skeleton class="h-7" style="width: 20em"></sl-skeleton>`}
|
||||
</h2>
|
||||
|
||||
<div class="flex-0">${this.renderMenu()}</div>
|
||||
</header>
|
||||
|
||||
${this.renderCurrentlyRunningNotice()}
|
||||
|
||||
@ -228,39 +234,43 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
: this.renderReadOnlySchedule()}
|
||||
</div>
|
||||
|
||||
<div class="ml-2">
|
||||
${this.crawlTemplate
|
||||
? html`
|
||||
<sl-button
|
||||
size="small"
|
||||
href=${`/archives/${
|
||||
this.archiveId
|
||||
}/crawl-templates/config/${this.crawlTemplate.id}${
|
||||
this.isEditing ? "" : "?edit=true"
|
||||
}`}
|
||||
@click=${(e: any) => {
|
||||
const hasChanges =
|
||||
this.isEditing && this.editedSchedule;
|
||||
if (
|
||||
!hasChanges ||
|
||||
window.confirm(
|
||||
msg(
|
||||
"You have unsaved schedule changes. Are you sure?"
|
||||
)
|
||||
)
|
||||
) {
|
||||
this.navLink(e);
|
||||
this.editedSchedule = "";
|
||||
} else {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
${this.isEditing ? msg("Cancel") : msg("Edit")}
|
||||
</sl-button>
|
||||
`
|
||||
: html`<sl-skeleton></sl-skeleton>`}
|
||||
</div>
|
||||
${this.crawlTemplate?.inactive
|
||||
? ""
|
||||
: html`
|
||||
<div class="ml-2">
|
||||
${this.crawlTemplate
|
||||
? html`
|
||||
<sl-button
|
||||
size="small"
|
||||
href=${`/archives/${
|
||||
this.archiveId
|
||||
}/crawl-templates/config/${
|
||||
this.crawlTemplate.id
|
||||
}${this.isEditing ? "" : "?edit=true"}`}
|
||||
@click=${(e: any) => {
|
||||
const hasChanges =
|
||||
this.isEditing && this.editedSchedule;
|
||||
if (
|
||||
!hasChanges ||
|
||||
window.confirm(
|
||||
msg(
|
||||
"You have unsaved schedule changes. Are you sure?"
|
||||
)
|
||||
)
|
||||
) {
|
||||
this.navLink(e);
|
||||
this.editedSchedule = "";
|
||||
} else {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
${this.isEditing ? msg("Cancel") : msg("Edit")}
|
||||
</sl-button>
|
||||
`
|
||||
: html`<sl-skeleton></sl-skeleton>`}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -293,6 +303,8 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
@click=${this.navLink}
|
||||
>${msg("View crawl")}</a
|
||||
>`
|
||||
: this.crawlTemplate.inactive
|
||||
? ""
|
||||
: html`<span class="text-0-400 text-sm p-1"
|
||||
>${msg("None")}</span
|
||||
><button
|
||||
@ -342,6 +354,107 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderMenu() {
|
||||
if (!this.crawlTemplate) return;
|
||||
|
||||
const menuItems: HTMLTemplateResult[] = [
|
||||
html`
|
||||
<li
|
||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${() => this.duplicateConfig()}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="files"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Duplicate crawl config")}</span
|
||||
>
|
||||
</li>
|
||||
`,
|
||||
];
|
||||
|
||||
if (this.crawlTemplate.crawlCount && !this.crawlTemplate.inactive) {
|
||||
menuItems.push(html`
|
||||
<li
|
||||
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
// Close dropdown before deleting template
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
|
||||
this.deactivateTemplate();
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="file-earmark-minus"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Deactivate")}</span
|
||||
>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
if (!this.crawlTemplate.crawlCount) {
|
||||
menuItems.push(html`
|
||||
<li
|
||||
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
// Close dropdown before deleting template
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
|
||||
this.deleteTemplate();
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="file-earmark-x"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2">${msg("Delete")}</span>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
return html`
|
||||
<sl-dropdown placement="bottom-end">
|
||||
<sl-icon-button
|
||||
slot="trigger"
|
||||
name="three-dots-vertical"
|
||||
label=${msg("More")}
|
||||
style="font-size: 1rem"
|
||||
></sl-icon-button>
|
||||
|
||||
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
|
||||
${menuItems.map((item: HTMLTemplateResult) => item)}
|
||||
</ul>
|
||||
</sl-dropdown>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderInactiveNotice() {
|
||||
if (this.crawlTemplate?.inactive) {
|
||||
return html`
|
||||
<div class="mb-5">
|
||||
<btrix-alert type="warning">
|
||||
<sl-icon
|
||||
name="exclamation-octagon"
|
||||
class="inline-block align-middle mr-2"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle">
|
||||
${msg("This crawl template is inactive.")}
|
||||
</span>
|
||||
</btrix-alert>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private renderCurrentlyRunningNotice() {
|
||||
if (this.crawlTemplate?.currCrawlId) {
|
||||
return html`
|
||||
@ -409,6 +522,94 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new template using existing template data
|
||||
*/
|
||||
private async duplicateConfig() {
|
||||
if (!this.crawlTemplate) return;
|
||||
|
||||
const config: CrawlTemplate["config"] = {
|
||||
seeds: this.crawlTemplate.config.seeds,
|
||||
scopeType: this.crawlTemplate.config.scopeType,
|
||||
limit: this.crawlTemplate.config.limit,
|
||||
};
|
||||
|
||||
this.navTo(`/archives/${this.archiveId}/crawl-templates/new`, {
|
||||
crawlTemplate: {
|
||||
name: msg(str`${this.crawlTemplate.name} Copy`),
|
||||
config,
|
||||
},
|
||||
});
|
||||
|
||||
this.notify({
|
||||
message: msg(str`Copied crawl configuration to new template.`),
|
||||
type: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
}
|
||||
|
||||
private async deactivateTemplate(): Promise<void> {
|
||||
if (!this.crawlTemplate) return;
|
||||
|
||||
try {
|
||||
await this.apiFetch(
|
||||
`/archives/${this.archiveId}/crawlconfigs/${this.crawlTemplate.id}`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
this.notify({
|
||||
message: msg(
|
||||
html`Deactivated <strong>${this.crawlTemplate.name}</strong>.`
|
||||
),
|
||||
type: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
} catch {
|
||||
this.notify({
|
||||
message: msg("Sorry, couldn't deactivate crawl template at this time."),
|
||||
type: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteTemplate(): Promise<void> {
|
||||
if (!this.crawlTemplate) return;
|
||||
|
||||
const isDeactivating = this.crawlTemplate.crawlCount > 0;
|
||||
|
||||
try {
|
||||
await this.apiFetch(
|
||||
`/archives/${this.archiveId}/crawlconfigs/${this.crawlTemplate.id}`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
this.navTo(`/archives/${this.archiveId}/crawl-templates`);
|
||||
|
||||
this.notify({
|
||||
message: isDeactivating
|
||||
? msg(html`Deactivated <strong>${this.crawlTemplate.name}</strong>.`)
|
||||
: msg(html`Deleted <strong>${this.crawlTemplate.name}</strong>.`),
|
||||
type: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
} catch {
|
||||
this.notify({
|
||||
message: isDeactivating
|
||||
? msg("Sorry, couldn't deactivate crawl template at this time.")
|
||||
: msg("Sorry, couldn't delete crawl template at this time."),
|
||||
type: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async runNow(): Promise<void> {
|
||||
try {
|
||||
const data = await this.apiFetch(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { HTMLTemplateResult } from "lit";
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronParser from "cron-parser";
|
||||
@ -111,66 +112,7 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
${t.name || "?"}
|
||||
</a>
|
||||
|
||||
<sl-dropdown @click=${(e: any) => e.stopPropagation()}>
|
||||
<sl-icon-button
|
||||
slot="trigger"
|
||||
name="three-dots-vertical"
|
||||
label="More"
|
||||
style="font-size: 1rem"
|
||||
></sl-icon-button>
|
||||
|
||||
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
|
||||
<li
|
||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
this.showEditDialog = true;
|
||||
this.selectedTemplateForEdit = t;
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="pencil-square"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Edit crawl schedule")}</span
|
||||
>
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${() => this.duplicateConfig(t)}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="files"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Duplicate crawl config")}</span
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
// Close dropdown before deleting template
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
|
||||
this.deleteTemplate(t);
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="file-earmark-x"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Delete")}</span
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</sl-dropdown>
|
||||
${this.renderCardMenu(t)}
|
||||
</header>
|
||||
|
||||
<div class="px-3 pb-3 flex justify-between items-end text-0-800">
|
||||
@ -276,30 +218,7 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
>`}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="text-xs border rounded px-2 h-7 ${this
|
||||
.runningCrawlsMap[t.id]
|
||||
? "bg-purple-50"
|
||||
: "bg-white"} border-purple-200 hover:border-purple-500 text-purple-600 transition-colors"
|
||||
@click=${(e: any) => {
|
||||
e.stopPropagation();
|
||||
this.runningCrawlsMap[t.id]
|
||||
? this.navTo(
|
||||
`/archives/${this.archiveId}/crawls/crawl/${
|
||||
this.runningCrawlsMap[t.id]
|
||||
}`
|
||||
)
|
||||
: this.runNow(t);
|
||||
}}
|
||||
>
|
||||
<span class="whitespace-nowrap">
|
||||
${this.runningCrawlsMap[t.id]
|
||||
? msg("View crawl")
|
||||
: msg("Run now")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
${this.renderCardFooter(t)}
|
||||
</div>
|
||||
</div>`
|
||||
)}
|
||||
@ -327,6 +246,137 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderCardMenu(t: CrawlTemplate) {
|
||||
const menuItems: HTMLTemplateResult[] = [
|
||||
html`
|
||||
<li
|
||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${() => this.duplicateConfig(t)}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="files"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Duplicate crawl config")}</span
|
||||
>
|
||||
</li>
|
||||
`,
|
||||
];
|
||||
|
||||
if (!t.inactive) {
|
||||
menuItems.unshift(html`
|
||||
<li
|
||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
this.showEditDialog = true;
|
||||
this.selectedTemplateForEdit = t;
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="pencil-square"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Edit crawl schedule")}</span
|
||||
>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
if (t.crawlCount && !t.inactive) {
|
||||
menuItems.push(html`
|
||||
<li
|
||||
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
// Close dropdown before deleting template
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
|
||||
this.deactivateTemplate(t);
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="file-earmark-minus"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2"
|
||||
>${msg("Deactivate")}</span
|
||||
>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
if (!t.crawlCount) {
|
||||
menuItems.push(html`
|
||||
<li
|
||||
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
|
||||
role="menuitem"
|
||||
@click=${(e: any) => {
|
||||
// Close dropdown before deleting template
|
||||
e.target.closest("sl-dropdown").hide();
|
||||
|
||||
this.deleteTemplate(t);
|
||||
}}
|
||||
>
|
||||
<sl-icon
|
||||
class="inline-block align-middle px-1"
|
||||
name="file-earmark-x"
|
||||
></sl-icon>
|
||||
<span class="inline-block align-middle pr-2">${msg("Delete")}</span>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
return html`
|
||||
<sl-dropdown @click=${(e: any) => e.stopPropagation()}>
|
||||
<sl-icon-button
|
||||
slot="trigger"
|
||||
name="three-dots-vertical"
|
||||
label=${msg("More")}
|
||||
style="font-size: 1rem"
|
||||
></sl-icon-button>
|
||||
|
||||
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
|
||||
${menuItems.map((item: HTMLTemplateResult) => item)}
|
||||
</ul>
|
||||
</sl-dropdown>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderCardFooter(t: CrawlTemplate) {
|
||||
if (t.inactive) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return html`
|
||||
<div>
|
||||
<button
|
||||
class="text-xs border rounded px-2 h-7 ${this.runningCrawlsMap[t.id]
|
||||
? "bg-purple-50"
|
||||
: "bg-white"} border-purple-200 hover:border-purple-500 text-purple-600 transition-colors"
|
||||
@click=${(e: any) => {
|
||||
e.stopPropagation();
|
||||
this.runningCrawlsMap[t.id]
|
||||
? this.navTo(
|
||||
`/archives/${this.archiveId}/crawls/crawl/${
|
||||
this.runningCrawlsMap[t.id]
|
||||
}`
|
||||
)
|
||||
: this.runNow(t);
|
||||
}}
|
||||
>
|
||||
<span class="whitespace-nowrap">
|
||||
${this.runningCrawlsMap[t.id] ? msg("View crawl") : msg("Run now")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch crawl templates and record running crawls
|
||||
* associated with the crawl templates
|
||||
@ -374,6 +424,34 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async deactivateTemplate(template: CrawlTemplate): Promise<void> {
|
||||
try {
|
||||
await this.apiFetch(
|
||||
`/archives/${this.archiveId}/crawlconfigs/${template.id}`,
|
||||
this.authState!,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
this.notify({
|
||||
message: msg(html`Deactivated <strong>${template.name}</strong>.`),
|
||||
type: "success",
|
||||
icon: "check2-circle",
|
||||
});
|
||||
|
||||
this.crawlTemplates = this.crawlTemplates!.filter(
|
||||
(t) => t.id !== template.id
|
||||
);
|
||||
} catch {
|
||||
this.notify({
|
||||
message: msg("Sorry, couldn't deactivate crawl template at this time."),
|
||||
type: "danger",
|
||||
icon: "exclamation-octagon",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteTemplate(template: CrawlTemplate): Promise<void> {
|
||||
try {
|
||||
await this.apiFetch(
|
||||
|
@ -370,7 +370,7 @@ export class CrawlsList extends LiteElement {
|
||||
<sl-icon-button
|
||||
slot="trigger"
|
||||
name="three-dots"
|
||||
label="More"
|
||||
label=${msg("More")}
|
||||
style="font-size: 1rem"
|
||||
></sl-icon-button>
|
||||
|
||||
|
@ -11,7 +11,7 @@ export type Crawl = {
|
||||
state: string; // "running" | "complete" | "failed" | "partial_complete"
|
||||
scale: number;
|
||||
stats: { done: string; found: string } | null;
|
||||
resources?: { name: string; path: string, hash: string; size: number }[];
|
||||
resources?: { name: string; path: string; hash: string; size: number }[];
|
||||
fileCount?: number;
|
||||
fileSize?: number;
|
||||
completions?: number;
|
||||
@ -32,11 +32,14 @@ export type CrawlTemplate = {
|
||||
name: string;
|
||||
schedule: string;
|
||||
userid: string;
|
||||
userName?: string;
|
||||
userName: string | null;
|
||||
created: string;
|
||||
crawlCount: number;
|
||||
lastCrawlId: string;
|
||||
lastCrawlTime: string;
|
||||
currCrawlId: string;
|
||||
newId: string | null;
|
||||
oldId: string | null;
|
||||
inactive: boolean;
|
||||
config: CrawlConfig;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user