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

View File

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

View File

@ -88,7 +88,11 @@ export class ExclusionEditor extends LiteElement {
private renderTable() {
return html`
${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>`
: html`
<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() {
if (!this.regex) {
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 }) => {
customElements.define("btrix-badge", Badge);
});
import("./icon-button").then(({ IconButton }) => {
customElements.define("btrix-icon-button", IconButton);
});
customElements.define("btrix-alert", Alert);
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 })
totalCount: number = 0;
@ -95,9 +98,6 @@ export class Pagination extends LitElement {
@state()
private inputValue = "";
@state()
private page: number = 1;
@state()
private pages = 0;

View File

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

View File

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