@if (!!settings.title) {
<c8y-title>
@let dashboardTitle =
settings.translateDashboardTitle ? (settings.title | translate) : settings.title;
{{ dashboardTitle }}
</c8y-title>
}
@if (!!breadcrumb) {
<c8y-breadcrumb>
<c8y-breadcrumb-item
[icon]="breadcrumb.icon"
[label]="breadcrumb.label"
[path]="breadcrumb.path"
></c8y-breadcrumb-item>
</c8y-breadcrumb>
}
@if (!(editMode$ | async)) {
<c8y-action-bar-item
[placement]="'right'"
[priority]="ACTION_BAR_EDIT_WIDGETS_PRIORITY"
>
<button
class="btn btn-link animated fadeIn hidden-xs"
title="{{ 'Edit widgets' | translate }}"
type="button"
[disabled]="settings.isDisabled"
(click)="enableEditMode()"
data-cy="c8y-widget-dashboard--edit-widgets"
>
<i c8yIcon="send-backward"></i>
<span class="m-l-4">{{ 'Edit widgets' | translate }}</span>
</button>
<button
class="btn btn-link visible-xs m-l-0"
tooltip="{{ 'Not available on mobile phone' | translate }}"
type="button"
[disabled]="true"
>
<i c8yIcon="send-backward"></i>
<span class="m-l-4">{{ 'Edit widgets' | translate }}</span>
</button>
</c8y-action-bar-item>
}
@if (editMode$ | async) {
<c8y-action-bar-item
[placement]="'right'"
[priority]="ACTION_BAR_EDIT_WIDGETS_PRIORITY"
>
<button
class="btn btn-link animated fadeIn"
title="{{ 'Add widget' | translate }}"
type="button"
(click)="onAddWidget.emit()"
data-cy="widget-dashboard--Add-widget"
>
<i c8yIcon="plus-circle"></i>
{{ 'Add widget' | translate }}
</button>
</c8y-action-bar-item>
<c8y-action-bar-item
[placement]="'right'"
[priority]="ACTION_BAR_EDIT_WIDGETS_PRIORITY"
itemClass="d-flex a-i-center gap-8"
>
<div class="input-group input-group-sm animated fadeIn">
<div class="input-group-btn">
<button
class="btn btn-default btn-sm btn-icon"
[attr.aria-label]="'Undo' | translate"
[tooltip]="
editModeButtons.undoButtonDisabled
? ''
: (undoMessage
| translate: { changeToUndo: editModeButtons.changeToUndoName | translate })
"
container="body"
(click)="revertChange.emit('undo')"
[disabled]="editModeButtons.undoButtonDisabled"
>
<i [c8yIcon]="'undo'"></i>
</button>
</div>
<div class="input-group-btn">
<button
class="btn btn-default btn-sm btn-icon"
[attr.aria-label]="'Redo' | translate"
[tooltip]="
editModeButtons.redoButtonDisabled
? ''
: (redoMessage
| translate: { changeToRedo: editModeButtons.changeToRedoName | translate })
"
container="body"
(click)="revertChange.emit('redo')"
[disabled]="editModeButtons.redoButtonDisabled"
>
<i [c8yIcon]="'redo'"></i>
</button>
</div>
<span></span>
</div>
<div class="btn-group animated fadeIn">
<button
class="btn btn-default btn-sm"
title="{{ 'Cancel' | translate }}"
type="button"
(click)="cancelDashboardSave()"
>
{{ 'Cancel' | translate }}
</button>
<button
class="btn btn-primary btn-sm m-l-8"
title="{{ 'Save' | translate }}"
type="button"
[disabled]="editModeButtons.undoButtonDisabled && isSaveDisabled"
(click)="saveDashboard()"
data-cy="c8y-widgets-dashboard--save"
>
{{ 'Save' | translate }}
</button>
</div>
</c8y-action-bar-item>
}
@if (onEditDashboard.observers.length) {
<c8y-action-bar-item [placement]="'right'">
<button
class="btn btn-link hidden-xs m-l-0"
title="{{ 'Dashboard settings' | translate }}"
type="button"
[disabled]="settings.isDisabled || (editMode$ | async)"
(click)="onEditDashboard.emit()"
data-cy="c8y-widgets-dashboard--edit-dashboard"
>
<i c8yIcon="sorting-slider"></i>
<span class="visible-xs-inline hidden-sm visible-md-inline visible-lg-inline">
{{ 'Dashboard settings' | translate }}
</span>
</button>
<button
class="btn btn-link visible-xs m-l-0"
tooltip="{{ 'Not available on mobile phone' | translate }}"
type="button"
[disabled]="true"
>
<i c8yIcon="sorting-slider"></i>
<span class="visible-xs-inline hidden-sm visible-md-inline visible-lg-inline">
{{ 'Dashboard settings' | translate }}
</span>
</button>
</c8y-action-bar-item>
}
@if (settings.allowFullscreen) {
<c8y-action-bar-item
[placement]="'right'"
[priority]="-5000"
itemClass="pull-right"
>
<button
class="btn btn-link"
[attr.aria-label]="'Full screen' | translate"
tooltip="{{ 'Full screen' | translate }}"
placement="left"
container="body"
type="button"
[delay]="500"
(click)="toggleFullscreen()"
data-cy="widgets-dashboard--Full-screen"
>
<i [c8yIcon]="(inFullScreen$ | async) ? 'compress' : 'expand'"></i>
<span class="visible-xs-inline hidden-sm visible-md-inline visibile-lg-inline">
{{ 'Full screen' | translate }}
</span>
</button>
</c8y-action-bar-item>
}
@if (settings.canCopy) {
<c8y-action-bar-item
[placement]="'more'"
[priority]="-2000"
>
<div class="d-flex a-i-center p-r-16">
<button
class="hidden-xs"
title="{{
(isCopyDisabled === true || !isCopyDisabled?.state ? 'Disabled' : copyDashboardLabel)
| translate
}}"
type="button"
[ngClass]="{ 'btn btn-link': !settings.canDelete }"
data-cy="widgets-dashboard--copy-dashboard"
(click)="onCopyDashboard.emit()"
[disabled]="isCopyDisabled === true || !isCopyDisabled?.state || (editMode$ | async)"
>
<i c8yIcon="clone"></i>
<span>{{ copyDashboardLabel | translate }}</span>
</button>
@if (!isCopyDisabled?.state && copyDisabledPopoverMsg) {
<button
class="btn-help btn-help--sm hidden-xs flex-no-shrink"
[attr.aria-label]="'Help' | translate"
[popover]="copyDisabledPopoverMsg | translate"
placement="top"
triggers="focus"
container="body"
type="button"
[adaptivePosition]="false"
data-cy="widgets-dashboard--info-copy-dashboard"
(click)="$event.preventDefault(); $event.stopPropagation()"
></button>
}
</div>
<button
class="visible-xs m-l-0"
tooltip="{{ 'Not available on mobile phone' | translate }}"
type="button"
[ngClass]="{ 'btn btn-link': !settings.canDelete }"
[disabled]="true"
>
<i c8yIcon="clone"></i>
<span>{{ copyDashboardLabel | translate }}</span>
</button>
</c8y-action-bar-item>
}
@if (settings.canDelete && onDeleteDashboard.observers.length) {
<c8y-action-bar-item
[placement]="'more'"
[priority]="-3000"
>
<button
class="hidden-xs"
title="{{ 'Delete dashboard' | translate }}"
type="button"
data-cy="widgets-dashboard--delete-dashboard"
[ngClass]="{ 'btn btn-link': !settings.canCopy }"
(click)="onDeleteDashboard.emit()"
[disabled]="settings.isDisabled || (editMode$ | async)"
>
<i c8yIcon="delete"></i>
<span translate>Delete dashboard</span>
</button>
<button
class="visible-xs m-l-0"
tooltip="{{ 'Not available on mobile phone' | translate }}"
type="button"
data-cy="widgets-dashboard--delete-dashboard-mobile"
[ngClass]="{ 'btn btn-link': !settings.canCopy }"
[disabled]="true"
>
<i c8yIcon="delete"></i>
<span translate>Delete dashboard</span>
</button>
</c8y-action-bar-item>
}
@if (!(isLoadingWidgets$ | async)) {
@if (resolvedWidgets$ | async; as widgetsToDisplay) {
<!-- empty state -->
@if (widgetsToDisplay?.length === 0) {
<c8y-ui-empty-state
[icon]="'c8y-device'"
[title]="'No widgets to display.' | translate"
>
@if (onAddWidget.observers.length) {
<div>
@if (editMode$ | async) {
<p translate>Add widgets to this dashboard.</p>
}
@if (!(editMode$ | async)) {
<p translate>Click "Edit widgets" to unlock</p>
}
<div>
@if (!settings.isDisabled && (editMode$ | async)) {
<button
class="btn btn-primary m-t-16"
title="{{ 'Add widget' | translate }}"
type="button"
(click)="onAddWidget.emit()"
data-cy="c8y-widgets-dashboard--add-widget"
translate
>
Add widget
</button>
}
</div>
<p c8y-guide-docs>
<small
translate
ngNonBindable
>
Find out more in the
<a c8y-guide-href="/docs/cockpit/working-with-dashboards">user documentation</a>.
</small>
</p>
</div>
}
</c8y-ui-empty-state>
}
<c8y-dashboard
[columns]="settings.columns"
(dashboardChange)="onChangeDashboard.emit($event)"
[layout]="settings.layout"
[rows]="settings.rows"
[alignViewHeight]="settings.alignViewHeight"
[float]="settings.float"
[gap]="settings.widgetMargin"
#dashboard
>
@for (widget of widgetsToDisplay; track widget.id || widget.name || $index) {
<c8y-dashboard-child
[class]="widget.classes"
[x]="widget._x"
[y]="widget._y"
[width]="widget._width || scaledDefaultWidth"
[height]="widget._height || scaledDefaultHeight"
[minW]="widget._minW || scaledDefaultMinWidth"
[minH]="widget._minH || scaledDefaultMinHeight"
[margin]="settings.widgetMargin"
[data]="widget"
[useIntersection]="true"
[editMode]="editMode$ | async"
(changeStart)="
onChangeStart.emit({ widget: widget, source: child, dashboard: dashboard })
"
(changeEnd)="onChangeEnd.emit({ widget: widget, source: child, dashboard: dashboard })"
(toggleFullscreen)="toggleFullscreenOnWidget(child)"
[canToggleFullscreen]="!(inFullScreen$ | async) || widgetInFullscreenMode"
#child
>
@let widgetTitle =
widget.title | translate: { noTranslateRemoveContext: !settings.translateWidgetTitle };
<c8y-dashboard-child-title [attr.title]="widgetTitle">
<span data-cy="c8y-dashboard-list--device-widget">
{{ widgetTitle }}
</span>
</c8y-dashboard-child-title>
@if (onEditWidget.observers.length) {
<c8y-dashboard-child-action>
<button
title="{{ 'Edit widget' | translate }}"
type="button"
data-cy="widgets-dashboard--Edit-widget"
(click)="onEditWidget.emit({ widget: widget, source: child, dashboard: dashboard })"
>
<i c8yIcon="pencil"></i>
<span translate>Edit</span>
</button>
</c8y-dashboard-child-action>
}
@if (onDeleteWidget.observers.length) {
<c8y-dashboard-child-action>
<button
title="{{ 'Remove widget' | translate }}"
type="button"
data-cy="c8y-widgets-dashboard--remove-widget"
(click)="
onDeleteWidget.emit({ widget: widget, source: child, dashboard: dashboard })
"
>
<i c8yIcon="delete"></i>
<span translate>Remove</span>
</button>
</c8y-dashboard-child-action>
}
@if (child.intersected) {
<c8y-dynamic-component
[componentId]="widget.componentId || widget.name"
[config]="
widget.templateUrl || widget.widgetComponent
? { child: widget, dashboard: contextDashboard, context: context }
: widget.config
"
(updateWidgetClasses)="updateWidgetClasses(widget, $event)"
></c8y-dynamic-component>
}
</c8y-dashboard-child>
}
</c8y-dashboard>
}
} @else {
<c8y-loading class="col-xs-12 text-center"></c8y-loading>
}