Initial crawl detail page (#108)
This commit is contained in:
parent
7c067ffe36
commit
be4bf3742f
@ -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);
|
||||
});
|
||||
|
||||
32
frontend/src/components/relative-duration.ts
Normal file
32
frontend/src/components/relative-duration.ts
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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)}
|
||||
|
||||
447
frontend/src/pages/archive/crawl-detail.ts
Normal file
447
frontend/src/pages/archive/crawl-detail.ts
Normal 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"
|
||||
>
|
||||
●
|
||||
</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);
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}`
|
||||
);
|
||||
|
||||
@ -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!}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user