web: more admin overview components
This commit is contained in:
parent
b218ded241
commit
1779b4d888
55
swagger.yaml
55
swagger.yaml
|
@ -133,6 +133,42 @@ paths:
|
|||
tags:
|
||||
- audit
|
||||
parameters: []
|
||||
/audit/events/top_per_user/:
|
||||
get:
|
||||
operationId: audit_events_top_per_user
|
||||
description: Get the top_n events grouped by user count
|
||||
parameters:
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
required: false
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: A page number within the paginated result set.
|
||||
required: false
|
||||
type: integer
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Number of results to return per page.
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: Response object of Event's top_per_user
|
||||
schema:
|
||||
description: ''
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/EventTopPerUserSerialier'
|
||||
tags:
|
||||
- audit
|
||||
parameters: []
|
||||
/audit/events/{event_uuid}/:
|
||||
get:
|
||||
operationId: audit_events_read
|
||||
|
@ -6509,6 +6545,25 @@ definitions:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
EventTopPerUserSerialier:
|
||||
description: Response object of Event's top_per_user
|
||||
required:
|
||||
- application
|
||||
- counted_events
|
||||
- unique_users
|
||||
type: object
|
||||
properties:
|
||||
application:
|
||||
title: Application
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
counted_events:
|
||||
title: Counted events
|
||||
type: integer
|
||||
unique_users:
|
||||
title: Unique users
|
||||
type: integer
|
||||
Application:
|
||||
description: Application Serializer
|
||||
required:
|
||||
|
|
16
web/src/api/events.ts
Normal file
16
web/src/api/events.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { DefaultClient } from "./client";
|
||||
|
||||
export class AuditEvent {
|
||||
//audit/events/top_per_user/?filter_action=authorize_application
|
||||
static topForUser(action: string): Promise<TopNEvent[]> {
|
||||
return DefaultClient.fetch<TopNEvent[]>(["audit", "events", "top_per_user"], {
|
||||
"filter_action": action,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface TopNEvent {
|
||||
application: { [key: string]: string};
|
||||
counted_events: number;
|
||||
unique_users: number;
|
||||
}
|
48
web/src/elements/cards/AggregateCard.ts
Normal file
48
web/src/elements/cards/AggregateCard.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { gettext } from "django";
|
||||
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
@customElement("pb-aggregate-card")
|
||||
export class AggregateCard extends LitElement {
|
||||
@property()
|
||||
icon?: string;
|
||||
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
@property()
|
||||
headerLink?: string;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES.concat([css`
|
||||
.center-value {
|
||||
font-size: var(--pf-global--icon--FontSize--lg);
|
||||
text-align: center;
|
||||
}
|
||||
.subtext {
|
||||
font-size: var(--pf-global--FontSize--sm);
|
||||
}
|
||||
`]);
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card pf-c-card-aggregate">
|
||||
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||
<div class="pf-c-card__header-main">
|
||||
<i class="${this.icon}"></i> ${this.header ? gettext(this.header) : ""}
|
||||
</div>
|
||||
${this.headerLink ? html`<a href="${this.headerLink}">
|
||||
<i class="fa fa-external-link-alt"> </i>
|
||||
</a>` : ""}
|
||||
</div>
|
||||
<div class="pf-c-card__body center-value">
|
||||
${this.renderInner()}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
25
web/src/elements/cards/AggregatePromiseCard.ts
Normal file
25
web/src/elements/cards/AggregatePromiseCard.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { AggregateCard } from "./AggregateCard";
|
||||
|
||||
@customElement("pb-aggregate-card-promise")
|
||||
export class AggregatePromiseCard extends AggregateCard {
|
||||
@property()
|
||||
promise?: Promise<string>;
|
||||
|
||||
promiseProxy(): Promise<TemplateResult> {
|
||||
if (!this.promise) {
|
||||
return new Promise<TemplateResult>(() => html``);
|
||||
}
|
||||
return this.promise.then(s => {
|
||||
return html`<i class="fa fa-check-circle"></i> ${s}`;
|
||||
});
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
return html`<p class="center-value">
|
||||
${until(this.promiseProxy(), html`<pb-spinner size="large"></pb-spinner>`)}
|
||||
</p>`;
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,8 @@ import "./elements/sidebar/SidebarUser";
|
|||
import "./elements/table/TablePagination";
|
||||
|
||||
import "./elements/AdminLoginsChart";
|
||||
import "./elements/cards/AggregateCard";
|
||||
import "./elements/cards/AggregatePromiseCard";
|
||||
import "./elements/CodeMirror";
|
||||
import "./elements/Messages";
|
||||
import "./elements/Spinner";
|
||||
|
@ -23,7 +25,8 @@ import "./pages/generic/SiteShell";
|
|||
|
||||
import "./pages/router/RouterOutlet";
|
||||
|
||||
import "./pages/AdminOverviewPage";
|
||||
import "./pages/admin-overview/AdminOverviewPage";
|
||||
import "./pages/admin-overview/TopApplicationsTable";
|
||||
import "./pages/applications/ApplicationListPage";
|
||||
import "./pages/applications/ApplicationViewPage";
|
||||
import "./pages/LibraryPage";
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { AdminOverview } from "../api/admin_overview";
|
||||
import { DefaultClient } from "../api/client";
|
||||
import { User } from "../api/user";
|
||||
import { COMMON_STYLES } from "../common/styles";
|
||||
|
||||
@customElement("pb-aggregate-card")
|
||||
export class AggregateCard extends LitElement {
|
||||
@property()
|
||||
icon?: string;
|
||||
|
||||
@property()
|
||||
header?: string;
|
||||
|
||||
@property()
|
||||
headerLink?: string;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" >
|
||||
<div class="pf-c-card__header pf-l-flex pf-m-justify-content-space-between">
|
||||
<div class="pf-c-card__header-main">
|
||||
<i class="${this.icon}"></i> ${this.header ? gettext(this.header) : ""}
|
||||
</div>
|
||||
${this.headerLink ? html`<a href="${this.headerLink}">
|
||||
<i class="fa fa-external-link-alt"> </i>
|
||||
</a>` : ""}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
${this.renderInner()}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@customElement("pb-aggregate-card-promise")
|
||||
export class AggregatePromiseCard extends AggregateCard {
|
||||
@property()
|
||||
promise?: Promise<string>;
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
return html`<p class="pb-aggregate-card">
|
||||
${until(this.promise, html`<pb-spinner></pb-spinner>`)}
|
||||
</p>`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@customElement("pb-admin-overview")
|
||||
export class AdminOverviewPage extends LitElement {
|
||||
@property()
|
||||
data?: AdminOverview;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
AdminOverview.get().then(value => this.data = value);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>${gettext("System Overview")}</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-gallery pf-m-gutter">
|
||||
<pb-aggregate-card icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
|
||||
<pb-admin-logins-chart url="${DefaultClient.makeUrl(["admin", "metrics"])}"></pb-admin-logins-chart>
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card icon="pf-icon pf-icon-server" header="Workers">
|
||||
${this.data ?
|
||||
this.data?.worker_count < 1 ?
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> ${this.data.worker_count}
|
||||
</p>
|
||||
<p>${gettext("No workers connected.")}</p>` :
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> ${this.data.worker_count}
|
||||
</p>`
|
||||
: html`<pb-spinner></pb-spinner>`}
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/">
|
||||
${this.data ?
|
||||
this.data?.providers_without_application < 1 ?
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> 0
|
||||
</p>
|
||||
<p>${gettext("At least one Provider has no application assigned.")}</p>` :
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> 0
|
||||
</p>`
|
||||
: html`<pb-spinner></pb-spinner>`}
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card icon="pf-icon pf-icon-plugged" header="Policies" headerLink="#/administration/policies/">
|
||||
${this.data ?
|
||||
this.data?.policies_without_binding < 1 ?
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> 0
|
||||
</p>
|
||||
<p>${gettext("Policies without binding exist.")}</p>` :
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> 0
|
||||
</p>`
|
||||
: html`<pb-spinner></pb-spinner>`}
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card-promise
|
||||
icon="pf-icon pf-icon-user"
|
||||
header="Users"
|
||||
headerLink="#/administration/users/"
|
||||
.promise=${User.count()}>
|
||||
</pb-aggregate-card-promise>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
}
|
116
web/src/pages/admin-overview/AdminOverviewPage.ts
Normal file
116
web/src/pages/admin-overview/AdminOverviewPage.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { AdminOverview } from "../../api/admin_overview";
|
||||
import { DefaultClient } from "../../api/client";
|
||||
import { User } from "../../api/user";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
import { AggregatePromiseCard } from "../../elements/cards/AggregatePromiseCard";
|
||||
|
||||
@customElement("pb-admin-status-card")
|
||||
export class AdminStatusCard extends AggregatePromiseCard {
|
||||
|
||||
@property()
|
||||
value?: number;
|
||||
|
||||
@property()
|
||||
warningText?: string;
|
||||
|
||||
@property()
|
||||
lessThanThreshold?: number;
|
||||
|
||||
renderNone(): TemplateResult {
|
||||
return html`<pb-spinner size="large"></pb-spinner>`;
|
||||
}
|
||||
|
||||
renderGood(): TemplateResult {
|
||||
return html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> ${this.value}
|
||||
</p>`;
|
||||
}
|
||||
|
||||
renderBad(): TemplateResult {
|
||||
return html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> ${this.value}
|
||||
</p>
|
||||
<p class="subtext">${this.warningText ? gettext(this.warningText) : ""}</p>`;
|
||||
}
|
||||
|
||||
renderInner(): TemplateResult {
|
||||
if (!this.value) {
|
||||
return this.renderNone();
|
||||
}
|
||||
|
||||
return html``;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@customElement("pb-admin-overview")
|
||||
export class AdminOverviewPage extends LitElement {
|
||||
@property()
|
||||
data?: AdminOverview;
|
||||
|
||||
@property()
|
||||
users?: Promise<number>;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
AdminOverview.get().then(value => this.data = value);
|
||||
this.users = User.count();
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
<h1>${gettext("System Overview")}</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-gallery pf-m-gutter">
|
||||
<pb-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Logins over the last 24 hours" style="grid-column-end: span 3;grid-row-end: span 2;">
|
||||
<pb-admin-logins-chart url="${DefaultClient.makeUrl(["admin", "metrics"])}"></pb-admin-logins-chart>
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Apps with most usage" style="grid-column-end: span 2;grid-row-end: span 3;">
|
||||
<pb-top-applications-table></pb-top-applications-table>
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-server" header="Workers">
|
||||
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Providers" headerLink="#/administration/providers/">
|
||||
${this.data ?
|
||||
this.data?.providers_without_application > 1 ?
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> 0
|
||||
</p>
|
||||
<p class="subtext">${gettext("At least one Provider has no application assigned.")}</p>` :
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> 0
|
||||
</p>`
|
||||
: html`<pb-spinner size="large"></pb-spinner>`}
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card class="pf-l-gallery__item pf-m-4-col" icon="pf-icon pf-icon-plugged" header="Policies" headerLink="#/administration/policies/">
|
||||
${this.data ?
|
||||
this.data?.policies_without_binding > 1 ?
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-exclamation-triangle"></i> 0
|
||||
</p>
|
||||
<p class="subtext">${gettext("Policies without binding exist.")}</p>` :
|
||||
html`<p class="pb-aggregate-card">
|
||||
<i class="fa fa-check-circle"></i> 0
|
||||
</p>`
|
||||
: html`<pb-spinner size="large"></pb-spinner>`}
|
||||
</pb-aggregate-card>
|
||||
<pb-aggregate-card-promise
|
||||
icon="pf-icon pf-icon-user"
|
||||
header="Users"
|
||||
headerLink="#/administration/users/"
|
||||
.promise=${this.users}>
|
||||
</pb-aggregate-card-promise>
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
|
||||
}
|
49
web/src/pages/admin-overview/TopApplicationsTable.ts
Normal file
49
web/src/pages/admin-overview/TopApplicationsTable.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { gettext } from "django";
|
||||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||
import { AuditEvent, TopNEvent } from "../../api/events";
|
||||
import { COMMON_STYLES } from "../../common/styles";
|
||||
|
||||
@customElement("pb-top-applications-table")
|
||||
export class TopApplicationsTable extends LitElement {
|
||||
|
||||
@property()
|
||||
topN?: TopNEvent[];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return COMMON_STYLES;
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
AuditEvent.topForUser("authorize_application").then(events => this.topN = events);
|
||||
}
|
||||
|
||||
renderRow(event: TopNEvent): TemplateResult {
|
||||
return html`<tr role="row">
|
||||
<td role="cell">
|
||||
${event.application.name}
|
||||
</td>
|
||||
<td role="cell">
|
||||
${event.counted_events}
|
||||
</td>
|
||||
<td role="cell">
|
||||
<progress value="${event.counted_events}" max="${this.topN ? this.topN[0].counted_events : 0}"></progress>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<table class="pf-c-table pf-m-compact" role="grid">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th role="columnheader" scope="col">${gettext("Application")}</th>
|
||||
<th role="columnheader" scope="col">${gettext("Logins")}</th>
|
||||
<th role="columnheader" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody role="rowgroup">
|
||||
${this.topN ? this.topN.map((e) => this.renderRow(e)) : html`<pb-spinner></pb-spinner>`}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
}
|
|
@ -66,193 +66,6 @@ select[multiple] {
|
|||
height: initial;
|
||||
}
|
||||
|
||||
/* Selector */
|
||||
.selector {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 45vh;
|
||||
}
|
||||
|
||||
.selector .selector-filter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selector .selector-filter label {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.selector .selector-filter input {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
flex: 1 1;
|
||||
}
|
||||
|
||||
.selector-available,
|
||||
.selector-chosen {
|
||||
width: auto;
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.selector select {
|
||||
width: 100%;
|
||||
flex: 1 0 auto;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.selector ul.selector-chooser {
|
||||
width: 26px;
|
||||
height: 52px;
|
||||
padding: 2px 0;
|
||||
margin: auto 15px;
|
||||
border-radius: 20px;
|
||||
transform: translateY(-10px);
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.selector-add,
|
||||
.selector-remove {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: 20px auto;
|
||||
}
|
||||
|
||||
.selector-add {
|
||||
background-position: 0 -120px;
|
||||
}
|
||||
|
||||
.selector-remove {
|
||||
background-position: 0 -80px;
|
||||
}
|
||||
|
||||
a.selector-chooseall,
|
||||
a.selector-clearall {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.stacked {
|
||||
flex-direction: column;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.stacked > * {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.stacked select {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stacked .selector-available,
|
||||
.stacked .selector-chosen {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.stacked ul.selector-chooser {
|
||||
width: 52px;
|
||||
height: 26px;
|
||||
padding: 0 2px;
|
||||
margin: 15px auto;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.stacked .selector-chooser li {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.stacked .selector-add,
|
||||
.stacked .selector-remove {
|
||||
background-size: 20px auto;
|
||||
}
|
||||
|
||||
.stacked .selector-add {
|
||||
background-position: 0 -40px;
|
||||
}
|
||||
|
||||
.stacked .active.selector-add {
|
||||
background-position: 0 -60px;
|
||||
}
|
||||
|
||||
.stacked .selector-remove {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.stacked .active.selector-remove {
|
||||
background-position: 0 -20px;
|
||||
}
|
||||
|
||||
.help-tooltip,
|
||||
.selector .help-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form .form-row p.datetime {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.datetime input {
|
||||
width: 50%;
|
||||
max-width: 120px;
|
||||
}
|
||||
|
||||
.datetime span {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.datetime .timezonewarning {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.datetimeshortcuts {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.inline-group {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.selector-add,
|
||||
.selector-remove {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
text-indent: -3000px;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.active.selector-add,
|
||||
.active.selector-remove {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.active.selector-add:hover,
|
||||
.active.selector-remove:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selector-add {
|
||||
background: url(../admin/img/selector-icons.svg) 0 -96px no-repeat;
|
||||
}
|
||||
|
||||
.active.selector-add:focus,
|
||||
.active.selector-add:hover {
|
||||
background-position: 0 -112px;
|
||||
}
|
||||
|
||||
.selector-remove {
|
||||
background: url(../admin/img/selector-icons.svg) 0 -64px no-repeat;
|
||||
}
|
||||
|
||||
input[data-is-monospace] {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* Form with user */
|
||||
.form-control-static {
|
||||
margin-top: var(--pf-global--spacer--sm);
|
||||
|
@ -291,12 +104,6 @@ input[data-is-monospace] {
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Aggregate Cards */
|
||||
.pb-aggregate-card {
|
||||
font-size: var(--pf-global--icon--FontSize--lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pf-c-content h1 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
|
Reference in a new issue