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