Remove exclusion from running crawl (#352)

This commit is contained in:
sua yoo 2022-11-14 12:58:33 -06:00 committed by GitHub
parent 793611e5bb
commit d41b582ef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 296 additions and 40 deletions

View File

@ -77,7 +77,7 @@ export class CrawlQueue extends LiteElement {
render() { render() {
return html` return html`
<btrix-details open disabled> <btrix-details open>
<span slot="title"> ${msg("Crawl Queue")} ${this.renderBadge()} </span> <span slot="title"> ${msg("Crawl Queue")} ${this.renderBadge()} </span>
<div slot="summary-description"> <div slot="summary-description">
${this.queue?.total && this.queue.total > this.pageSize ${this.queue?.total && this.queue.total > this.pageSize
@ -139,8 +139,12 @@ export class CrawlQueue extends LiteElement {
<footer class="text-center"> <footer class="text-center">
<span class="text-xs text-neutral-400" aria-live="polite"> <span class="text-xs text-neutral-400" aria-live="polite">
${msg( ${msg(
str`${((this.page - 1) * this.pageSize + 1).toLocaleString()}${( str`${(
this.page * this.pageSize (this.page - 1) * this.pageSize +
1
).toLocaleString()}${Math.min(
this.page * this.pageSize,
this.queue.total
).toLocaleString()} of ${this.queue.total.toLocaleString()} URLs` ).toLocaleString()} of ${this.queue.total.toLocaleString()} URLs`
)} )}
</span> </span>

View File

@ -30,9 +30,8 @@ export class Details extends LitElement {
} }
summary { summary {
border-bottom: 1px solid var(--sl-panel-border-color);
color: var(--sl-color-neutral-500); color: var(--sl-color-neutral-500);
margin-bottom: var(--sl-spacing-x-small); margin-bottom: var(--sl-spacing-2x-small);
line-height: 1; line-height: 1;
display: flex; display: flex;
align-items: center; align-items: center;
@ -40,6 +39,8 @@ export class Details extends LitElement {
} }
details[aria-disabled="false"] summary { details[aria-disabled="false"] summary {
border-bottom: 1px solid var(--sl-panel-border-color);
margin-bottom: var(--sl-spacing-x-small);
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }

View File

@ -88,7 +88,11 @@ export class ExclusionEditor extends LiteElement {
private renderTable() { private renderTable() {
return html` return html`
${this.config ${this.config
? html`<btrix-queue-exclusion-table .config=${this.config}> ? html`<btrix-queue-exclusion-table
?isActiveCrawl=${this.isActiveCrawl}
.config=${this.config}
@on-remove=${this.deleteExclusion}
>
</btrix-queue-exclusion-table>` </btrix-queue-exclusion-table>`
: html` : html`
<div class="flex items-center justify-center my-9 text-xl"> <div class="flex items-center justify-center my-9 text-xl">
@ -137,6 +141,45 @@ export class ExclusionEditor extends LiteElement {
} }
} }
private async deleteExclusion(e: CustomEvent) {
const { value } = e.detail;
try {
const data = await this.apiFetch(
`/archives/${this.archiveId}/crawls/${this.crawlId}/exclusions?regex=${value}`,
this.authState!,
{
method: "DELETE",
}
);
if (data.new_cid) {
this.notify({
message: msg(html`Removed exclusion: <code>${value}</code>`),
type: "success",
icon: "check2-circle",
});
this.dispatchEvent(
new CustomEvent("on-success", {
detail: { cid: data.new_cid },
})
);
} else {
throw data;
}
} catch (e: any) {
this.notify({
message:
e.message === "crawl_running_cant_deactivate"
? msg("Cannot remove exclusion when crawl is no longer running.")
: msg("Sorry, couldn't remove exclusion at this time."),
type: "danger",
icon: "exclamation-octagon",
});
}
}
private async fetchQueueMatches() { private async fetchQueueMatches() {
if (!this.regex) { if (!this.regex) {
this.matchedURLs = null; this.matchedURLs = null;

View File

@ -0,0 +1,131 @@
import { LitElement, html, css } from "lit";
import { property } from "lit/decorators.js";
/**
* Button with single icon.
* Icons names from https://shoelace.style/components/icon
*
* Usage example:
* ```ts
* <btrix-icon-button name="plus-lg"></btrix-icon-button>
* ```
*/
export class IconButton extends LitElement {
@property({ type: String })
name: string = "square";
@property({ type: String })
type: "submit" | "button" = "button";
@property({ type: String })
variant: "primary" | "danger" | "neutral" = "neutral";
@property({ type: Boolean })
disabled: boolean = false;
@property({ type: Boolean })
loading: boolean = false;
static styles = css`
:host {
display: inline-block;
}
button {
all: unset;
display: block;
width: 1.5rem;
height: 1.5rem;
padding: 0.25rem;
border-radius: var(--sl-border-radius-small);
box-sizing: border-box;
text-align: center;
cursor: pointer;
transform: translateY(0px);
transition: background-color 0.15s, box-shadow 0.15s, color 0.15s,
transform 0.15s;
}
button[disabled] {
cursor: not-allowed;
background-color: var(--sl-color-neutral-100) !important;
color: var(--sl-color-neutral-300) !important;
}
sl-icon {
display: block;
font-size: 1rem;
}
.primary,
.danger {
box-shadow: var(--sl-shadow-x-small);
}
.primary:not([disabled]):hover,
.dangery:not([disabled]):hover {
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.1);
transform: translateY(1px);
}
.primary {
background-color: var(--sl-color-blue-50);
color: var(--sl-color-blue-600);
}
.primary:hover {
background-color: var(--sl-color-blue-100);
}
.danger {
background-color: var(--sl-color-danger-50);
color: var(--sl-color-danger-600);
}
.danger:hover {
background-color: var(--sl-color-danger-100);
}
.neutral {
color: var(--sl-color-neutral-500);
}
.neutral:hover {
color: var(--sl-color-blue-500);
}
`;
render() {
return html`<button
type="submit"
class=${this.variant}
?disabled=${this.disabled}
@click=${this.handleClick}
>
${this.loading
? html`<sl-spinner></sl-spinner>`
: html`<sl-icon name=${this.name}></sl-icon>`}
</button>`;
}
private handleClick(e: MouseEvent) {
if (this.disabled || this.loading) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this.type === "submit") {
this.submit();
}
}
private submit() {
const form = (this.closest("form") ||
this.closest("sl-form")) as HTMLFormElement;
if (form) {
form.submit();
}
}
}

View File

@ -69,6 +69,9 @@ import("./crawl-pending-exclusions").then(({ CrawlPendingExclusions }) => {
import("./badge").then(({ Badge }) => { import("./badge").then(({ Badge }) => {
customElements.define("btrix-badge", Badge); customElements.define("btrix-badge", Badge);
}); });
import("./icon-button").then(({ IconButton }) => {
customElements.define("btrix-icon-button", IconButton);
});
customElements.define("btrix-alert", Alert); customElements.define("btrix-alert", Alert);
customElements.define("btrix-input", Input); customElements.define("btrix-input", Input);

View File

@ -86,6 +86,9 @@ export class Pagination extends LitElement {
} }
`; `;
@property({ type: Number })
page: number = 1;
@property({ type: Number }) @property({ type: Number })
totalCount: number = 0; totalCount: number = 0;
@ -95,9 +98,6 @@ export class Pagination extends LitElement {
@state() @state()
private inputValue = ""; private inputValue = "";
@state()
private page: number = 1;
@state() @state()
private pages = 0; private pages = 0;

View File

@ -59,7 +59,7 @@ export class QueueExclusionForm extends LiteElement {
return html` return html`
<sl-form @sl-submit=${this.onSubmit}> <sl-form @sl-submit=${this.onSubmit}>
<div class="flex"> <div class="flex">
<div class="pr-1 flex-0 w-40"> <div class="px-1 flex-0 w-40">
<sl-select <sl-select
name="excludeType" name="excludeType"
placeholder=${msg("Select Type")} placeholder=${msg("Select Type")}
@ -73,8 +73,8 @@ export class QueueExclusionForm extends LiteElement {
<sl-menu-item value="regex">${msg("Regex")}</sl-menu-item> <sl-menu-item value="regex">${msg("Regex")}</sl-menu-item>
</sl-select> </sl-select>
</div> </div>
<div class="pl-1 flex-1 md:flex"> <div class="pl-1 flex-1 flex">
<div class="flex-1 mb-2 md:mb-0 md:mr-2"> <div class="flex-1 mr-1 mb-2 md:mb-0">
<sl-input <sl-input
class=${this.fieldErrorMessage ? "invalid" : ""} class=${this.fieldErrorMessage ? "invalid" : ""}
name="excludeValue" name="excludeValue"
@ -122,15 +122,15 @@ export class QueueExclusionForm extends LiteElement {
: ""} : ""}
</sl-input> </sl-input>
</div> </div>
<div class="flex-0"> <div class="flex-0 w-10 pt-1 text-center">
<sl-button <btrix-icon-button
type="primary" type="submit"
size="small" variant="primary"
submit name="plus-lg"
?disabled=${this.isRegexInvalid || this.isSubmitting} ?disabled=${this.isRegexInvalid || this.isSubmitting}
?loading=${this.isSubmitting} ?loading=${this.isSubmitting}
>${msg("Add Exclusion")}</sl-button
> >
</btrix-icon-button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -18,12 +18,17 @@ import type { Exclusion } from "./queue-exclusion-form";
* > * >
* </btrix-queue-exclusion-table> * </btrix-queue-exclusion-table>
* ``` * ```
*
* @event on-remove { value: string; }
*/ */
@localized() @localized()
export class QueueExclusionTable extends LiteElement { export class QueueExclusionTable extends LiteElement {
@property({ type: Array }) @property({ type: Array })
config?: CrawlConfig; config?: CrawlConfig;
@property({ type: Boolean })
isActiveCrawl = false;
@state() @state()
private results: Exclusion[] = []; private results: Exclusion[] = [];
@ -34,11 +39,27 @@ export class QueueExclusionTable extends LiteElement {
private pageSize: number = 5; private pageSize: number = 5;
@state() @state()
private total?: number; private exclusionToRemove?: string;
private get total() {
return this.config?.exclude?.length;
}
willUpdate(changedProperties: Map<string, any>) { willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has("config") && this.config?.exclude) { if (changedProperties.has("config") && this.config?.exclude) {
this.total = this.config.exclude.length; this.exclusionToRemove = "";
const prevConfig = changedProperties.get("config");
if (prevConfig) {
const prevTotal = prevConfig.exclude?.length;
const lastPage = Math.ceil(this.total! / this.pageSize);
if (this.total! < prevTotal) {
this.page = Math.min(this.page, lastPage);
} else if (this.total! > prevTotal) {
this.page = lastPage;
}
}
this.updatePageResults(); this.updatePageResults();
} else if (changedProperties.has("page")) { } else if (changedProperties.has("page")) {
this.updatePageResults(); this.updatePageResults();
@ -60,11 +81,15 @@ export class QueueExclusionTable extends LiteElement {
} }
render() { render() {
const [typeColClass, valueColClass, actionColClass] =
this.getColumnClassNames(0, this.results.length);
return html`<btrix-details open disabled> return html`<btrix-details open disabled>
<h4 slot="title">${msg("Exclusion Table")}</h4> <h4 slot="title">${msg("Exclusion Table")}</h4>
<div slot="summary-description"> <div slot="summary-description">
${this.total && this.total > this.pageSize ${this.total && this.total > this.pageSize
? html`<btrix-pagination ? html`<btrix-pagination
page=${this.page}
size=${this.pageSize} size=${this.pageSize}
totalCount=${this.total} totalCount=${this.total}
@page-change=${(e: CustomEvent) => { @page-change=${(e: CustomEvent) => {
@ -78,13 +103,20 @@ export class QueueExclusionTable extends LiteElement {
class="w-full leading-none border-separate" class="w-full leading-none border-separate"
style="border-spacing: 0;" style="border-spacing: 0;"
> >
<thead class="text-xs text-neutral-700"> <thead class="text-xs font-mono text-neutral-600 uppercase">
<tr class="text-left"> <tr class="h-10 text-left">
<th class="font-normal px-2 pb-1 w-40">${msg("Exclusion Type")}</th> <th class="font-normal px-2 w-40 bg-slate-50 ${typeColClass}">
<th class="font-normal px-2 pb-1">${msg("Exclusion Value")}</th> ${msg("Exclusion Type")}
</th>
<th class="font-normal px-2 bg-slate-50 ${valueColClass}">
${msg("Exclusion Value")}
</th>
<th class="font-normal px-2 w-10 bg-slate-50 ${actionColClass}">
<span class="sr-only">Row actions</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-neutral-600"> <tbody>
${this.results.map(this.renderItem)} ${this.results.map(this.renderItem)}
</tbody> </tbody>
</table> </table>
@ -96,17 +128,8 @@ export class QueueExclusionTable extends LiteElement {
index: number, index: number,
arr: Exclusion[] arr: Exclusion[]
) => { ) => {
let typeColClass = ""; const [typeColClass, valueColClass, actionColClass] =
let valueColClass = ""; this.getColumnClassNames(index + 1, arr.length);
if (index === 0) {
typeColClass = " rounded-tl";
valueColClass = " rounded-tr";
}
if (index === arr.length - 1) {
typeColClass = " border-b rounded-bl";
valueColClass = " border-b rounded-br";
}
let typeLabel: string = exclusion.type; let typeLabel: string = exclusion.type;
let value: any = exclusion.value; let value: any = exclusion.value;
@ -126,14 +149,65 @@ export class QueueExclusionTable extends LiteElement {
} }
return html` return html`
<tr class="even:bg-neutral-50 h-8"> <tr
<td class="border-t border-x p-2 whitespace-nowrap${typeColClass}"> class="h-10 ${this.exclusionToRemove === value
? "text-neutral-200"
: "text-neutral-600"}"
>
<td class="py-2 px-3 whitespace-nowrap ${typeColClass}">
${typeLabel} ${typeLabel}
</td> </td>
<td class="border-t border-r p-2 font-mono${valueColClass}"> <td class="p-2 font-mono ${valueColClass}">${value}</td>
${value} <td class="text-[1rem] text-center ${actionColClass}">
<btrix-icon-button
name="trash"
@click=${() => this.removeExclusion(exclusion)}
></btrix-icon-button>
</td> </td>
</tr> </tr>
`; `;
}; };
private getColumnClassNames(index: number, count: number) {
let typeColClass = "border-t border-x";
let valueColClass = "border-t border-r";
let actionColClass = "border-t border-r";
if (index === 0) {
typeColClass += " rounded-tl";
if (this.isActiveCrawl) {
actionColClass += " rounded-tr";
} else {
valueColClass += " rounded-tr";
}
}
if (index === count) {
typeColClass += " border-b rounded-bl";
if (this.isActiveCrawl) {
valueColClass += " border-b";
actionColClass += " border-b rounded-br";
} else {
valueColClass += " border-b rounded-br";
}
}
if (!this.isActiveCrawl) {
actionColClass += " hidden";
}
return [typeColClass, valueColClass, actionColClass];
}
private removeExclusion(exclusion: Exclusion) {
this.exclusionToRemove = exclusion.value;
this.dispatchEvent(
new CustomEvent("on-remove", {
detail: exclusion,
})
);
}
} }