diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 89ca4b2a..6988dee7 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -44,11 +44,7 @@ export class App extends LiteElement { userInfo?: CurrentUser; @state() - private viewState!: ViewState & { - aid?: string; - // TODO common tab type - tab?: "running" | "finished" | "configs"; - }; + private viewState!: ViewState; @state() private globalDialogContent: DialogContent = {}; @@ -203,9 +199,9 @@ export class App extends LiteElement { renderNavBar() { return html` -
+
@@ -255,32 +255,6 @@ export class App extends LiteElement { } renderPage() { - const navLink = ({ - activeRoutes, - href, - label, - }: { - activeRoutes: string[]; - href: string; - label: string; - }) => html` -
  • - ${label} -
  • - `; - const appLayout = (template: TemplateResult) => html` -
    - ${template} -
    - `; - switch (this.viewState.route) { case "signUp": { if (!this.isAppSettingsLoaded) { @@ -365,13 +339,13 @@ export class App extends LiteElement {
    `; case "archives": - return appLayout(html``); + >`; case "archive": case "archiveAddMember": @@ -379,7 +353,7 @@ export class App extends LiteElement { case "crawl": case "crawlTemplate": case "crawlTemplateEdit": - return appLayout(html``); + >`; case "accountSettings": - return appLayout(html``); - - case "archive-info": - case "archive-info-tab": - return appLayout(html``); + >`; case "usersInvite": { if (this.userInfo?.isAdmin) { - return appLayout(html``); + >`; } else { return this.renderNotFoundPage(); } @@ -554,20 +517,24 @@ export class App extends LiteElement { noHeader: true, body: html`
    -

    Welcome to Browsertrix Cloud!

    +

    + ${msg("Welcome to Browsertrix Cloud!")} +

    - A confirmation email was sent to:
    - ${email}. + ${msg(html`A confirmation email was sent to:
    + ${email}.`)}

    - Click the link in your email to confirm your email address. + ${msg( + "Click the link in your email to confirm your email address." + )}

    this.closeDialog()} - >Got it, go to dashboard${msg("Got it, go to dashboard")}
    `, diff --git a/frontend/src/pages/archive/crawl-detail.ts b/frontend/src/pages/archive/crawl-detail.ts index 5c4a860c..23cba241 100644 --- a/frontend/src/pages/archive/crawl-detail.ts +++ b/frontend/src/pages/archive/crawl-detail.ts @@ -1,3 +1,4 @@ +import type { TemplateResult } from "lit"; import { state, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { msg, localized, str } from "@lit/localize"; @@ -7,6 +8,8 @@ import type { AuthState } from "../../utils/AuthService"; import LiteElement, { html } from "../../utils/LiteElement"; import type { Crawl } from "./types"; +type SectionName = "overview" | "watch" | "download" | "logs"; + const POLL_INTERVAL_SECONDS = 10; /** @@ -35,6 +38,9 @@ export class CrawlDetail extends LiteElement { @state() private isWatchExpanded: boolean = false; + @state() + private sectionName: SectionName = "overview"; + // For long polling: private timerId?: number; @@ -52,92 +58,225 @@ export class CrawlDetail extends LiteElement { // } } + connectedCallback(): void { + // Set initial active section based on URL #hash value + const hash = window.location.hash.slice(1); + if (["overview", "watch", "download", "logs"].includes(hash)) { + this.sectionName = hash as SectionName; + } + super.connectedCallback(); + } + disconnectedCallback(): void { this.stopPollTimer(); super.disconnectedCallback(); } render() { + let sectionContent: string | TemplateResult = ""; + + switch (this.sectionName) { + case "watch": + sectionContent = this.renderWatch(); + break; + case "download": + sectionContent = this.renderFiles(); + break; + case "logs": + sectionContent = this.renderLogs(); + break; + default: + sectionContent = this.renderOverview(); + break; + } + return html` -
    + +
    +
    ${this.renderNav()}
    +
    +
    ${sectionContent}
    +
    +
    + `; + } + + private renderNav() { + const renderNavItem = ({ + section, + label, + }: { + section: SectionName; + label: any; + }) => { + const isActive = section === this.sectionName; + return html` + + `; + }; + return html` + + `; + } -
    -

    - ${this.crawl - ? msg(str`Crawl of ${this.crawl.configName}`) - : html``} -

    -
    + private renderHeader() { + const isRunning = this.crawl?.state === "running"; -
    -
    + return html` +
    +
    +

    + ${msg( + html`Crawl of ${this.crawl + ? this.crawl.configName + : html``}` + )} +

    -
    ${msg("Crawl ID")}
    -
    - ${this.crawl?.id || - html``} -
    + ${isRunning + ? html` + + ${msg("Stop Crawl")} + + + ${msg("Cancel Crawl")} + + ` + : this.crawl + ? html` + + ${msg("View Config")} + + ` + : ""}
    -
    -
    ${msg("Crawl Template")}
    -
    +
    +
    +
    +
    ${msg("Status")}
    +
    ${this.crawl ? html` - ${this.crawl.configName} +
    +
    + + ● + + ${this.crawl.state.replace(/_/g, " ")} +
    +
    ` - : html``} + : html``} +
    +
    +
    +
    ${msg("Pages Crawled")}
    +
    + ${this.crawl?.stats + ? html` + + ${this.numberFormatter.format(+this.crawl.stats.done)} + / + ${this.numberFormatter.format(+this.crawl.stats.found)} + + ` + : html``} +
    +
    +
    +
    ${msg("Run Duration")}
    +
    + ${this.crawl + ? html` + ${this.crawl.finished + ? html`${RelativeDuration.humanize( + new Date(`${this.crawl.finished}Z`).valueOf() - + new Date(`${this.crawl.started}Z`).valueOf() + )}` + : html` + + + + `} + ` + : html``}
    - - -
    - -
    -
    -
    - ${this.renderWatch()} -
    - -
    - ${this.renderDetails()} -
    -
    - -
    -

    ${msg("Download Files")}

    - ${this.renderFiles()} -
    -
    + `; } @@ -151,6 +290,8 @@ export class CrawlDetail extends LiteElement { const replaySource = this.crawl?.resources?.[0]?.path; return html` +

    ${msg("Watch or Replay Crawl")}

    +
    -
    -
    ${msg("Status")}
    -
    - ${this.crawl - ? html` -
    -
    - - ● - - ${this.crawl.state.replace(/_/g, " ")} -
    -
    - ` - : html``} - ${isRunning - ? html` - - - ${msg("Manage")} - - -
    - - ${msg("Stop Crawl")} - - - ${msg("Cancel Crawl")} - -
    -
    - ` - : ""} -
    -
    -
    ${msg("Pages Crawled")}
    -
    - ${this.crawl?.stats - ? html` - - ${this.numberFormatter.format(+this.crawl.stats.done)} - / - ${this.numberFormatter.format(+this.crawl.stats.found)} - - ` - : html``} -
    -
    -
    -
    ${msg("Run Duration")}
    -
    - ${this.crawl - ? html` - ${this.crawl.finished - ? html`${RelativeDuration.humanize( - new Date(`${this.crawl.finished}Z`).valueOf() - - new Date(`${this.crawl.started}Z`).valueOf() - )}` - : html` - - - - `} - ` - : html``} -
    -
    -
    ${msg("Started")}
    ${this.crawl @@ -318,7 +363,7 @@ export class CrawlDetail extends LiteElement { : html``}
    -
    +
    ${msg("Finished")}
    ${this.crawl @@ -338,7 +383,7 @@ export class CrawlDetail extends LiteElement { : html``}
    -
    +
    ${msg("Reason")}
    ${this.crawl @@ -355,12 +400,48 @@ export class CrawlDetail extends LiteElement { : html``}
    +
    +
    ${msg("Crawl Template")}
    +
    + ${this.crawl + ? html` + + + + ${this.crawl.configName} + + + ` + : html``} +
    +
    +
    +
    ${msg("Crawl ID")}
    +
    + ${this.crawl + ? html` + ${this.crawl.id} ` + : html``} +
    +
    `; } private renderFiles() { return html` +

    ${msg("Download Files")}

      ${this.crawl?.resources?.map( (file) => html` @@ -382,6 +463,10 @@ export class CrawlDetail extends LiteElement { `; } + private renderLogs() { + return html`TODO`; + } + /** * Fetch crawl and update internal state */ diff --git a/frontend/src/pages/archive/crawl-templates-detail.ts b/frontend/src/pages/archive/crawl-templates-detail.ts index 6463e54f..a7a5179b 100644 --- a/frontend/src/pages/archive/crawl-templates-detail.ts +++ b/frontend/src/pages/archive/crawl-templates-detail.ts @@ -118,7 +118,7 @@ export class CrawlTemplatesDetail extends LiteElement { (this.openDialogName = "config")} + @click=${() => (this.openDialogName = "name")} > ${msg("Edit")} diff --git a/frontend/src/pages/archive/index.ts b/frontend/src/pages/archive/index.ts index 8029486f..a238448a 100644 --- a/frontend/src/pages/archive/index.ts +++ b/frontend/src/pages/archive/index.ts @@ -80,11 +80,14 @@ export class Archive extends LiteElement { render() { if (!this.archive) { - return html`
      - -
      `; + return html` +
      + +
      + `; } const showMembersTab = Boolean(this.archive.users); @@ -112,20 +115,22 @@ export class Archive extends LiteElement { break; } - return html`
      - + return html`
      +
      + +
      -
      -