Re-run crawl from detail view + handle inactive crawl template (#268)
closes #253
This commit is contained in:
parent
d144591dbf
commit
92292591ad
@ -7,8 +7,7 @@ import { RelativeDuration } from "../../components/relative-duration";
|
|||||||
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 { CopyButton } from "../../components/copy-button";
|
import { CopyButton } from "../../components/copy-button";
|
||||||
import type { Crawl } from "./types";
|
import type { Crawl, CrawlTemplate } from "./types";
|
||||||
import { times } from "lodash";
|
|
||||||
|
|
||||||
type SectionName = "overview" | "watch" | "replay" | "files" | "logs";
|
type SectionName = "overview" | "watch" | "replay" | "files" | "logs";
|
||||||
|
|
||||||
@ -42,6 +41,9 @@ export class CrawlDetail extends LiteElement {
|
|||||||
@state()
|
@state()
|
||||||
private crawl?: Crawl;
|
private crawl?: Crawl;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private crawlTemplate?: CrawlTemplate;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private sectionName: SectionName = "overview";
|
private sectionName: SectionName = "overview";
|
||||||
|
|
||||||
@ -77,15 +79,22 @@ export class CrawlDetail extends LiteElement {
|
|||||||
return this.crawl.resources.length > 0;
|
return this.crawl.resources.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
firstUpdated() {
|
||||||
if (!this.crawlsBaseUrl) {
|
if (!this.crawlsBaseUrl) {
|
||||||
throw new Error("Crawls base URL not defined");
|
throw new Error("Crawls base URL not defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchCrawl();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties: Map<string, any>) {
|
updated(changedProperties: Map<string, any>) {
|
||||||
|
const prevId = changedProperties.get("crawlId");
|
||||||
|
|
||||||
|
if (prevId && prevId !== this.crawlId) {
|
||||||
|
// Handle update on URL change, e.g. from re-run
|
||||||
|
this.stopPollTimer();
|
||||||
|
this.fetchData();
|
||||||
|
} else {
|
||||||
const prevCrawl = changedProperties.get("crawl");
|
const prevCrawl = changedProperties.get("crawl");
|
||||||
|
|
||||||
if (prevCrawl && this.crawl) {
|
if (prevCrawl && this.crawl) {
|
||||||
@ -94,6 +103,7 @@ export class CrawlDetail extends LiteElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
// Set initial active section based on URL #hash value
|
// Set initial active section based on URL #hash value
|
||||||
@ -302,6 +312,28 @@ export class CrawlDetail extends LiteElement {
|
|||||||
>
|
>
|
||||||
|
|
||||||
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
|
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
|
||||||
|
${!this.isActive && this.crawlTemplate && !this.crawlTemplate.inactive
|
||||||
|
? html`
|
||||||
|
<li
|
||||||
|
class="p-2 text-purple-500 hover:bg-purple-500 hover:text-white cursor-pointer"
|
||||||
|
role="menuitem"
|
||||||
|
@click=${(e: any) => {
|
||||||
|
this.runNow();
|
||||||
|
e.target.closest("sl-dropdown").hide();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<sl-icon
|
||||||
|
class="inline-block align-middle"
|
||||||
|
name="arrow-clockwise"
|
||||||
|
></sl-icon>
|
||||||
|
<span class="inline-block align-middle">
|
||||||
|
${msg("Re-run crawl")}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<hr />
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
class="p-2 hover:bg-zinc-100 cursor-pointer"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
@ -608,6 +640,11 @@ export class CrawlDetail extends LiteElement {
|
|||||||
<span class="inline-block align-middle">
|
<span class="inline-block align-middle">
|
||||||
${this.crawl.configName}
|
${this.crawl.configName}
|
||||||
</span>
|
</span>
|
||||||
|
${this.crawlTemplate?.inactive
|
||||||
|
? html`<sl-tag type="warning" size="small"
|
||||||
|
>${msg("Inactive")}</sl-tag
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
</a>
|
</a>
|
||||||
`
|
`
|
||||||
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
|
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
|
||||||
@ -743,6 +780,11 @@ export class CrawlDetail extends LiteElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchData() {
|
||||||
|
await this.fetchCrawl();
|
||||||
|
this.fetchCrawlTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch crawl and update internal state
|
* Fetch crawl and update internal state
|
||||||
*/
|
*/
|
||||||
@ -751,7 +793,8 @@ export class CrawlDetail extends LiteElement {
|
|||||||
this.crawl = await this.getCrawl();
|
this.crawl = await this.getCrawl();
|
||||||
|
|
||||||
if (this.isActive) {
|
if (this.isActive) {
|
||||||
// Start timer for next poll
|
// Restart timer for next poll
|
||||||
|
this.stopPollTimer();
|
||||||
this.timerId = window.setTimeout(() => {
|
this.timerId = window.setTimeout(() => {
|
||||||
this.fetchCrawl();
|
this.fetchCrawl();
|
||||||
}, 1000 * POLL_INTERVAL_SECONDS);
|
}, 1000 * POLL_INTERVAL_SECONDS);
|
||||||
@ -768,12 +811,6 @@ export class CrawlDetail extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCrawl(): Promise<Crawl> {
|
async getCrawl(): Promise<Crawl> {
|
||||||
// Mock to use in dev:
|
|
||||||
// return import("../../__mocks__/api/archives/[id]/crawls").then(
|
|
||||||
// (module) => module.default.running[0]
|
|
||||||
// // (module) => module.default.finished[0]
|
|
||||||
// );
|
|
||||||
|
|
||||||
const data: Crawl = await this.apiFetch(
|
const data: Crawl = await this.apiFetch(
|
||||||
`${this.crawlsAPIBaseUrl || this.crawlsBaseUrl}/${this.crawlId}.json`,
|
`${this.crawlsAPIBaseUrl || this.crawlsBaseUrl}/${this.crawlId}.json`,
|
||||||
this.authState!
|
this.authState!
|
||||||
@ -782,6 +819,30 @@ export class CrawlDetail extends LiteElement {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch crawl template and update internal state
|
||||||
|
*/
|
||||||
|
private async fetchCrawlTemplate(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.crawlTemplate = await this.getCrawlTemplate();
|
||||||
|
} catch {
|
||||||
|
// Fail silently since page will mostly still function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCrawlTemplate(): Promise<CrawlTemplate> {
|
||||||
|
if (!this.crawl) {
|
||||||
|
throw new Error("missing crawl");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: CrawlTemplate = await this.apiFetch(
|
||||||
|
`/archives/${this.crawl.aid}/crawlconfigs/${this.crawl.cid}`,
|
||||||
|
this.authState!
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
private async cancel() {
|
private async cancel() {
|
||||||
if (window.confirm(msg("Are you sure you want to cancel the crawl?"))) {
|
if (window.confirm(msg("Are you sure you want to cancel the crawl?"))) {
|
||||||
const data = await this.apiFetch(
|
const data = await this.apiFetch(
|
||||||
@ -864,6 +925,63 @@ export class CrawlDetail extends LiteElement {
|
|||||||
this.isSubmittingUpdate = false;
|
this.isSubmittingUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async runNow() {
|
||||||
|
if (!this.crawl) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get crawl config to check if crawl is already running
|
||||||
|
const crawlTemplate = await this.getCrawlTemplate();
|
||||||
|
|
||||||
|
if (crawlTemplate.currCrawlId) {
|
||||||
|
this.notify({
|
||||||
|
message: msg(
|
||||||
|
html`Crawl of <strong>${this.crawl.configName}</strong> is already
|
||||||
|
running.
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
class="underline hover:no-underline"
|
||||||
|
href="/archives/${this.crawl
|
||||||
|
.aid}/crawls/crawl/${crawlTemplate.currCrawlId}"
|
||||||
|
@click=${this.navLink.bind(this)}
|
||||||
|
>View crawl</a
|
||||||
|
>`
|
||||||
|
),
|
||||||
|
type: "warning",
|
||||||
|
icon: "exclamation-triangle",
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.apiFetch(
|
||||||
|
`/archives/${this.crawl.aid}/crawlconfigs/${this.crawl.cid}/run`,
|
||||||
|
this.authState!,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.started) {
|
||||||
|
this.navTo(`/archives/${this.crawl.aid}/crawls/crawl/${data.started}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notify({
|
||||||
|
message: msg(
|
||||||
|
html`Started crawl from <strong>${this.crawl.configName}</strong>.`
|
||||||
|
),
|
||||||
|
type: "success",
|
||||||
|
icon: "check2-circle",
|
||||||
|
duration: 8000,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
this.notify({
|
||||||
|
message: msg("Sorry, couldn't run crawl at this time."),
|
||||||
|
type: "danger",
|
||||||
|
icon: "exclamation-octagon",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private stopPollTimer() {
|
private stopPollTimer() {
|
||||||
window.clearTimeout(this.timerId);
|
window.clearTimeout(this.timerId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1125,6 +1125,11 @@ export class CrawlTemplatesDetail extends LiteElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.crawlTemplate = {
|
||||||
|
...this.crawlTemplate,
|
||||||
|
inactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
this.notify({
|
this.notify({
|
||||||
message: msg(
|
message: msg(
|
||||||
html`Deactivated <strong>${this.crawlTemplate.name}</strong>.`
|
html`Deactivated <strong>${this.crawlTemplate.name}</strong>.`
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { RelativeDuration } from "../../components/relative-duration";
|
|||||||
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 type { Crawl, CrawlTemplate } from "./types";
|
import type { Crawl, CrawlTemplate } from "./types";
|
||||||
|
import type { InitialCrawlTemplate } from "./crawl-templates-new";
|
||||||
|
|
||||||
type CrawlSearchResult = {
|
type CrawlSearchResult = {
|
||||||
item: Crawl;
|
item: Crawl;
|
||||||
@ -49,9 +50,6 @@ export class CrawlsList extends LiteElement {
|
|||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
authState!: AuthState;
|
authState!: AuthState;
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
archiveId?: string;
|
|
||||||
|
|
||||||
// e.g. `/archive/${this.archiveId}/crawls`
|
// e.g. `/archive/${this.archiveId}/crawls`
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
crawlsBaseUrl!: string;
|
crawlsBaseUrl!: string;
|
||||||
@ -571,20 +569,17 @@ export class CrawlsList extends LiteElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async runNow(crawl: Crawl) {
|
private async runNow(crawl: Crawl) {
|
||||||
try {
|
|
||||||
// Get crawl config to check if crawl is already running
|
// Get crawl config to check if crawl is already running
|
||||||
const crawlTemplate = await this.getCrawlTemplate(crawl);
|
const crawlTemplate = await this.getCrawlTemplate(crawl);
|
||||||
|
|
||||||
if (crawlTemplate.currCrawlId) {
|
if (crawlTemplate?.currCrawlId) {
|
||||||
this.notify({
|
this.notify({
|
||||||
message: msg(
|
message: msg(
|
||||||
html`Crawl of <strong>${crawl.configName}</strong> is already
|
html`Crawl of <strong>${crawl.configName}</strong> is already running.
|
||||||
running.
|
|
||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
class="underline hover:no-underline"
|
class="underline hover:no-underline"
|
||||||
href="/archives/${this
|
href="/archives/${crawl.aid}/crawls/crawl/${crawlTemplate.currCrawlId}"
|
||||||
.archiveId}/crawls/crawl/${crawlTemplate.currCrawlId}"
|
|
||||||
@click=${this.navLink.bind(this)}
|
@click=${this.navLink.bind(this)}
|
||||||
>View crawl</a
|
>View crawl</a
|
||||||
>`
|
>`
|
||||||
@ -596,6 +591,7 @@ export class CrawlsList extends LiteElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
const data = await this.apiFetch(
|
const data = await this.apiFetch(
|
||||||
`/archives/${crawl.aid}/crawlconfigs/${crawl.cid}/run`,
|
`/archives/${crawl.aid}/crawlconfigs/${crawl.cid}/run`,
|
||||||
this.authState!,
|
this.authState!,
|
||||||
@ -614,8 +610,7 @@ export class CrawlsList extends LiteElement {
|
|||||||
<br />
|
<br />
|
||||||
<a
|
<a
|
||||||
class="underline hover:no-underline"
|
class="underline hover:no-underline"
|
||||||
href="/archives/${this
|
href="/archives/${crawl.aid}/crawls/crawl/${data.started}#watch"
|
||||||
.archiveId}/crawls/crawl/${data.started}#watch"
|
|
||||||
@click=${this.navLink.bind(this)}
|
@click=${this.navLink.bind(this)}
|
||||||
>Watch crawl</a
|
>Watch crawl</a
|
||||||
>`
|
>`
|
||||||
@ -624,7 +619,24 @@ export class CrawlsList extends LiteElement {
|
|||||||
icon: "check2-circle",
|
icon: "check2-circle",
|
||||||
duration: 8000,
|
duration: 8000,
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (e: any) {
|
||||||
|
if (e.isApiError && e.statusCode === 404) {
|
||||||
|
this.notify({
|
||||||
|
message: msg(
|
||||||
|
html`Sorry, cannot rerun crawl from a deactivated crawl template.
|
||||||
|
<br />
|
||||||
|
<button
|
||||||
|
class="underline hover:no-underline"
|
||||||
|
@click=${() => this.duplicateConfig(crawl, crawlTemplate)}
|
||||||
|
>
|
||||||
|
Duplicate crawl template
|
||||||
|
</button>`
|
||||||
|
),
|
||||||
|
type: "danger",
|
||||||
|
icon: "exclamation-octagon",
|
||||||
|
duration: 8000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
this.notify({
|
this.notify({
|
||||||
message: msg("Sorry, couldn't run crawl at this time."),
|
message: msg("Sorry, couldn't run crawl at this time."),
|
||||||
type: "danger",
|
type: "danger",
|
||||||
@ -632,6 +644,7 @@ export class CrawlsList extends LiteElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getCrawlTemplate(crawl: Crawl): Promise<CrawlTemplate> {
|
async getCrawlTemplate(crawl: Crawl): Promise<CrawlTemplate> {
|
||||||
const data: CrawlTemplate = await this.apiFetch(
|
const data: CrawlTemplate = await this.apiFetch(
|
||||||
@ -641,6 +654,27 @@ export class CrawlsList extends LiteElement {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new template using existing template data
|
||||||
|
*/
|
||||||
|
private async duplicateConfig(crawl: Crawl, template: CrawlTemplate) {
|
||||||
|
const crawlTemplate: InitialCrawlTemplate = {
|
||||||
|
name: msg(str`${template.name} Copy`),
|
||||||
|
config: template.config,
|
||||||
|
profileid: template.profileid || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.navTo(`/archives/${crawl.aid}/crawl-templates/new`, {
|
||||||
|
crawlTemplate,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.notify({
|
||||||
|
message: msg(str`Copied crawl configuration to new template.`),
|
||||||
|
type: "success",
|
||||||
|
icon: "check2-circle",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("btrix-crawls-list", CrawlsList);
|
customElements.define("btrix-crawls-list", CrawlsList);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user