Initial crawl detail page (#108)

This commit is contained in:
sua yoo 2022-01-30 18:36:43 -08:00 committed by GitHub
parent 7c067ffe36
commit be4bf3742f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 572 additions and 79 deletions

View File

@ -15,6 +15,9 @@ import("./copy-button").then(({ CopyButton }) => {
import("./invite-form").then(({ InviteForm }) => {
customElements.define("btrix-invite-form", InviteForm);
});
import("./relative-duration").then(({ RelativeDuration }) => {
customElements.define("btrix-relative-duration", RelativeDuration);
});
import("./sign-up-form").then(({ SignUpForm }) => {
customElements.define("btrix-sign-up-form", SignUpForm);
});

View File

@ -0,0 +1,32 @@
import { LitElement } from "lit";
import { property, state } from "lit/decorators.js";
import humanizeDuration from "pretty-ms";
/**
* Show time passed from date in human-friendly format
*
* Usage example:
* ```ts
* <btrix-relative-duration value=${value}></btrix-relative-duration>
* ```
*
* @event on-copied
*/
export class RelativeDuration extends LitElement {
@property({ type: String })
value?: string; // `new Date` compatible date format
static humanize(duration: number) {
return humanizeDuration(duration, {
secondsDecimalDigits: 0,
});
}
render() {
if (!this.value) return "";
return RelativeDuration.humanize(
Date.now() - new Date(this.value).valueOf()
);
}
}

View File

@ -371,6 +371,7 @@ export class App extends LiteElement {
case "archive":
case "archiveAddMember":
case "archiveNewResourceTab":
case "crawl":
case "crawlTemplate":
case "crawlTemplateEdit":
return appLayout(html`<btrix-archive
@ -386,6 +387,7 @@ export class App extends LiteElement {
? "crawl-templates"
: (this.viewState.params.tab as ArchiveTab)}
crawlConfigId=${this.viewState.params.crawlConfigId}
crawlId=${this.viewState.params.crawlId}
?isAddingMember=${this.viewState.route === "archiveAddMember"}
?isNewResourceTab=${this.viewState.route === "archiveNewResourceTab"}
?isEditing=${Boolean(this.viewState.params.edit)}

View File

@ -0,0 +1,447 @@
import { state, property } from "lit/decorators.js";
import { msg, localized, str } from "@lit/localize";
import { RelativeDuration } from "../../components/relative-duration";
import type { AuthState } from "../../utils/AuthService";
import LiteElement, { html } from "../../utils/LiteElement";
import type { Crawl } from "./types";
const POLL_INTERVAL_SECONDS = 10;
/**
* Usage:
* ```ts
* <btrix-crawl-detail></btrix-crawl-detail>
* ```
*/
@localized()
export class CrawlDetail extends LiteElement {
@property({ type: Object })
authState?: AuthState;
@property({ type: String })
archiveId?: string;
@property({ type: String })
crawlId?: string;
@state()
private crawl?: Crawl;
@state()
private watchUrl?: string;
@state()
private isWatchExpanded: boolean = false;
// For long polling:
private timerId?: number;
// TODO localize
private numberFormatter = new Intl.NumberFormat();
async firstUpdated() {
this.fetchCrawl();
// try {
// this.watchUrl = await this.watchCrawl();
// console.log(this.watchUrl);
// } catch (e) {
// console.error(e);
// }
}
disconnectedCallback(): void {
this.stopPollTimer();
super.disconnectedCallback();
}
render() {
return html`
<header class="my-3">
<h2 class="font-mono text-xs text-0-400 h-4">
${this.crawl?.id ||
html`<sl-skeleton style="width: 37em"></sl-skeleton>`}
</h2>
</header>
<main class="grid gap-5">
<section
class="grid grid-cols-2 md:grid-cols-8 gap-3 rounded-lg md:p-4 md:bg-zinc-100"
>
<div
class="col-span-8 ${this.isWatchExpanded
? "md:col-span-8"
: "md:col-span-5"} relative"
>
${this.renderWatch()}
</div>
<div
class="col-span-8 ${this.isWatchExpanded
? "md:col-span-8"
: "md:col-span-3"} border rounded-lg bg-white p-4 md:p-8"
>
${this.renderDetails()}
</div>
</section>
<section>
<h3 class="text-lg font-medium mb-2">${msg("Files")}</h3>
${this.renderFiles()}
</section>
</main>
`;
}
private renderWatch() {
const isRunning = this.crawl?.state === "running";
return html`
<div
class="aspect-video rounded border ${isRunning
? "border-purple-200"
: "border-slate-100"}"
>
<!-- https://github.com/webrecorder/browsertrix-crawler/blob/9f541ab011e8e4bccf8de5bd7dc59b632c694bab/screencast/index.html -->
[watch/replay]
</div>
<div
class="absolute top-2 right-2 flex bg-white/90 hover:bg-white rounded-full"
>
${this.isWatchExpanded
? html`
<sl-icon-button
class="px-1"
name="arrows-angle-contract"
label=${msg("Contract crawl video")}
@click=${() => (this.isWatchExpanded = false)}
></sl-icon-button>
`
: html`
<sl-icon-button
class="px-1"
name="arrows-angle-expand"
label=${msg("Expand crawl video")}
@click=${() => (this.isWatchExpanded = true)}
></sl-icon-button>
`}
${this.watchUrl
? html`
<sl-icon-button
class="border-l px-1"
href=${this.watchUrl}
name="box-arrow-up-right"
label=${msg("Open in new window")}
target="_blank"
></sl-icon-button>
`
: ""}
</div>
`;
}
private renderDetails() {
const isRunning = this.crawl?.state === "running";
return html`
<dl class="grid grid-cols-2 gap-5">
<div class="col-span-2">
<dt class="text-sm text-0-600">${msg("Crawl Template")}</dt>
<dd>
${this.crawl
? html`
<a
class="font-medium hover:underline"
href=${`/archives/${this.archiveId}/crawl-templates/${this.crawl.cid}`}
@click=${this.navLink}
>${this.crawl.configName}</a
>
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
<div class="col-span-2">
<dt class="text-sm text-0-600">${msg("Status")}</dt>
<dd>
${this.crawl
? html`
<div class="flex items-baseline justify-between">
<div
class="whitespace-nowrap capitalize${isRunning
? " motion-safe:animate-pulse"
: ""}"
>
<span
class="inline-block ${this.crawl.state === "failed"
? "text-red-500"
: this.crawl.state === "complete"
? "text-emerald-500"
: isRunning
? "text-purple-500"
: "text-zinc-300"}"
style="font-size: 10px; vertical-align: 2px"
>
&#9679;
</span>
${this.crawl.state.replace(/_/g, " ")}
</div>
</div>
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
${isRunning
? html`
<sl-details
class="mt-2"
style="--sl-spacing-medium: var(--sl-spacing-x-small)"
>
<span slot="summary" class="text-sm text-0-700">
${msg("Manage")}
</span>
<div class="mb-3 text-center text-sm leading-none">
<sl-button class="mr-2" size="small" @click=${this.stop}>
${msg("Stop Crawl")}
</sl-button>
<sl-button
size="small"
type="danger"
@click=${this.cancel}
>
${msg("Cancel Crawl")}
</sl-button>
</div>
</sl-details>
`
: ""}
</dd>
</div>
<div class="col-span-1">
<dt class="text-sm text-0-600">${msg("Pages Crawled")}</dt>
<dd>
${this.crawl?.stats
? html`
<span
class="font-mono tracking-tighter${isRunning
? " text-purple-600"
: ""}"
>
${this.numberFormatter.format(+this.crawl.stats.done)}
<span class="text-0-400">/</span>
${this.numberFormatter.format(+this.crawl.stats.found)}
</span>
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
<div class="col-span-1">
<dt class="text-sm text-0-600">${msg("Run Duration")}</dt>
<dd>
${this.crawl
? html`
${this.crawl.finished
? html`${RelativeDuration.humanize(
new Date(`${this.crawl.finished}Z`).valueOf() -
new Date(`${this.crawl.started}Z`).valueOf()
)}`
: html`
<span class="text-purple-600">
<btrix-relative-duration
value=${`${this.crawl.started}Z`}
></btrix-relative-duration>
</span>
`}
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
<div class="col-span-2">
<dt class="text-sm text-0-600">${msg("Started")}</dt>
<dd>
${this.crawl
? html`
<sl-format-date
date=${`${this.crawl.started}Z` /** Z for UTC */}
month="2-digit"
day="2-digit"
year="2-digit"
hour="numeric"
minute="numeric"
time-zone-name="short"
></sl-format-date>
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
<div class="col-span-2">
<dt class="text-sm text-0-600">${msg("Finished")}</dt>
<dd>
${this.crawl
? html`
${this.crawl.finished
? html`<sl-format-date
date=${`${this.crawl.finished}Z` /** Z for UTC */}
month="2-digit"
day="2-digit"
year="2-digit"
hour="numeric"
minute="numeric"
time-zone-name="short"
></sl-format-date>`
: html`<span class="text-0-400">${msg("Pending")}</span>`}
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
<div class="col-span-2">
<dt class="text-sm text-0-600">${msg("Reason")}</dt>
<dd>
${this.crawl
? html`
${this.crawl.manual
? msg(
html`Manual start by
<span
>${this.crawl?.userName || this.crawl?.userid}</span
>`
)
: msg(html`Scheduled run`)}
`
: html`<sl-skeleton class="h-6"></sl-skeleton>`}
</dd>
</div>
</dl>
`;
}
private renderFiles() {
return html`
<ul class="border rounded text-sm">
${this.crawl?.files?.map(
(file) => html`
<li class="flex justify-between p-3 border-t first:border-t-0">
<div>
<a
class="text-primary hover:underline"
href=${file.filename}
download
title=${file.filename.slice(
file.filename.lastIndexOf("/") + 1
)}
>${msg(
str`Download ${file.filename.slice(
file.filename.lastIndexOf(".")
)}`
)}</a
>
</div>
<div><sl-format-bytes value=${file.size}></sl-format-bytes></div>
</li>
`
)}
</ul>
`;
}
/**
* Fetch crawl and update internal state
*/
private async fetchCrawl(): Promise<void> {
try {
this.crawl = await this.getCrawl();
if (this.crawl.state === "running") {
// Start timer for next poll
this.timerId = window.setTimeout(() => {
this.fetchCrawl();
}, 1000 * POLL_INTERVAL_SECONDS);
} else {
this.stopPollTimer();
}
} catch {
this.notify({
message: msg("Sorry, couldn't retrieve crawl at this time."),
type: "danger",
icon: "exclamation-octagon",
});
}
}
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(
`/archives/${this.archiveId}/crawls/${this.crawlId}`,
this.authState!
);
return data;
}
private async watchCrawl(): Promise<string> {
const data = await this.apiFetch(
`/archives/${this.archiveId}/crawls/${this.crawlId}/watch`,
this.authState!,
{
method: "POST",
}
);
return data.watch_url;
}
private async cancel() {
if (window.confirm(msg("Are you sure you want to cancel the crawl?"))) {
const data = await this.apiFetch(
`/archives/${this.archiveId}/crawls/${this.crawlId}/cancel`,
this.authState!,
{
method: "POST",
}
);
if (data.canceled === true) {
this.fetchCrawl();
} else {
this.notify({
message: msg("Sorry, couldn't cancel crawl at this time."),
type: "danger",
icon: "exclamation-octagon",
});
}
}
}
private async stop() {
if (window.confirm(msg("Are you sure you want to stop the crawl?"))) {
const data = await this.apiFetch(
`/archives/${this.archiveId}/crawls/${this.crawlId}/stop`,
this.authState!,
{
method: "POST",
}
);
if (data.stopped_gracefully === true) {
this.fetchCrawl();
} else {
this.notify({
message: msg("Sorry, couldn't stop crawl at this time."),
type: "danger",
icon: "exclamation-octagon",
});
}
}
}
private stopPollTimer() {
window.clearTimeout(this.timerId);
}
}
customElements.define("btrix-crawl-detail", CrawlDetail);

View File

@ -272,7 +272,7 @@ export class CrawlTemplatesDetail extends LiteElement {
${this.crawlTemplate.currCrawlId
? html` <a
class="text-primary font-medium hover:underline text-sm p-1"
href=${`/archives/${this.archiveId}/crawls/${this.crawlTemplate.currCrawlId}`}
href=${`/archives/${this.archiveId}/crawls/crawl/${this.crawlTemplate.currCrawlId}`}
@click=${this.navLink}
>${msg("View crawl")}</a
>`
@ -298,7 +298,7 @@ export class CrawlTemplatesDetail extends LiteElement {
${this.crawlTemplate?.lastCrawlId
? html`<a
class="text-primary font-medium hover:underline text-sm p-1"
href=${`/archives/${this.archiveId}/crawls/${this.crawlTemplate.lastCrawlId}`}
href=${`/archives/${this.archiveId}/crawls/crawl/${this.crawlTemplate.lastCrawlId}`}
@click=${this.navLink}
>${msg("View crawl")}</a
>
@ -330,7 +330,7 @@ export class CrawlTemplatesDetail extends LiteElement {
return html`
<a
class="flex items-center justify-between mb-4 px-3 py-2 border rounded-lg bg-purple-50 border-purple-200 hover:border-purple-500 shadow shadow-purple-200 text-purple-800 transition-colors"
href=${`/archives/${this.archiveId}/crawls/${this.crawlTemplate.currCrawlId}`}
href=${`/archives/${this.archiveId}/crawls/crawl/${this.crawlTemplate.currCrawlId}`}
@click=${this.navLink}
>
<span>${msg("View currently running crawl")}</span>
@ -417,7 +417,7 @@ export class CrawlTemplatesDetail extends LiteElement {
this.crawlTemplate!.name
}</strong>. <br /><a class="underline hover:no-underline" href="/archives/${
this.archiveId
}/crawls/${data.run_now_job}">View crawl</a>`
}/crawls/crawl/${data.run_now_job}">View crawl</a>`
),
type: "success",
icon: "check2-circle",

View File

@ -280,7 +280,7 @@ export class CrawlTemplatesList extends LiteElement {
e.stopPropagation();
this.runningCrawlsMap[t.id]
? this.navTo(
`/archives/${this.archiveId}/crawls/${
`/archives/${this.archiveId}/crawls/crawl/${
this.runningCrawlsMap[t.id]
}`
)
@ -415,7 +415,7 @@ export class CrawlTemplatesList extends LiteElement {
this.notify({
message: msg(
str`Started crawl from <strong>${template.name}</strong>. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/${data.run_now_job}">View crawl</a>`
str`Started crawl from <strong>${template.name}</strong>. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/crawl/${data.run_now_job}">View crawl</a>`
),
type: "success",
icon: "check2-circle",

View File

@ -543,7 +543,7 @@ export class CrawlTemplatesNew extends LiteElement {
this.notify({
message: data.run_now_job
? msg(
str`Crawl running with new template. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/${data.run_now_job}">View crawl</a>`
str`Crawl running with new template. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/crawl/${data.run_now_job}">View crawl</a>`
)
: msg("Crawl template created."),
type: "success",

View File

@ -155,7 +155,7 @@ export class CrawlTemplatesList extends LiteElement {
this.notify({
message: msg(
str`Started crawl from <strong>${template.name}</strong>. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/${data.run_now_job}">View crawl</a>`
str`Started crawl from <strong>${template.name}</strong>. <br /><a class="underline hover:no-underline" href="/archives/${this.archiveId}/crawls/crawl/${data.run_now_job}">View crawl</a>`
),
type: "success",
icon: "check2-circle",

View File

@ -1,6 +1,5 @@
import { state, property } from "lit/decorators.js";
import { msg, localized, str } from "@lit/localize";
import humanizeDuration from "pretty-ms";
import debounce from "lodash/fp/debounce";
import flow from "lodash/fp/flow";
import map from "lodash/fp/map";
@ -8,28 +7,10 @@ import orderBy from "lodash/fp/orderBy";
import Fuse from "fuse.js";
import { CopyButton } from "../../components/copy-button";
import { RelativeDuration } from "../../components/relative-duration";
import type { AuthState } from "../../utils/AuthService";
import LiteElement, { html } from "../../utils/LiteElement";
type Crawl = {
id: string;
user: string;
username?: string;
aid: string;
cid: string;
configName?: string;
schedule: string;
manual: boolean;
started: string; // UTC ISO date
finished?: string; // UTC ISO date
state: string; // "running" | "complete" | "failed" | "partial_complete"
scale: number;
stats: { done: number; found: number } | null;
files?: { filename: string; hash: string; size: number }[];
fileCount?: number;
fileSize?: number;
completions?: number;
};
import type { Crawl } from "./types";
type CrawlSearchResult = {
item: Crawl;
@ -119,8 +100,8 @@ export class CrawlsList extends LiteElement {
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.stopPollTimer();
super.disconnectedCallback();
}
render() {
@ -254,17 +235,14 @@ export class CrawlsList extends LiteElement {
private renderCrawlItem = ({ item: crawl }: CrawlSearchResult) => {
return html`<li
class="grid grid-cols-12 gap-2 items-center md:gap-6 p-4 leading-none border-t first:border-t-0"
class="grid grid-cols-12 gap-2 p-4 leading-none hover:bg-zinc-50 hover:text-primary border-t first:border-t-0 transition-colors"
role="button"
@click=${() =>
this.navTo(`/archives/${this.archiveId}/crawls/crawl/${crawl.id}`)}
title=${crawl.configName || crawl.cid}
>
<div class="col-span-12 md:col-span-5">
<div class="font-medium mb-1">
<a
class="hover:text-0-600 transition-colors"
href=${`/archives/${this.archiveId}/crawl-templates/${crawl.cid}`}
@click=${this.navLink}
>${crawl.configName || crawl.cid}</a
>
</div>
<div class="font-medium mb-1">${crawl.configName || crawl.cid}</div>
<div class="text-0-700 text-sm whitespace-nowrap truncate">
<sl-format-date
date=${`${crawl.started}Z` /** Z for UTC */}
@ -274,20 +252,9 @@ export class CrawlsList extends LiteElement {
hour="numeric"
minute="numeric"
></sl-format-date>
${crawl.manual
? html` <span
class="bg-fuchsia-50 text-fuchsia-700 text-xs rounded px-1 leading-4"
>${msg("Manual Start")}</span
>`
: html`
<span
class="bg-teal-50 text-teal-700 text-xs rounded px-1 leading-4"
>${msg("Scheduled Run")}</span
>
`}
</div>
</div>
<div class="col-span-6 md:col-span-2 flex items-start">
<div class="col-span-4 md:col-span-2 flex items-start">
<div class="mr-2">
<!-- TODO switch case in lit template? needed for tailwindcss purging -->
<span
@ -318,16 +285,13 @@ export class CrawlsList extends LiteElement {
date=${`${crawl.finished}Z` /** Z for UTC */}
></sl-relative-time>
`
: humanizeDuration(
Date.now() - new Date(`${crawl.started}Z`).valueOf(),
{
secondsDecimalDigits: 0,
}
)}
: html`<btrix-relative-duration
value=${`${crawl.started}Z`}
></btrix-relative-duration>`}
</div>
</div>
</div>
<div class="col-span-6 md:col-span-2">
<div class="col-span-4 md:col-span-2">
${crawl.finished
? html`
<div class="whitespace-nowrap truncate text-sm">
@ -345,12 +309,9 @@ export class CrawlsList extends LiteElement {
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${msg(
str`in ${humanizeDuration(
str`in ${RelativeDuration.humanize(
new Date(`${crawl.finished}Z`).valueOf() -
new Date(`${crawl.started}Z`).valueOf(),
{
secondsDecimalDigits: 0,
}
new Date(`${crawl.started}Z`).valueOf()
)}`
)}
</div>
@ -360,9 +321,9 @@ export class CrawlsList extends LiteElement {
<div
class="whitespace-nowrap truncate text-sm text-purple-600 font-mono tracking-tighter"
>
${this.numberFormatter.format(crawl.stats.done)}
${this.numberFormatter.format(+crawl.stats.done)}
<span class="text-0-400">/</span>
${this.numberFormatter.format(crawl.stats.found)}
${this.numberFormatter.format(+crawl.stats.found)}
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${msg("pages crawled")}
@ -370,20 +331,30 @@ export class CrawlsList extends LiteElement {
`
: ""}
</div>
<div class="col-span-6 md:col-span-2">
<div class="col-span-4 md:col-span-2">
${crawl.manual
? html`
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${msg("Started by")}
<div class="whitespace-nowrap truncate mb-1">
<span
class="bg-fuchsia-50 text-fuchsia-700 text-sm rounded px-1 leading-4"
>${msg("Manual Start")}</span
>
</div>
<div class="text-0-500 text-sm whitespace-nowrap truncate">
${crawl.username || crawl.user}
<div class="ml-1 text-0-500 text-sm whitespace-nowrap truncate">
${msg(str`by ${crawl.userName || crawl.userid}`)}
</div>
`
: ""}
: html`
<div class="whitespace-nowrap truncate">
<span
class="bg-teal-50 text-teal-700 text-sm rounded px-1 leading-4"
>${msg("Scheduled Run")}</span
>
</div>
`}
</div>
<div class="col-span-12 md:col-span-1 flex justify-end">
<sl-dropdown>
<sl-dropdown @click=${(e: any) => e.stopPropagation()}>
<sl-icon-button
slot="trigger"
name="three-dots"
@ -391,14 +362,13 @@ export class CrawlsList extends LiteElement {
style="font-size: 1rem"
></sl-icon-button>
<ul class="text-sm whitespace-nowrap" role="menu">
<ul class="text-sm text-0-800 whitespace-nowrap" role="menu">
${isRunning(crawl)
? html`
<li
class="p-2 hover:bg-zinc-100 cursor-pointer"
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
role="menuitem"
@click=${(e: any) => {
e.stopPropagation();
this.cancel(crawl.id);
e.target.closest("sl-dropdown").hide();
}}
@ -406,10 +376,9 @@ export class CrawlsList extends LiteElement {
${msg("Cancel immediately")}
</li>
<li
class="p-2 text-danger hover:bg-danger hover:text-white cursor-pointer"
class="p-2 hover:bg-zinc-100 cursor-pointer"
role="menuitem"
@click=${(e: any) => {
e.stopPropagation();
this.stop(crawl.id);
e.target.closest("sl-dropdown").hide();
}}
@ -422,7 +391,16 @@ export class CrawlsList extends LiteElement {
class="p-2 hover:bg-zinc-100 cursor-pointer"
role="menuitem"
@click=${(e: any) => {
e.stopPropagation();
CopyButton.copyToClipboard(crawl.id);
e.target.closest("sl-dropdown").hide();
}}
>
${msg("Copy Crawl ID")}
</li>
<li
class="p-2 hover:bg-zinc-100 cursor-pointer"
role="menuitem"
@click=${(e: any) => {
CopyButton.copyToClipboard(crawl.cid);
e.target.closest("sl-dropdown").hide();
}}
@ -433,7 +411,6 @@ export class CrawlsList extends LiteElement {
class="p-2 hover:bg-zinc-100 cursor-pointer"
role="menuitem"
@click=${(e: any) => {
e.stopPropagation();
this.navTo(
`/archives/${this.archiveId}/crawl-templates/${crawl.cid}`
);

View File

@ -11,6 +11,7 @@ import { isOwner } from "../../utils/archives";
import "./crawl-templates-detail";
import "./crawl-templates-list";
import "./crawl-templates-new";
import "./crawl-detail";
import "./crawls-list";
export type ArchiveTab = "crawls" | "crawl-templates" | "members";
@ -35,6 +36,9 @@ export class Archive extends LiteElement {
@property({ type: String })
archiveTab: ArchiveTab = defaultTab;
@property({ type: String })
crawlId?: string;
@property({ type: String })
crawlConfigId?: string;
@ -151,6 +155,14 @@ export class Archive extends LiteElement {
}
private renderCrawls() {
if (this.crawlId) {
return html`<btrix-crawl-detail
.authState=${this.authState!}
.archiveId=${this.archiveId!}
crawlId=${this.crawlId}
></btrix-crawl-detail>`;
}
return html`<btrix-crawls-list
.authState=${this.authState!}
.archiveId=${this.archiveId!}

View File

@ -1,3 +1,22 @@
export type Crawl = {
id: string;
userid: string;
userName: string;
cid: string;
configName: string;
schedule: string;
manual: boolean;
started: string; // UTC ISO date
finished?: string; // UTC ISO date
state: string; // "running" | "complete" | "failed" | "partial_complete"
scale: number;
stats: { done: string; found: string } | null;
files?: { filename: string; hash: string; size: number }[];
fileCount?: number;
fileSize?: number;
completions?: number;
};
type SeedConfig = {
scopeType?: string;
limit?: number;

View File

@ -14,6 +14,7 @@ export const ROUTES = {
archive: "/archives/:id/:tab",
archiveNewResourceTab: "/archives/:id/:tab/new",
archiveAddMember: "/archives/:id/:tab/add-member",
crawl: "/archives/:id/:tab/crawl/:crawlId",
crawlTemplate: "/archives/:id/crawl-templates/:crawlConfigId",
crawlTemplateEdit: "/archives/:id/crawl-templates/:crawlConfigId?edit",
users: "/users",