2022-09-14 22:05:21 +00:00
|
|
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
|
|
|
import { AKElement } from "@goauthentik/elements/Base";
|
2022-10-18 22:06:45 +00:00
|
|
|
import {
|
|
|
|
Chart,
|
|
|
|
ChartConfiguration,
|
|
|
|
ChartData,
|
|
|
|
ChartOptions,
|
|
|
|
Filler,
|
|
|
|
LineElement,
|
|
|
|
Plugin,
|
|
|
|
PointElement,
|
|
|
|
Tick,
|
|
|
|
} from "chart.js";
|
2021-05-06 11:02:07 +00:00
|
|
|
import { Legend, Tooltip } from "chart.js";
|
2021-10-28 07:48:51 +00:00
|
|
|
import { BarController, DoughnutController, LineController } from "chart.js";
|
2021-05-06 11:02:07 +00:00
|
|
|
import { ArcElement, BarElement } from "chart.js";
|
2021-10-28 07:48:51 +00:00
|
|
|
import { LinearScale, TimeScale } from "chart.js";
|
2021-04-05 09:28:22 +00:00
|
|
|
import "chartjs-adapter-moment";
|
2021-09-21 09:31:37 +00:00
|
|
|
|
2021-12-14 21:04:16 +00:00
|
|
|
import { t } from "@lingui/macro";
|
|
|
|
|
2022-09-14 22:05:21 +00:00
|
|
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
2021-11-04 21:34:48 +00:00
|
|
|
import { property } from "lit/decorators.js";
|
2021-09-21 09:31:37 +00:00
|
|
|
|
2021-05-06 11:02:07 +00:00
|
|
|
Chart.register(Legend, Tooltip);
|
|
|
|
Chart.register(LineController, BarController, DoughnutController);
|
2022-10-18 22:06:45 +00:00
|
|
|
Chart.register(ArcElement, BarElement, PointElement, LineElement);
|
|
|
|
Chart.register(TimeScale, LinearScale, Filler);
|
2021-03-08 10:14:00 +00:00
|
|
|
|
2022-09-21 18:16:37 +00:00
|
|
|
export const FONT_COLOUR_DARK_MODE = "#fafafa";
|
|
|
|
export const FONT_COLOUR_LIGHT_MODE = "#151515";
|
|
|
|
|
2022-10-18 22:06:45 +00:00
|
|
|
export class RGBAColor {
|
|
|
|
constructor(public r: number, public g: number, public b: number, public a: number = 1) {}
|
|
|
|
toString(): string {
|
|
|
|
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getColorFromString(stringInput: string): RGBAColor {
|
|
|
|
let hash = 0;
|
|
|
|
for (let i = 0; i < stringInput.length; i++) {
|
|
|
|
hash = stringInput.charCodeAt(i) + ((hash << 5) - hash);
|
|
|
|
hash = hash & hash;
|
|
|
|
}
|
|
|
|
const rgb = [0, 0, 0];
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
const value = (hash >> (i * 8)) & 255;
|
|
|
|
rgb[i] = value;
|
|
|
|
}
|
|
|
|
return new RGBAColor(rgb[0], rgb[1], rgb[2]);
|
|
|
|
}
|
|
|
|
|
2022-09-14 22:05:21 +00:00
|
|
|
export abstract class AKChart<T> extends AKElement {
|
2021-03-08 10:14:00 +00:00
|
|
|
abstract apiRequest(): Promise<T>;
|
2021-05-05 20:15:11 +00:00
|
|
|
abstract getChartData(data: T): ChartData;
|
2021-03-08 10:14:00 +00:00
|
|
|
|
2022-03-30 08:36:50 +00:00
|
|
|
chart?: Chart;
|
2021-03-08 10:14:00 +00:00
|
|
|
|
2021-05-06 11:02:07 +00:00
|
|
|
@property()
|
|
|
|
centerText?: string;
|
|
|
|
|
2021-05-06 14:25:29 +00:00
|
|
|
fontColour = FONT_COLOUR_LIGHT_MODE;
|
|
|
|
|
2021-03-08 10:14:00 +00:00
|
|
|
static get styles(): CSSResult[] {
|
2021-08-03 15:52:21 +00:00
|
|
|
return [
|
|
|
|
css`
|
|
|
|
.container {
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
canvas {
|
|
|
|
width: 100px;
|
|
|
|
height: 100px;
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
];
|
2021-03-08 10:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
2021-05-06 14:25:29 +00:00
|
|
|
const matcher = window.matchMedia("(prefers-color-scheme: light)");
|
|
|
|
const handler = (ev?: MediaQueryListEvent) => {
|
|
|
|
if (ev?.matches || matcher.matches) {
|
|
|
|
this.fontColour = FONT_COLOUR_LIGHT_MODE;
|
|
|
|
} else {
|
|
|
|
this.fontColour = FONT_COLOUR_DARK_MODE;
|
|
|
|
}
|
|
|
|
this.chart?.update();
|
|
|
|
};
|
|
|
|
matcher.addEventListener("change", handler);
|
|
|
|
handler();
|
2021-03-08 10:14:00 +00:00
|
|
|
}
|
|
|
|
|
2023-01-02 09:26:52 +00:00
|
|
|
connectedCallback(): void {
|
|
|
|
super.connectedCallback();
|
|
|
|
window.addEventListener("resize", this.resizeHandler);
|
|
|
|
this.addEventListener(EVENT_REFRESH, this.refreshHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnectedCallback(): void {
|
|
|
|
super.disconnectedCallback();
|
|
|
|
window.removeEventListener("resize", this.resizeHandler);
|
|
|
|
this.removeEventListener(EVENT_REFRESH, this.refreshHandler);
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshHandler(): void {
|
|
|
|
this.apiRequest().then((r: T) => {
|
|
|
|
if (!this.chart) return;
|
|
|
|
this.chart.data = this.getChartData(r);
|
|
|
|
this.chart.update();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
resizeHandler(): void {
|
|
|
|
if (!this.chart) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.chart.resize();
|
|
|
|
}
|
|
|
|
|
2021-05-10 13:57:52 +00:00
|
|
|
firstUpdated(): void {
|
2022-03-30 08:36:50 +00:00
|
|
|
this.apiRequest().then((r) => {
|
|
|
|
const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>("canvas");
|
|
|
|
if (!canvas) {
|
|
|
|
console.warn("Failed to get canvas element");
|
2023-01-01 22:44:15 +00:00
|
|
|
return;
|
2022-03-30 08:36:50 +00:00
|
|
|
}
|
|
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
if (!ctx) {
|
|
|
|
console.warn("failed to get 2d context");
|
2023-01-01 22:44:15 +00:00
|
|
|
return;
|
2022-03-30 08:36:50 +00:00
|
|
|
}
|
|
|
|
this.chart = this.configureChart(r, ctx);
|
2021-05-10 13:57:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:02:07 +00:00
|
|
|
getChartType(): string {
|
|
|
|
return "bar";
|
|
|
|
}
|
|
|
|
|
|
|
|
getPlugins(): Plugin[] {
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
id: "center-text",
|
|
|
|
beforeDraw: (chart) => {
|
|
|
|
if (!chart.ctx) return;
|
|
|
|
if (!this.centerText) return;
|
|
|
|
const width = chart.width || 0;
|
|
|
|
const height = chart.height || 0;
|
|
|
|
|
|
|
|
const fontSize = (height / 114).toFixed(2);
|
2021-10-14 10:50:52 +00:00
|
|
|
chart.ctx.font = `${fontSize}em Overpass, Arial, sans-serif`;
|
2021-05-06 11:02:07 +00:00
|
|
|
chart.ctx.textBaseline = "middle";
|
2021-05-06 14:25:29 +00:00
|
|
|
chart.ctx.fillStyle = this.fontColour;
|
2021-05-06 11:02:07 +00:00
|
|
|
|
2021-08-03 15:52:21 +00:00
|
|
|
const textX = Math.round(
|
|
|
|
(width - chart.ctx.measureText(this.centerText).width) / 2,
|
|
|
|
);
|
2021-05-06 11:02:07 +00:00
|
|
|
const textY = height / 2;
|
|
|
|
|
|
|
|
chart.ctx.fillText(this.centerText, textX, textY);
|
2021-08-03 15:52:21 +00:00
|
|
|
},
|
|
|
|
},
|
2021-05-06 11:02:07 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2021-12-14 21:04:16 +00:00
|
|
|
timeTickCallback(tickValue: string | number, index: number, ticks: Tick[]): string {
|
|
|
|
const valueStamp = ticks[index];
|
|
|
|
const delta = Date.now() - valueStamp.value;
|
|
|
|
const ago = Math.round(delta / 1000 / 3600);
|
|
|
|
return t`${ago} hours ago`;
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:02:07 +00:00
|
|
|
getOptions(): ChartOptions {
|
|
|
|
return {
|
|
|
|
maintainAspectRatio: false,
|
|
|
|
scales: {
|
|
|
|
x: {
|
|
|
|
type: "time",
|
|
|
|
display: true,
|
|
|
|
ticks: {
|
2021-12-14 21:04:16 +00:00
|
|
|
callback: (tickValue: string | number, index: number, ticks: Tick[]) => {
|
|
|
|
return this.timeTickCallback(tickValue, index, ticks);
|
2021-03-08 10:14:00 +00:00
|
|
|
},
|
2021-05-06 11:02:07 +00:00
|
|
|
autoSkip: true,
|
|
|
|
maxTicksLimit: 8,
|
2021-04-05 09:28:22 +00:00
|
|
|
},
|
2021-05-06 11:02:07 +00:00
|
|
|
stacked: true,
|
|
|
|
grid: {
|
|
|
|
color: "rgba(0, 0, 0, 0)",
|
|
|
|
},
|
2021-08-03 15:52:21 +00:00
|
|
|
offset: true,
|
2021-03-08 10:14:00 +00:00
|
|
|
},
|
2021-05-06 11:02:07 +00:00
|
|
|
y: {
|
|
|
|
type: "linear",
|
|
|
|
display: true,
|
|
|
|
stacked: true,
|
|
|
|
grid: {
|
|
|
|
color: "rgba(0, 0, 0, 0)",
|
|
|
|
},
|
2021-08-03 15:52:21 +00:00
|
|
|
},
|
2021-03-08 10:14:00 +00:00
|
|
|
},
|
2021-05-06 11:02:07 +00:00
|
|
|
} as ChartOptions;
|
|
|
|
}
|
|
|
|
|
2022-03-30 08:36:50 +00:00
|
|
|
configureChart(data: T, ctx: CanvasRenderingContext2D): Chart {
|
2021-05-06 11:02:07 +00:00
|
|
|
const config = {
|
|
|
|
type: this.getChartType(),
|
2022-03-30 08:36:50 +00:00
|
|
|
data: this.getChartData(data),
|
2021-05-06 11:02:07 +00:00
|
|
|
options: this.getOptions(),
|
|
|
|
plugins: this.getPlugins(),
|
2021-04-05 09:28:22 +00:00
|
|
|
};
|
|
|
|
return new Chart(ctx, config as ChartConfiguration);
|
2021-03-08 10:14:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
render(): TemplateResult {
|
2021-05-06 11:02:07 +00:00
|
|
|
return html`
|
|
|
|
<div class="container">
|
|
|
|
<canvas></canvas>
|
|
|
|
</div>
|
|
|
|
`;
|
2021-03-08 10:14:00 +00:00
|
|
|
}
|
|
|
|
}
|