Browser profile list and detail minor UX improvements (#1822)
- Paginates browser profile list view - Moves back-up status icon to list view - Display last updated date if available (fallback to created date) - Adds max length validation to description - Better handle description and crawl workflow list
This commit is contained in:
		
							parent
							
								
									7e5d742fd1
								
							
						
					
					
						commit
						523ad68880
					
				@ -3,6 +3,7 @@ import { html, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
import { when } from "lit/directives/when.js";
 | 
			
		||||
import { capitalize } from "lodash/fp";
 | 
			
		||||
import queryString from "query-string";
 | 
			
		||||
 | 
			
		||||
import type { Profile } from "./types";
 | 
			
		||||
@ -15,7 +16,8 @@ import { NotifyController } from "@/controllers/notify";
 | 
			
		||||
import type { BrowserConnectionChange } from "@/features/browser-profiles/profile-browser";
 | 
			
		||||
import { isApiError } from "@/utils/api";
 | 
			
		||||
import type { AuthState } from "@/utils/AuthService";
 | 
			
		||||
import { getLocale } from "@/utils/localization";
 | 
			
		||||
import { maxLengthValidator } from "@/utils/form";
 | 
			
		||||
import { formatNumber, getLocale } from "@/utils/localization";
 | 
			
		||||
 | 
			
		||||
const DESCRIPTION_MAXLENGTH = 500;
 | 
			
		||||
 | 
			
		||||
@ -78,6 +80,8 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
  private readonly navigate = new NavigateController(this);
 | 
			
		||||
  private readonly notify = new NotifyController(this);
 | 
			
		||||
 | 
			
		||||
  private readonly validateDescriptionMax = maxLengthValidator(500);
 | 
			
		||||
 | 
			
		||||
  disconnectedCallback() {
 | 
			
		||||
    if (this.browserId) {
 | 
			
		||||
      void this.deleteBrowser(this.browserId);
 | 
			
		||||
@ -90,6 +94,9 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const isBackedUp =
 | 
			
		||||
      this.profile?.resource?.replicas &&
 | 
			
		||||
      this.profile.resource.replicas.length > 0;
 | 
			
		||||
    const none = html`<span class="text-neutral-400">${msg("None")}</span>`;
 | 
			
		||||
 | 
			
		||||
    return html`<div class="mb-7">
 | 
			
		||||
@ -123,25 +130,31 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
        </div>
 | 
			
		||||
      </header>
 | 
			
		||||
 | 
			
		||||
      <section class="mb-5 rounded border p-4">
 | 
			
		||||
        <dl class="grid grid-cols-3 gap-5">
 | 
			
		||||
          <div class="col-span-3 md:col-span-1">
 | 
			
		||||
            <dt class="text-sm text-0-600">${msg("Description")}</dt>
 | 
			
		||||
            <dd>
 | 
			
		||||
              ${this.profile
 | 
			
		||||
                ? this.profile.description
 | 
			
		||||
                  ? this.profile.description.slice(0, DESCRIPTION_MAXLENGTH)
 | 
			
		||||
                  : none
 | 
			
		||||
                : ""}
 | 
			
		||||
            </dd>
 | 
			
		||||
      <section class="mb-5 rounded-lg border px-4 py-2">
 | 
			
		||||
        <btrix-desc-list horizontal>
 | 
			
		||||
          <btrix-desc-list-item label=${msg("Backup Status")}>
 | 
			
		||||
            <div class="flex items-center gap-2">
 | 
			
		||||
              ${isBackedUp
 | 
			
		||||
                ? html`<sl-icon
 | 
			
		||||
                      name="clouds-fill"
 | 
			
		||||
                      class="text-success"
 | 
			
		||||
                    ></sl-icon>
 | 
			
		||||
                    ${msg("Backed Up")}`
 | 
			
		||||
                : html`<sl-icon
 | 
			
		||||
                      name="cloud-slash-fill"
 | 
			
		||||
                      class="text-neutral-500"
 | 
			
		||||
                    ></sl-icon>
 | 
			
		||||
                    ${msg("Not Backed Up")}`}
 | 
			
		||||
            </div>
 | 
			
		||||
          <div class="col-span-3 md:col-span-1">
 | 
			
		||||
            <dt class="text-sm text-0-600">
 | 
			
		||||
              <span class="inline-block align-middle"
 | 
			
		||||
                >${msg("Created at")}</span
 | 
			
		||||
              >
 | 
			
		||||
            </dt>
 | 
			
		||||
            <dd>
 | 
			
		||||
          </btrix-desc-list-item>
 | 
			
		||||
          <btrix-desc-list-item label=${msg("Crawler Release Channel")}>
 | 
			
		||||
            ${this.profile
 | 
			
		||||
              ? this.profile.crawlerChannel
 | 
			
		||||
                ? capitalize(this.profile.crawlerChannel)
 | 
			
		||||
                : none
 | 
			
		||||
              : nothing}
 | 
			
		||||
          </btrix-desc-list-item>
 | 
			
		||||
          <btrix-desc-list-item label=${msg("Created At")}>
 | 
			
		||||
            ${this.profile
 | 
			
		||||
              ? html`
 | 
			
		||||
                  <sl-format-date
 | 
			
		||||
@ -155,43 +168,31 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
                    time-zone-name="short"
 | 
			
		||||
                  ></sl-format-date>
 | 
			
		||||
                `
 | 
			
		||||
                : ""}
 | 
			
		||||
            </dd>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-span-3 md:col-span-1">
 | 
			
		||||
            <dt class="text-sm text-0-600">
 | 
			
		||||
              <span class="inline-block align-middle"
 | 
			
		||||
                >${msg("Crawl Workflows")}</span
 | 
			
		||||
              >
 | 
			
		||||
              <sl-tooltip content=${msg("Crawl workflows using this profile")}>
 | 
			
		||||
                <sl-icon
 | 
			
		||||
                  class="inline-block align-middle"
 | 
			
		||||
                  name="info-circle"
 | 
			
		||||
                ></sl-icon>
 | 
			
		||||
              </sl-tooltip>
 | 
			
		||||
            </dt>
 | 
			
		||||
            <dd>
 | 
			
		||||
              <ul class="text-sm font-medium">
 | 
			
		||||
                ${this.profile?.crawlconfigs?.map(
 | 
			
		||||
                  ({ id, name }) => html`
 | 
			
		||||
                    <li>
 | 
			
		||||
                      <a
 | 
			
		||||
                        class="text-neutral-600 hover:underline"
 | 
			
		||||
                        href=${`${this.navigate.orgBasePath}/workflows/crawl/${id}`}
 | 
			
		||||
                        @click=${this.navigate.link}
 | 
			
		||||
                      >
 | 
			
		||||
                        ${name}
 | 
			
		||||
                      </a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                  `,
 | 
			
		||||
                )}
 | 
			
		||||
              </ul>
 | 
			
		||||
            </dd>
 | 
			
		||||
          </div>
 | 
			
		||||
        </dl>
 | 
			
		||||
              : nothing}
 | 
			
		||||
          </btrix-desc-list-item>
 | 
			
		||||
          <btrix-desc-list-item label=${msg("Last Updated")}>
 | 
			
		||||
            ${this.profile
 | 
			
		||||
              ? html` <sl-format-date
 | 
			
		||||
                  lang=${getLocale()}
 | 
			
		||||
                  date=${
 | 
			
		||||
                    `${
 | 
			
		||||
                      // NOTE older profiles may not have "modified" data
 | 
			
		||||
                      this.profile.modified || this.profile.created
 | 
			
		||||
                    }Z` /** Z for UTC */
 | 
			
		||||
                  }
 | 
			
		||||
                  month="2-digit"
 | 
			
		||||
                  day="2-digit"
 | 
			
		||||
                  year="2-digit"
 | 
			
		||||
                  hour="numeric"
 | 
			
		||||
                  minute="numeric"
 | 
			
		||||
                  time-zone-name="short"
 | 
			
		||||
                ></sl-format-date>`
 | 
			
		||||
              : nothing}
 | 
			
		||||
          </btrix-desc-list-item>
 | 
			
		||||
        </btrix-desc-list>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <div class="flex flex-col gap-5 lg:flex-row">
 | 
			
		||||
      <div class="mb-7 flex flex-col gap-5 lg:flex-row">
 | 
			
		||||
        <section class="flex-1">
 | 
			
		||||
          <h2 class="text-lg font-medium leading-none">
 | 
			
		||||
            ${msg("Browser Profile")}
 | 
			
		||||
@ -245,6 +246,46 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <section class="mb-7">
 | 
			
		||||
        <header class="flex items-center justify-between">
 | 
			
		||||
          <h2 class="mb-1 text-lg font-medium leading-none">
 | 
			
		||||
            ${msg("Description")}
 | 
			
		||||
          </h2>
 | 
			
		||||
          ${when(
 | 
			
		||||
            this.isCrawler,
 | 
			
		||||
            () => html`
 | 
			
		||||
              <sl-icon-button
 | 
			
		||||
                class="text-base"
 | 
			
		||||
                name="pencil"
 | 
			
		||||
                @click=${() => (this.isEditDialogOpen = true)}
 | 
			
		||||
                label=${msg("Edit description")}
 | 
			
		||||
              ></sl-icon-button>
 | 
			
		||||
            `,
 | 
			
		||||
          )}
 | 
			
		||||
        </header>
 | 
			
		||||
        <div class="rounded border p-5">
 | 
			
		||||
          ${this.profile
 | 
			
		||||
            ? this.profile.description ||
 | 
			
		||||
              html`
 | 
			
		||||
                <div class="text-center text-neutral-400">
 | 
			
		||||
                  ${msg("No description added.")}
 | 
			
		||||
                </div>
 | 
			
		||||
              `
 | 
			
		||||
            : nothing}
 | 
			
		||||
        </div>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <section class="mb-7">
 | 
			
		||||
        <h2 class="mb-2 text-lg font-medium leading-none">
 | 
			
		||||
          ${msg("Crawl Workflows")}${this.profile?.crawlconfigs?.length
 | 
			
		||||
            ? html`<span class="font-normal text-neutral-500">
 | 
			
		||||
                (${formatNumber(this.profile.crawlconfigs.length)})
 | 
			
		||||
              </span>`
 | 
			
		||||
            : nothing}
 | 
			
		||||
        </h2>
 | 
			
		||||
        ${this.renderCrawlWorkflows()}
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
      <btrix-dialog id="discardChangesDialog" .label=${msg("Cancel Editing?")}>
 | 
			
		||||
        ${msg(
 | 
			
		||||
          "Are you sure you want to discard changes to this browser profile?",
 | 
			
		||||
@ -280,6 +321,33 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
      </btrix-dialog> `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private renderCrawlWorkflows() {
 | 
			
		||||
    if (this.profile?.crawlconfigs?.length) {
 | 
			
		||||
      return html`<ul>
 | 
			
		||||
        ${this.profile.crawlconfigs.map(
 | 
			
		||||
          ({ id, name }) => html`
 | 
			
		||||
            <li
 | 
			
		||||
              class="border-x border-b first:rounded-t first:border-t last:rounded-b"
 | 
			
		||||
            >
 | 
			
		||||
              <a
 | 
			
		||||
                class="block p-2 transition-colors focus-within:bg-neutral-50 hover:bg-neutral-50"
 | 
			
		||||
                href=${`${this.navigate.orgBasePath}/workflows/crawl/${id}`}
 | 
			
		||||
                @click=${this.navigate.link}
 | 
			
		||||
              >
 | 
			
		||||
                ${name ||
 | 
			
		||||
                html`<span class="text-neutral-400">${msg("(no name)")}</span>`}
 | 
			
		||||
              </a>
 | 
			
		||||
            </li>
 | 
			
		||||
          `,
 | 
			
		||||
        )}
 | 
			
		||||
      </ul>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return html`<div class="rounded border p-5 text-center text-neutral-400">
 | 
			
		||||
      ${msg("Not used in any crawl workflows.")}
 | 
			
		||||
    </div>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private readonly renderVisitedSites = () => {
 | 
			
		||||
    return html`
 | 
			
		||||
      <section class="flex-grow-1 flex flex-col lg:w-[60ch]">
 | 
			
		||||
@ -356,6 +424,8 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
  private renderEditProfile() {
 | 
			
		||||
    if (!this.profile) return;
 | 
			
		||||
 | 
			
		||||
    const { helpText, validate } = this.validateDescriptionMax;
 | 
			
		||||
 | 
			
		||||
    return html`
 | 
			
		||||
      <form @submit=${this.onSubmitEdit}>
 | 
			
		||||
        <div class="mb-5">
 | 
			
		||||
@ -372,9 +442,12 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
          <sl-textarea
 | 
			
		||||
            name="description"
 | 
			
		||||
            label=${msg("Description")}
 | 
			
		||||
            rows="2"
 | 
			
		||||
            autocomplete="off"
 | 
			
		||||
            value=${this.profile.description || ""}
 | 
			
		||||
            rows="3"
 | 
			
		||||
            autocomplete="off"
 | 
			
		||||
            resize="auto"
 | 
			
		||||
            help-text=${helpText}
 | 
			
		||||
            @sl-input=${validate}
 | 
			
		||||
          ></sl-textarea>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -628,9 +701,10 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
  private async onSubmitEdit(e: SubmitEvent) {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    this.isSubmittingProfileChange = true;
 | 
			
		||||
    const formEl = e.target as HTMLFormElement;
 | 
			
		||||
    if (!(await this.checkFormValidity(formEl))) return;
 | 
			
		||||
 | 
			
		||||
    const formData = new FormData(e.target as HTMLFormElement);
 | 
			
		||||
    const formData = new FormData(formEl);
 | 
			
		||||
    const name = formData.get("name") as string;
 | 
			
		||||
    const description = formData.get("description") as string;
 | 
			
		||||
 | 
			
		||||
@ -639,6 +713,8 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
      description,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.isSubmittingProfileChange = true;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await this.api.fetch<{ updated: boolean }>(
 | 
			
		||||
        `/orgs/${this.orgId}/profiles/${this.profileId}`,
 | 
			
		||||
@ -687,12 +763,8 @@ export class BrowserProfilesDetail extends TailwindElement {
 | 
			
		||||
    this.isSubmittingProfileChange = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Stop propgation of sl-select events.
 | 
			
		||||
   * Prevents bug where sl-dialog closes when dropdown closes
 | 
			
		||||
   * https://github.com/shoelace-style/shoelace/issues/170
 | 
			
		||||
   */
 | 
			
		||||
  private stopProp(e: CustomEvent) {
 | 
			
		||||
    e.stopPropagation();
 | 
			
		||||
  async checkFormValidity(formEl: HTMLFormElement) {
 | 
			
		||||
    await this.updateComplete;
 | 
			
		||||
    return !formEl.querySelector("[data-invalid]");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { localized, msg } from "@lit/localize";
 | 
			
		||||
import { nothing } from "lit";
 | 
			
		||||
import { localized, msg, str } from "@lit/localize";
 | 
			
		||||
import { nothing, type PropertyValues } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators.js";
 | 
			
		||||
import { when } from "lit/directives/when.js";
 | 
			
		||||
import queryString from "query-string";
 | 
			
		||||
@ -8,12 +8,15 @@ import type { Profile } from "./types";
 | 
			
		||||
 | 
			
		||||
import type { SelectNewDialogEvent } from ".";
 | 
			
		||||
 | 
			
		||||
import type { APIPaginatedList } from "@/types/api";
 | 
			
		||||
import type { PageChangeEvent } from "@/components/ui/pagination";
 | 
			
		||||
import type { APIPaginatedList, APIPaginationQuery } from "@/types/api";
 | 
			
		||||
import type { Browser } from "@/types/browser";
 | 
			
		||||
import type { AuthState } from "@/utils/AuthService";
 | 
			
		||||
import LiteElement, { html } from "@/utils/LiteElement";
 | 
			
		||||
import { getLocale } from "@/utils/localization";
 | 
			
		||||
 | 
			
		||||
const INITIAL_PAGE_SIZE = 20;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Usage:
 | 
			
		||||
 * ```ts
 | 
			
		||||
@ -32,17 +35,29 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
  @property({ type: String })
 | 
			
		||||
  orgId!: string;
 | 
			
		||||
 | 
			
		||||
  @state()
 | 
			
		||||
  browserProfiles?: Profile[];
 | 
			
		||||
  @property({ type: Boolean })
 | 
			
		||||
  isCrawler = false;
 | 
			
		||||
 | 
			
		||||
  firstUpdated() {
 | 
			
		||||
  @state()
 | 
			
		||||
  browserProfiles?: APIPaginatedList<Profile>;
 | 
			
		||||
 | 
			
		||||
  protected willUpdate(
 | 
			
		||||
    changedProperties: PropertyValues<this> & Map<string, unknown>,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (changedProperties.has("orgId")) {
 | 
			
		||||
      void this.fetchBrowserProfiles();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`<header>
 | 
			
		||||
        <div class="mb-4 flex h-8 w-full justify-between">
 | 
			
		||||
          <h1 class="text-xl font-semibold">${msg("Browser Profiles")}</h1>
 | 
			
		||||
        <div class="mb-3 flex flex-wrap justify-between gap-2 border-b pb-3">
 | 
			
		||||
          <h1 class="mb-2 text-xl font-semibold leading-8 md:mb-0">
 | 
			
		||||
            ${msg("Browser Profiles")}
 | 
			
		||||
          </h1>
 | 
			
		||||
          ${when(
 | 
			
		||||
            this.isCrawler,
 | 
			
		||||
            () => html`
 | 
			
		||||
              <sl-button
 | 
			
		||||
                variant="primary"
 | 
			
		||||
                size="small"
 | 
			
		||||
@ -57,6 +72,8 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
                <sl-icon slot="prefix" name="plus-lg"></sl-icon>
 | 
			
		||||
                ${msg("New Browser Profile")}
 | 
			
		||||
              </sl-button>
 | 
			
		||||
            `,
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      </header>
 | 
			
		||||
      <div class="overflow-auto px-2 pb-1">${this.renderTable()}</div>`;
 | 
			
		||||
@ -65,17 +82,14 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
  private renderTable() {
 | 
			
		||||
    return html`
 | 
			
		||||
      <btrix-table
 | 
			
		||||
        style="grid-template-columns: min-content [clickable-start] 60ch repeat(2, auto) [clickable-end] min-content; --btrix-cell-padding-left: var(--sl-spacing-x-small); --btrix-cell-padding-right: var(--sl-spacing-x-small);"
 | 
			
		||||
        style="grid-template-columns: [clickable-start] 60ch repeat(2, auto) [clickable-end] min-content; --btrix-cell-padding-left: var(--sl-spacing-x-small); --btrix-cell-padding-right: var(--sl-spacing-x-small);"
 | 
			
		||||
      >
 | 
			
		||||
        <btrix-table-head class="mb-2">
 | 
			
		||||
          <btrix-table-header-cell>
 | 
			
		||||
            <span class="sr-only">${msg("Backed up status")}</span>
 | 
			
		||||
          </btrix-table-header-cell>
 | 
			
		||||
          <btrix-table-header-cell class="pl-0">
 | 
			
		||||
          <btrix-table-header-cell class="pl-3">
 | 
			
		||||
            ${msg("Name")}
 | 
			
		||||
          </btrix-table-header-cell>
 | 
			
		||||
          <btrix-table-header-cell>
 | 
			
		||||
            ${msg("Date Created")}
 | 
			
		||||
            ${msg("Last Updated")}
 | 
			
		||||
          </btrix-table-header-cell>
 | 
			
		||||
          <btrix-table-header-cell>
 | 
			
		||||
            ${msg("Visited URLs")}
 | 
			
		||||
@ -84,21 +98,34 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
            <span class="sr-only">${msg("Row Actions")}</span>
 | 
			
		||||
          </btrix-table-header-cell>
 | 
			
		||||
        </btrix-table-head>
 | 
			
		||||
        ${this.browserProfiles?.length
 | 
			
		||||
        ${when(this.browserProfiles, ({ total, items }) =>
 | 
			
		||||
          total
 | 
			
		||||
            ? html`
 | 
			
		||||
                <btrix-table-body
 | 
			
		||||
                  style="--btrix-row-gap: var(--sl-spacing-x-small); --btrix-cell-padding-top: var(--sl-spacing-2x-small); --btrix-cell-padding-bottom: var(--sl-spacing-2x-small);"
 | 
			
		||||
                >
 | 
			
		||||
                ${this.browserProfiles.map(this.renderItem)}
 | 
			
		||||
                  ${items.map(this.renderItem)}
 | 
			
		||||
                </btrix-table-body>
 | 
			
		||||
              `
 | 
			
		||||
          : nothing}
 | 
			
		||||
            : nothing,
 | 
			
		||||
        )}
 | 
			
		||||
      </btrix-table>
 | 
			
		||||
      ${when(
 | 
			
		||||
        this.browserProfiles,
 | 
			
		||||
        (browserProfiles) =>
 | 
			
		||||
          browserProfiles.length
 | 
			
		||||
            ? nothing
 | 
			
		||||
        ({ total, page, pageSize }) =>
 | 
			
		||||
          total
 | 
			
		||||
            ? html`
 | 
			
		||||
                <footer class="mt-6 flex justify-center">
 | 
			
		||||
                  <btrix-pagination
 | 
			
		||||
                    page=${page}
 | 
			
		||||
                    totalCount=${total}
 | 
			
		||||
                    size=${pageSize}
 | 
			
		||||
                    @page-change=${async (e: PageChangeEvent) => {
 | 
			
		||||
                      void this.fetchBrowserProfiles({ page: e.detail.page });
 | 
			
		||||
                    }}
 | 
			
		||||
                  ></btrix-pagination>
 | 
			
		||||
                </footer>
 | 
			
		||||
              `
 | 
			
		||||
            : html`
 | 
			
		||||
                <div class="border-b border-t py-5">
 | 
			
		||||
                  <p class="text-center text-0-500">
 | 
			
		||||
@ -117,24 +144,12 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
    </div>`;
 | 
			
		||||
 | 
			
		||||
  private readonly renderItem = (data: Profile) => {
 | 
			
		||||
    const isBackedUp =
 | 
			
		||||
      data.resource?.replicas && data.resource.replicas.length > 0;
 | 
			
		||||
    return html`
 | 
			
		||||
      <btrix-table-row
 | 
			
		||||
        class="cursor-pointer select-none rounded border shadow transition-all focus-within:bg-neutral-50 hover:bg-neutral-50 hover:shadow-none"
 | 
			
		||||
      >
 | 
			
		||||
        <btrix-table-cell class="p-3">
 | 
			
		||||
          <sl-tooltip
 | 
			
		||||
            content=${isBackedUp ? msg("Backed up") : msg("Not backed up")}
 | 
			
		||||
          >
 | 
			
		||||
            <sl-icon
 | 
			
		||||
              name=${isBackedUp ? "clouds-fill" : "cloud-slash-fill"}
 | 
			
		||||
              class="${isBackedUp ? "text-success" : "text-neutral-500"}"
 | 
			
		||||
            ></sl-icon>
 | 
			
		||||
          </sl-tooltip>
 | 
			
		||||
        </btrix-table-cell>
 | 
			
		||||
        <btrix-table-cell
 | 
			
		||||
          class="flex-col items-center justify-center pl-0"
 | 
			
		||||
          class="flex-col items-center justify-center pl-3"
 | 
			
		||||
          rowClickTarget="a"
 | 
			
		||||
        >
 | 
			
		||||
          <a
 | 
			
		||||
@ -144,16 +159,16 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
          >
 | 
			
		||||
            ${data.name}
 | 
			
		||||
          </a>
 | 
			
		||||
          ${data.description
 | 
			
		||||
            ? html`<div class="w-full text-xs text-neutral-500">
 | 
			
		||||
                <div class="truncate">${data.description}</div>
 | 
			
		||||
              </div>`
 | 
			
		||||
            : nothing}
 | 
			
		||||
        </btrix-table-cell>
 | 
			
		||||
        <btrix-table-cell class="whitespace-nowrap">
 | 
			
		||||
          <sl-format-date
 | 
			
		||||
            lang=${getLocale()}
 | 
			
		||||
            date=${`${data.created}Z`}
 | 
			
		||||
            date=${
 | 
			
		||||
              `${
 | 
			
		||||
                // NOTE older profiles may not have "modified" data
 | 
			
		||||
                data.modified || data.created
 | 
			
		||||
              }Z` /** Z for UTC */
 | 
			
		||||
            }
 | 
			
		||||
            month="2-digit"
 | 
			
		||||
            day="2-digit"
 | 
			
		||||
            year="2-digit"
 | 
			
		||||
@ -161,7 +176,18 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
            minute="2-digit"
 | 
			
		||||
          ></sl-format-date>
 | 
			
		||||
        </btrix-table-cell>
 | 
			
		||||
        <btrix-table-cell>${data.origins.join(", ")}</btrix-table-cell>
 | 
			
		||||
        <btrix-table-cell>
 | 
			
		||||
          ${data.origins[0]}${data.origins.length > 1
 | 
			
		||||
            ? html`<sl-tooltip
 | 
			
		||||
                class="invert-tooltip"
 | 
			
		||||
                content=${data.origins.slice(1).join(", ")}
 | 
			
		||||
              >
 | 
			
		||||
                <sl-tag size="small" class="ml-2">
 | 
			
		||||
                  ${msg(str`+${data.origins.length - 1}`)}
 | 
			
		||||
                </sl-tag>
 | 
			
		||||
              </sl-tooltip>`
 | 
			
		||||
            : nothing}
 | 
			
		||||
        </btrix-table-cell>
 | 
			
		||||
        <btrix-table-cell class="px-1">
 | 
			
		||||
          ${this.renderActions(data)}
 | 
			
		||||
        </btrix-table-cell>
 | 
			
		||||
@ -256,9 +282,7 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
          icon: "check2-circle",
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.browserProfiles = this.browserProfiles!.filter(
 | 
			
		||||
          (p) => p.id !== profile.id,
 | 
			
		||||
        );
 | 
			
		||||
        void this.fetchBrowserProfiles();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      this.notify({
 | 
			
		||||
@ -287,9 +311,17 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
  /**
 | 
			
		||||
   * Fetch browser profiles and update internal state
 | 
			
		||||
   */
 | 
			
		||||
  private async fetchBrowserProfiles(): Promise<void> {
 | 
			
		||||
  private async fetchBrowserProfiles(
 | 
			
		||||
    params?: APIPaginationQuery,
 | 
			
		||||
  ): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await this.getProfiles();
 | 
			
		||||
      const data = await this.getProfiles({
 | 
			
		||||
        page: params?.page || this.browserProfiles?.page || 1,
 | 
			
		||||
        pageSize:
 | 
			
		||||
          params?.pageSize ||
 | 
			
		||||
          this.browserProfiles?.pageSize ||
 | 
			
		||||
          INITIAL_PAGE_SIZE,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      this.browserProfiles = data;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
@ -301,12 +333,16 @@ export class BrowserProfilesList extends LiteElement {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async getProfiles() {
 | 
			
		||||
  private async getProfiles(params: APIPaginationQuery) {
 | 
			
		||||
    const query = queryString.stringify(params, {
 | 
			
		||||
      arrayFormat: "comma",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const data = await this.apiFetch<APIPaginatedList<Profile>>(
 | 
			
		||||
      `/orgs/${this.orgId}/profiles`,
 | 
			
		||||
      `/orgs/${this.orgId}/profiles?${query}`,
 | 
			
		||||
      this.authState!,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return data.items;
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -659,6 +659,7 @@ export class Org extends LiteElement {
 | 
			
		||||
    return html`<btrix-browser-profiles-list
 | 
			
		||||
      .authState=${this.authState!}
 | 
			
		||||
      .orgId=${this.orgId}
 | 
			
		||||
      ?isCrawler=${this.isCrawler}
 | 
			
		||||
      @select-new-dialog=${this.onSelectNewDialog}
 | 
			
		||||
    ></btrix-browser-profiles-list>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -98,6 +98,9 @@ export type Profile = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  description: string;
 | 
			
		||||
  created: string;
 | 
			
		||||
  createdByName: string | null;
 | 
			
		||||
  modified: string | null;
 | 
			
		||||
  modifiedByName: string | null;
 | 
			
		||||
  origins: string[];
 | 
			
		||||
  profileId: string;
 | 
			
		||||
  baseProfileName: string;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user