Remove exclusion from running crawl (#352)
This commit is contained in:
parent
793611e5bb
commit
d41b582ef6
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
131
frontend/src/components/icon-button.ts
Normal file
131
frontend/src/components/icon-button.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user