Bugfix: Examine Management Dashboard Status+Modals

This commit is contained in:
Lone Iversen
2024-05-07 16:41:52 +02:00
parent 3d307e0fa0
commit f349b5bda7
18 changed files with 253 additions and 168 deletions

View File

@@ -979,6 +979,11 @@ export enum HealthStatusModel {
REBUILDING = 'Rebuilding'
}
export type HealthStatusResponseModel = {
status: HealthStatusModel
message?: string | null
};
export type HelpPageResponseModel = {
name?: string | null
description?: string | null
@@ -993,7 +998,7 @@ parent?: ReferenceByIdModel | null
export type IndexResponseModel = {
name: string
healthStatus: HealthStatusModel
healthStatus: HealthStatusResponseModel
canRebuild: boolean
searcherName: string
documentCount: number
@@ -5236,15 +5241,15 @@ PostWebhook: {
GetWebhookById: {
id: string
};
DeleteWebhookById: {
id: string
};
PutWebhookById: {
id: string
requestBody?: UpdateWebhookRequestModel
};
DeleteWebhookById: {
id: string
};
GetWebhookEvents: {
skip?: number
@@ -5259,8 +5264,8 @@ take?: number
,GetWebhook: PagedWebhookResponseModel
,PostWebhook: string
,GetWebhookById: WebhookResponseModel
,PutWebhookById: string
,DeleteWebhookById: string
,PutWebhookById: string
,GetWebhookEvents: PagedWebhookEventModel
}

View File

@@ -9022,20 +9022,17 @@ take
* @returns string Success
* @throws ApiError
*/
public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise<WebhookData['responses']['PutWebhookById']> {
public static deleteWebhookById(data: WebhookData['payloads']['DeleteWebhookById']): CancelablePromise<WebhookData['responses']['DeleteWebhookById']> {
const {
id,
requestBody
id
} = data;
return __request(OpenAPI, {
method: 'PUT',
method: 'DELETE',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
id
},
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Notifications',
errors: {
400: `Bad Request`,
@@ -9050,17 +9047,20 @@ requestBody
* @returns string Success
* @throws ApiError
*/
public static deleteWebhookById(data: WebhookData['payloads']['DeleteWebhookById']): CancelablePromise<WebhookData['responses']['DeleteWebhookById']> {
public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise<WebhookData['responses']['PutWebhookById']> {
const {
id
id,
requestBody
} = data;
return __request(OpenAPI, {
method: 'DELETE',
method: 'PUT',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
id
},
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Notifications',
errors: {
400: `Bad Request`,

View File

@@ -4,7 +4,6 @@ export * from './confirm-modal.token.js';
export * from './debug-modal.token.js';
export * from './embedded-media-modal.token.js';
export * from './entity-user-permission-settings-modal.token.js';
export * from './examine-fields-settings-modal.token.js';
export * from './icon-picker-modal.token.js';
export * from './item-picker-modal.token.js';
export * from './link-picker-modal.token.js';

View File

@@ -0,0 +1 @@
export * from './modal/index.js';

View File

@@ -0,0 +1,5 @@
import { manifests as modalManifests } from './modal/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes> = [...modalManifests];

View File

@@ -0,0 +1,73 @@
import type {
UmbExamineFieldsSettingsModalData,
UmbExamineFieldsSettingsModalValue,
FieldSettingsType,
} from './examine-fields-settings-modal.token.js';
import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
@customElement('umb-examine-fields-settings-modal')
export class UmbExamineFieldsSettingsModalElement extends UmbModalBaseElement<
UmbExamineFieldsSettingsModalData,
UmbExamineFieldsSettingsModalValue
> {
render() {
return html`<umb-body-layout headline=${this.localize.term('examineManagement_fields')}>
<uui-scroll-container id="field-settings"> ${this.#renderFields()} </uui-scroll-container>
<div slot="actions">
<uui-button look="primary" label="Close sidebar" @click="${this._submitModal}">Close</uui-button>
</div>
</umb-body-layout>`;
}
#setExposed(fieldSetting: FieldSettingsType) {
const newField: FieldSettingsType = { ...fieldSetting, exposed: !fieldSetting.exposed };
const updatedFields =
this.modalContext?.getValue().fields.map((field) => {
if (field.name === fieldSetting.name) return newField;
else return field;
}) ?? [];
this.modalContext?.updateValue({ fields: updatedFields });
}
#renderFields() {
if (!this.value.fields.length) return;
return html`<span>
${Object.values(this.value.fields).map((field) => {
return html`<uui-toggle
name="${field.name}"
label="${field.name}"
.checked="${field.exposed}"
@change="${() => this.#setExposed(field)}"></uui-toggle>
<br />`;
})}
</span>`;
}
static styles = [
UmbTextStyles,
css`
:host {
display: relative;
}
uui-scroll-container {
overflow-y: scroll;
max-height: 100%;
min-height: 0;
flex: 1;
}
`,
];
}
export default UmbExamineFieldsSettingsModalElement;
declare global {
interface HTMLElementTagNameMap {
'umb-examine-fields-settings-modal': UmbExamineFieldsSettingsModalElement;
}
}

View File

@@ -1,8 +1,8 @@
import { UmbModalToken } from './modal-token.js';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export type UmbExamineFieldsSettingsModalData = never;
type FieldSettingsType = {
export type FieldSettingsType = {
name: string;
exposed: boolean;
};
@@ -14,7 +14,7 @@ export type UmbExamineFieldsSettingsModalValue = {
export const UMB_EXAMINE_FIELDS_SETTINGS_MODAL = new UmbModalToken<
UmbExamineFieldsSettingsModalData,
UmbExamineFieldsSettingsModalValue
>('Umb.Modal.ExamineFieldsSettings', {
>('Umb.Modal.Examine.FieldsSettings', {
modal: {
type: 'sidebar',
size: 'small',

View File

@@ -0,0 +1,2 @@
export * from './examine-fields-settings-modal.element.js';
export * from './examine-fields-settings-modal.token.js';

View File

@@ -1,11 +1,15 @@
import type {
UmbExamineFieldsViewerModalData,
UmbExamineFieldsViewerModalValue,
} from './examine-fields-viewer-modal.token.js';
import { html, css, nothing, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
@customElement('umb-modal-element-fields-viewer')
export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
SearchResultResponseModel & { name: string }
@customElement('umb-examine-fields-viewer-modal')
export class UmbExamineFieldsViewerModalElement extends UmbModalBaseElement<
UmbExamineFieldsViewerModalData,
UmbExamineFieldsViewerModalValue
> {
private _handleClose() {
this.modalContext?.reject();
@@ -15,7 +19,7 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
if (!this.data) return nothing;
return html`
<uui-dialog-layout class="uui-text" headline="${this.data.name}">
<umb-body-layout headline="${this.data?.name}">
<uui-scroll-container id="field-viewer">
<span>
<uui-table>
@@ -23,7 +27,7 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
<uui-table-head-cell> Field </uui-table-head-cell>
<uui-table-head-cell> Value </uui-table-head-cell>
</uui-table-head>
${Object.values(this.data.fields ?? []).map((cell) => {
${Object.values(this.data.searchResult.fields ?? []).map((cell) => {
return html`<uui-table-row>
<uui-table-cell> ${cell.name} </uui-table-cell>
<uui-table-cell> ${cell.values?.join(', ')} </uui-table-cell>
@@ -32,10 +36,13 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
</uui-table>
</span>
</uui-scroll-container>
<div>
<uui-button look="primary" @click="${this._handleClose}">Close</uui-button>
<div slot="actions">
<uui-button
look="primary"
label=${this.localize.term('general_close')}
@click=${this._rejectModal}></uui-button>
</div>
</uui-dialog-layout>
</umb-body-layout>
`;
}
@@ -45,11 +52,6 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
:host {
display: relative;
}
uui-dialog-layout {
display: flex;
flex-direction: column;
height: 100%;
}
span {
display: block;
@@ -57,22 +59,18 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
}
uui-scroll-container {
line-height: 0;
overflow-y: scroll;
max-height: 100%;
min-height: 0;
}
div {
margin-top: var(--uui-size-space-5);
display: flex;
flex-direction: row-reverse;
}
`,
];
}
export default UmbExamineFieldsViewerModalElement;
declare global {
interface HTMLElementTagNameMap {
'umb-modal-element-fields-viewer': UmbModalElementFieldsViewerElement;
'umb-examine-fields-viewer-modal': UmbExamineFieldsViewerModalElement;
}
}

View File

@@ -0,0 +1,19 @@
import type { SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export type UmbExamineFieldsViewerModalData = {
name: string;
searchResult: SearchResultResponseModel;
};
export type UmbExamineFieldsViewerModalValue = never;
export const UMB_EXAMINE_FIELDS_VIEWER_MODAL = new UmbModalToken<
UmbExamineFieldsViewerModalData,
UmbExamineFieldsViewerModalValue
>('Umb.Modal.Examine.FieldsViewer', {
modal: {
type: 'sidebar',
size: 'small',
},
});

View File

@@ -0,0 +1,2 @@
export * from './examine-fields-viewer-modal.element.js';
export * from './examine-fields-viewer-modal.token.js';

View File

@@ -0,0 +1,2 @@
export * from './fields-settings/index.js';
export * from './fields-viewer/index.js';

View File

@@ -0,0 +1,18 @@
import type { ManifestModal, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const modals: Array<ManifestModal> = [
{
type: 'modal',
alias: 'Umb.Modal.Examine.FieldsSettings',
name: 'Examine Field Settings Modal',
js: () => import('./fields-settings/examine-fields-settings-modal.element.js'),
},
{
type: 'modal',
alias: 'Umb.Modal.Examine.FieldsViewer',
name: 'Examine Field Viewer Modal',
js: () => import('./fields-viewer/examine-fields-viewer-modal.element.js'),
},
];
export const manifests: Array<ManifestTypes> = [...modals];

View File

@@ -1,81 +0,0 @@
import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
UmbExamineFieldsSettingsModalValue,
UmbExamineFieldsSettingsModalData} from '@umbraco-cms/backoffice/modal';
import {
UmbModalBaseElement,
} from '@umbraco-cms/backoffice/modal';
@customElement('umb-examine-fields-settings-modal')
export default class UmbExamineFieldsSettingsModalElement extends UmbModalBaseElement<
UmbExamineFieldsSettingsModalData,
UmbExamineFieldsSettingsModalValue
> {
render() {
if (this.value.fields) {
return html`
<uui-dialog-layout headline="Show fields">
<uui-scroll-container id="field-settings">
<span>
${Object.values(this.value.fields).map((field, index) => {
return html`<uui-toggle
name="${field.name}"
label="${field.name}"
.checked="${field.exposed}"
@change="${() => {
this.value.fields ? (this.value.fields[index].exposed = !field.exposed) : '';
}}"></uui-toggle>
<br />`;
})}
</span>
</uui-scroll-container>
<div>
<uui-button look="primary" label="Close sidebar" @click="${this._submitModal}">Close</uui-button>
</div>
</uui-dialog-layout>
`;
} else {
return '';
}
}
static styles = [
UmbTextStyles,
css`
:host {
display: relative;
}
uui-dialog-layout {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--uui-color-surface);
box-shadow: var(--uui-shadow-depth-1, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24));
border-radius: var(--uui-border-radius);
padding: var(--uui-size-space-5);
box-sizing: border-box;
}
uui-scroll-container {
overflow-y: scroll;
max-height: 100%;
min-height: 0;
flex: 1;
}
div {
margin-top: var(--uui-size-space-5);
display: flex;
flex-direction: row-reverse;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
'umb-examine-fields-settings-modal': UmbExamineFieldsSettingsModalElement;
}
}

View File

@@ -1,7 +1,7 @@
import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import type { IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { HealthStatusResponseModel, IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { HealthStatusModel, IndexerService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
@@ -36,7 +36,7 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
this._indexData = data;
// TODO: Add continuous polling to update the status
if (this._indexData?.healthStatus === HealthStatusModel.REBUILDING) {
if (this._indexData?.healthStatus.status === HealthStatusModel.REBUILDING) {
this._buttonState = 'waiting';
}
@@ -73,6 +73,20 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
await this._getIndexData();
}
#renderHealthStatus(healthStatus: HealthStatusResponseModel) {
const msg = healthStatus.message ? healthStatus.message : healthStatus.status;
switch (healthStatus.status) {
case HealthStatusModel.HEALTHY:
return html`<umb-icon name="icon-check color-green"></umb-icon>${msg}`;
case HealthStatusModel.UNHEALTHY:
return html`<umb-icon name="icon-error color-red"></umb-icon>${msg}`;
case HealthStatusModel.REBUILDING:
return html`<umb-icon name="icon-time color-yellow"></umb-icon>${msg}`;
default:
return;
}
}
render() {
if (!this._indexData || this._loading) return html` <uui-loader-bar></uui-loader-bar>`;
@@ -82,24 +96,14 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
<strong>Health Status</strong><br />
The health status of the ${this.indexName} and if it can be read
</p>
<div>
<uui-icon-essentials>
${
this._indexData.healthStatus === HealthStatusModel.UNHEALTHY
? html`<uui-icon name="wrong" class="danger"></uui-icon>`
: html`<uui-icon name="check" class="positive"></uui-icon>`
}
</uui-icon>
</uui-icon-essentials>
${this._indexData.healthStatus}
</div>
<div id="health-status">${this.#renderHealthStatus(this._indexData.healthStatus)}</div>
</uui-box>
${this.renderIndexSearch()} ${this.renderPropertyList()} ${this.renderTools()}
`;
}
private renderIndexSearch() {
if (!this._indexData || this._indexData.healthStatus !== HealthStatusModel.HEALTHY) return nothing;
if (!this._indexData || this._indexData.healthStatus.status !== HealthStatusModel.HEALTHY) return nothing;
return html`<umb-dashboard-examine-searcher .searcherName="${this.indexName}"></umb-dashboard-examine-searcher>`;
}
@@ -147,6 +151,11 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
static styles = [
UmbTextStyles,
css`
#health-status {
display: flex;
gap: var(--uui-size-6);
}
:host {
display: block;
}
@@ -190,13 +199,6 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
padding-right: var(--uui-size-space-5);
}
.positive {
color: var(--uui-color-positive);
}
.danger {
color: var(--uui-color-danger);
}
button {
background: none;
border: none;

View File

@@ -40,6 +40,19 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement {
this._loadingSearchers = false;
}
#renderStatus(status: HealthStatusModel) {
switch (status) {
case HealthStatusModel.HEALTHY:
return html`<umb-icon name="icon-check color-green"></umb-icon>`;
case HealthStatusModel.UNHEALTHY:
return html`<umb-icon name="icon-error color-red"></umb-icon>`;
case HealthStatusModel.REBUILDING:
return html`<umb-icon name="icon-time color-yellow"></umb-icon>`;
default:
return;
}
}
render() {
return html`
<uui-box headline="Indexers" class="overview">
@@ -66,16 +79,7 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement {
${this._indexers.map((index) => {
return html`
<uui-table-row>
<uui-table-cell style="width:0px">
<uui-icon-essentials>
${
index.healthStatus === HealthStatusModel.UNHEALTHY
? html`<uui-icon name="wrong" class="danger"></uui-icon>`
: html`<uui-icon name="check" class="positive"></uui-icon>`
}
</uui-icon>
</uui-icon-essentials>
</uui-table-cell>
<uui-table-cell style="width:0px"> ${this.#renderStatus(index.healthStatus.status)} </uui-table-cell>
<uui-table-cell>
<a href="${window.location.href.replace(/\/+$/, '')}/index/${index.name}">${index.name}</a>
</uui-table-cell>

View File

@@ -1,14 +1,16 @@
import { UMB_EXAMINE_FIELDS_SETTINGS_MODAL, UMB_EXAMINE_FIELDS_VIEWER_MODAL } from '../modal/index.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, state, query, property } from '@umbraco-cms/backoffice/external/lit';
import { UMB_MODAL_MANAGER_CONTEXT, UMB_EXAMINE_FIELDS_SETTINGS_MODAL } from '@umbraco-cms/backoffice/modal';
import {
UMB_MODAL_MANAGER_CONTEXT,
UMB_WORKSPACE_MODAL,
UmbModalRouteRegistrationController,
} from '@umbraco-cms/backoffice/modal';
import type { SearchResultResponseModel, FieldPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
import { SearcherService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import './modal-views/fields-viewer.element.js';
import './modal-views/fields-settings-modal.element.js';
interface ExposedSearchResultField {
name: string;
exposed: boolean;
@@ -36,10 +38,26 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
alert('TODO: Open workspace for ' + this.searcherName);
}
#entityType = '';
@state()
private _workspacePath = '';
private _onKeyPress(e: KeyboardEvent) {
e.key == 'Enter' ? this._onSearch() : undefined;
}
constructor() {
super();
new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
.onSetup(() => {
return { data: { entityType: this.#entityType, preset: {} } };
})
.observeRouteBuilder((routeBuilder) => {
this._workspacePath = routeBuilder({});
});
}
private async _onSearch() {
if (!this._searchInput.value.length) return;
this._searchLoading = true;
@@ -86,20 +104,24 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
const modalContext = modalManager.open(this, UMB_EXAMINE_FIELDS_SETTINGS_MODAL, {
value: { fields: this._exposedFields ?? [] },
});
modalContext?.onSubmit().then((value) => {
this._exposedFields = value.fields;
});
await modalContext.onSubmit().catch(() => undefined);
const value = modalContext.getValue();
this._exposedFields = value?.fields;
}
async #onFieldViewClick(rowData: SearchResultResponseModel) {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
modalManager.open(this, 'umb-modal-element-fields-viewer', {
const modalContext = modalManager.open(this, UMB_EXAMINE_FIELDS_VIEWER_MODAL, {
modal: {
type: 'sidebar',
size: 'medium',
},
data: { ...rowData, name: this.getSearchResultNodeName(rowData) },
data: { searchResult: rowData, name: this.getSearchResultNodeName(rowData) },
});
await modalContext.onSubmit().catch(() => undefined);
}
render() {
@@ -128,6 +150,15 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
return nodeNameField?.values?.join(', ') ?? '';
}
#getEntityTypeFromIndexType(indexType: string) {
switch (indexType) {
case 'content':
return 'document';
default:
return indexType;
}
}
private renderSearchResults() {
if (this._searchLoading) return html`<uui-loader></uui-loader>`;
if (!this._searchResults) return nothing;
@@ -145,11 +176,18 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
${this.renderHeadCells()}
</uui-table-head>
${this._searchResults?.map((rowData) => {
const indexType = rowData.fields?.find((field) => field.name === '__IndexType')?.values?.join(', ') ?? '';
this.#entityType = this.#getEntityTypeFromIndexType(indexType);
const unique = rowData.fields?.find((field) => field.name === '__Key')?.values?.join(', ') ?? '';
return html`<uui-table-row>
<uui-table-cell> ${rowData.score} </uui-table-cell>
<uui-table-cell> ${rowData.id} </uui-table-cell>
<uui-table-cell>
<uui-button look="secondary" label="Open workspace for this document" @click="${this._onNameClick}">
<uui-button
look="secondary"
label="Open workspace for this document"
href=${this._workspacePath + this.#entityType + '/edit/' + unique}>
${this.getSearchResultNodeName(rowData)}
</uui-button>
</uui-table-cell>

View File

@@ -1,5 +1,8 @@
import { manifests as examineManifests } from './examine-management-dashboard/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
import './examine-management-dashboard/index.js';
export const manifests: Array<ManifestTypes> = [
{
type: 'headerApp',
@@ -37,10 +40,5 @@ export const manifests: Array<ManifestTypes> = [
},
],
},
{
type: 'modal',
alias: 'Umb.Modal.ExamineFieldsSettings',
name: 'Examine Field Settings Modal',
js: () => import('./examine-management-dashboard/views/modal-views/fields-settings-modal.element.js'),
},
...examineManifests,
];