+
+ `;
@property({ type: Object })
unmatched: any = [];
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_manual_match/useMatched.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_manual_match/useMatched.ts
index e2d26dabebc2b922bcd5955e09c4257880af0d7e..0ff91499eb42758e3c54c7dc723e1b09bba84260 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_manual_match/useMatched.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_manual_match/useMatched.ts
@@ -115,7 +115,7 @@ const useMatched = (): UseMatchedType => {
if (isEmpty(npuNodeName) || isEmpty(benchNodeName)) {
return {
success: false,
- error:'调试侧节点或标杆节点为空'
+ error: '调试侧节点或标杆节点为空',
};
}
const metaData = {
@@ -162,7 +162,7 @@ const useMatched = (): UseMatchedType => {
if (isEmpty(npuNodeName) || isEmpty(benchNodeName)) {
return {
success: false,
- error:'调试侧节点或标杆节点为空'
+ error: '调试侧节点或标杆节点为空',
};
}
const metaData = {
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_search_combox/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_search_combox/index.ts
index a593a1fc04c0e74dfd5f358d1978f7761a044e21..234a43188ce4801c44a6025d94f16f4260b6b447 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_search_combox/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/tf_search_combox/index.ts
@@ -25,75 +25,68 @@ import { customElement, property } from '@polymer/decorators';
import '@vaadin/progress-bar';
@customElement('tf-search-combox')
class Legend extends PolymerElement {
- static get shadowRootOptions(): { mode: string } {
- return { mode: 'open' }; // 确保启用了 Shadow DOM
- }
-
// 定义模板
- static get template(): HTMLTemplateElement {
- return html`
-
-
+ `;
@property({ type: Object })
onSelectChange!: () => void;
@@ -112,7 +105,6 @@ class Legend extends PolymerElement {
// 选择列表中的下一个节点
_selectNext(): void {
-
if (!this.isCompareGraph) {
Notification.show('提示:单图节点不支持匹配', {
position: 'middle',
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/ts_linkage_search_combox/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/ts_linkage_search_combox/index.ts
index 57ea58f22729309e71e325f0ef29500a3effcff0..6ce3273656cdb7e44c840b8fdadaa3d95853b590 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/ts_linkage_search_combox/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/components/ts_linkage_search_combox/index.ts
@@ -27,96 +27,90 @@ import * as tf_graph_render from '../../../tf_graph_common/render';
import '../tf_search_combox/index';
@customElement('tf-linkage-search-combox')
class Legend extends PolymerElement {
- static get shadowRootOptions(): { mode: string } {
- return { mode: 'open' }; // 确保启用了 Shadow DOM
- }
-
// 定义模板
- static get template(): HTMLTemplateElement {
- return html`
-
-
-
-
-
-
-
-
-
+ static readonly template = html`
+
+
+ `;
@property({ type: Object })
renderHierarchy: tf_graph_render.MergedRenderGraphInfo = {} as any;
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/tf-graph-controls.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/tf-graph-controls.ts
index 5fbae194789264c4bcf15d94fece54a273852ecb..2f76248ca2cb09b1d2f909de008cb520f04991ac 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/tf-graph-controls.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_controls/tf-graph-controls.ts
@@ -635,10 +635,14 @@ class TfGraphControls extends LegacyElementMixin(DarkModeMixin(PolymerElement))
// MicroStep 选择 和 Step选择
@property({ type: Number })
_selectedMicroStep: number = -1;
+
+ @property({ type: Number })
_selectedStep: number = -1;
@property({ type: Object })
microsteps: any;
+
+ @property({ type: Object })
steplist: any;
// 目录,全量节点数据,支撑各种节点的搜索
@@ -816,7 +820,10 @@ class TfGraphControls extends LegacyElementMixin(DarkModeMixin(PolymerElement))
const { datasets: newDatasets, _selectedRunIndex: run, _selectedTagIndex: tag } = this;
function shouldSkip(datasets: any, run: any, tag: any): boolean {
return (
- !newDatasets || !newDatasets[run] || !(newDatasets[run] as any).tags[tag] || (newDatasets[run] as any).tags[tag].opGraph
+ !newDatasets ||
+ !newDatasets[run] ||
+ !(newDatasets[run] as any).tags[tag] ||
+ (newDatasets[run] as any).tags[tag].opGraph
);
}
if (shouldSkip(newDatasets, run, tag)) {
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_dashboard/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_dashboard/index.ts
index 91b39feaac152dd4e4ffe06b7ff5bf9c9f50f67b..fa2edb2180fe9a3bf8849c30396ab73abf6d2c72 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_dashboard/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_dashboard/index.ts
@@ -43,15 +43,6 @@ const RUN_STORAGE_KEY = 'run';
* profile: boolean,
* }}
*/
-const TagItem = {};
-/**
- * TODO(stephanwlee): Convert this to proper type when converting to TypeScript.
- * @typedef {{
- * name: string,
- * tags: !Array,
- * }}
- */
-const RunItem = {};
/**
* tf-graph-dashboard displays a graph from a TensorFlow run.
@@ -181,9 +172,7 @@ class TfGraphDashboard extends LegacyElementMixin(PolymerElement) {
`;
- /**
- * @type {!Array}
- */
+
@property({ type: Array })
_datasets: any[] = [];
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_debugger_data_card/tf-graph-debugger-data-card.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_debugger_data_card/tf-graph-debugger-data-card.ts
index f1004ad482c3ab95d513e093506b00607d23602d..8feb67818602b62026cc41f399b4be5cebd3cc98 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_debugger_data_card/tf-graph-debugger-data-card.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_debugger_data_card/tf-graph-debugger-data-card.ts
@@ -301,35 +301,6 @@ class TfGraphDebuggerDataCard extends LegacyElementMixin(PolymerElement) {
@property({ type: Boolean, notify: true })
allStepsModeEnabled: any;
- ready(): void {
- super.ready();
- let mainContainer = document.getElementById('mainContainer');
- let scrollbarContainer = document.querySelector('tf-dashboard-layout .scrollbar') as HTMLElement | null;
- if (mainContainer && scrollbarContainer) {
- // If this component is being used inside of TensorBoard's dashboard layout, it may easily
- // cause the dashboard layout element to overflow, giving the user 2 scroll bars. Prevent
- // that by hiding whatever content overflows - the user will have to expand the viewport to
- // use this debugging card.
- mainContainer.style.overflow = 'hidden';
- scrollbarContainer.style.overflow = 'hidden';
- }
- }
-
- _healthPillsAvailable(debuggerDataEnabled: any, nodeNamesToHealthPills: any): any {
- // So long as there is a mapping (even if empty) from node name to health pills, show the
- // legend and slider. We do that because, even if no health pills exist at the current step,
- // the user may desire to change steps, and the slider must show for the user to do that.
- return debuggerDataEnabled && nodeNamesToHealthPills;
- }
-
- _computeTensorCountString(healthPillValuesForSelectedNode: any, valueIndex: any): any {
- if (!healthPillValuesForSelectedNode) {
- // No health pill data is available.
- return '';
- }
- return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
- }
-
@computed(
'nodeNamesToHealthPills',
'healthPillStepIndex',
@@ -433,10 +404,6 @@ class TfGraphDebuggerDataCard extends LegacyElementMixin(PolymerElement) {
return 0;
}
- _hasDebuggerNumericAlerts(debuggerNumericAlerts: any): any {
- return debuggerNumericAlerts?.length;
- }
-
@observe('debuggerNumericAlerts')
_updateAlertsList(): void {
let debuggerNumericAlerts = this.debuggerNumericAlerts;
@@ -495,6 +462,39 @@ class TfGraphDebuggerDataCard extends LegacyElementMixin(PolymerElement) {
}
}
+ override ready(): void {
+ super.ready();
+ let mainContainer = document.getElementById('mainContainer');
+ let scrollbarContainer = document.querySelector('tf-dashboard-layout .scrollbar') as HTMLElement | null;
+ if (mainContainer && scrollbarContainer) {
+ // If this component is being used inside of TensorBoard's dashboard layout, it may easily
+ // cause the dashboard layout element to overflow, giving the user 2 scroll bars. Prevent
+ // that by hiding whatever content overflows - the user will have to expand the viewport to
+ // use this debugging card.
+ mainContainer.style.overflow = 'hidden';
+ scrollbarContainer.style.overflow = 'hidden';
+ }
+ }
+
+ _hasDebuggerNumericAlerts(debuggerNumericAlerts: any): any {
+ return debuggerNumericAlerts?.length;
+ }
+
+ _healthPillsAvailable(debuggerDataEnabled: any, nodeNamesToHealthPills: any): any {
+ // So long as there is a mapping (even if empty) from node name to health pills, show the
+ // legend and slider. We do that because, even if no health pills exist at the current step,
+ // the user may desire to change steps, and the slider must show for the user to do that.
+ return debuggerDataEnabled && nodeNamesToHealthPills;
+ }
+
+ _computeTensorCountString(healthPillValuesForSelectedNode: any, valueIndex: any): any {
+ if (!healthPillValuesForSelectedNode) {
+ // No health pill data is available.
+ return '';
+ }
+ return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
+ }
+
// Adds a listener to an element, so that when that element is clicked, the tensor with
// tensorName expands.
_addOpExpansionListener(clickableElement: any, tensorName: any): void {
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_loader/tf-graph-dashboard-loader.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_loader/tf-graph-dashboard-loader.ts
index da3df7898632974cf0f0db603ef4ee485b8aed55..8452d1ce9c82eb1ff45d240df9ed5bd36cdaa4cc 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_loader/tf-graph-dashboard-loader.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_loader/tf-graph-dashboard-loader.ts
@@ -33,13 +33,13 @@ interface GraphRunTag {
}
interface Components {
- Menu: object;
- ToolTip: '';
- Colors: object;
- OverflowCheck: boolean;
- MicroSteps: number;
- StepList: [];
- UnMatchedNode: [];
+ menu: object;
+ tooltips: string;
+ colors: object;
+ overflowCheck: boolean;
+ microSteps: number;
+ stepList: [];
+ unMatchedNode: [];
match: [];
}
/**
@@ -53,6 +53,8 @@ interface Components {
*/
@customElement('tf-graph-dashboard-loader')
class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
+ static readonly _template = null;
+
@property({ type: Array })
datasets: any[];
@@ -75,7 +77,7 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
selectedFile: object;
@property({ type: Object })
- hierarchyParams = tf_graph_hierarchy.DefaultHierarchyParams;
+ hierarchyParams = tf_graph_hierarchy.defaultHierarchyParams;
@property({
type: Object,
@@ -100,32 +102,27 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
@property({ type: Object })
_graphRunTag: GraphRunTag;
- override _template = null;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
menu: object;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
colorset: object;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
tooltips: object;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
colors: any;
@@ -138,34 +135,43 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
microsteps: any;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
steplist: any;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
unmatched: object;
@property({
type: Object,
- readOnly: true, // readonly so outsider can't change this via binding
notify: true,
})
matchedlist: object;
- getColors(): any {
- return this.colors;
+ @observe('selectedFile')
+ _selectedFileChanged(): void {
+ let e = this.selectedFile;
+ if (!e) {
+ return;
+ }
+ const target = (e as any).target as HTMLInputElement;
+ const file = target.files?.[0];
+ if (!file) {
+ return;
+ }
+ // Clear out the value of the file chooser. This ensures that if the user
+ // selects the same file, we'll re-read it.
+ target.value = '';
+ this._fetchAndConstructHierarchicalGraph(null, file);
}
@observe('selection')
@@ -180,6 +186,9 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
});
}
+ getColors(): any {
+ return this.colors;
+ }
_setCompoments(componentsPath): Promise
{
return new Promise(async (resolve, reject) => {
this.set('progress', {
@@ -212,7 +221,7 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
}
}.bind(this);
- const fetchTask = async function (): Promise {
+ const fetchTask = async (): Promise => {
let componentsStr;
try {
componentsStr = await tf_graph_parser.fetchPbTxt(componentsPath);
@@ -225,13 +234,13 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
shouldBreak = true; // 正常流程也停止定时器
let components: Components = {
- Menu: [],
- ToolTip: '',
- Colors: {},
- OverflowCheck: false,
- MicroSteps: 0,
- StepList: [],
- UnMatchedNode: [],
+ menu: [],
+ tooltips: '',
+ colors: {},
+ overflowCheck: false,
+ microSteps: 0,
+ stepList: [],
+ unMatchedNode: [],
match: [],
};
@@ -250,32 +259,32 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
);
return;
}
-
+ console.log('components', components);
// 后续处理逻辑...
- const entries = Object.entries(components.ToolTip);
+ const entries = Object.entries(components.tooltips || {});
const toolTipObject = Object.fromEntries(entries);
- this._setMenu(components.Menu);
- this._setTooltips(toolTipObject);
- this._setColors(components.Colors);
- this.set('overflowcheck', components.OverflowCheck);
- this._setColorset(Object.entries(components.Colors));
- this._setUnmatched(components.UnMatchedNode);
- this._setMatchedlist(components.match);
+ this.set('menu', components.menu);
+ this.set('tooltips', toolTipObject);
+ this.set('colors', components.colors);
+ this.set('overflowcheck', components.overflowCheck);
+ this.set('colorset', Object.entries(components.colors || {}));
+ this.set('unmatched', components.unMatchedNode);
+ this.set('matchedlist', components.match);
- tf_graph_node.getColors(components.Colors);
+ tf_graph_node.getColors(components.colors);
- const microstepsCount = Number(components.MicroSteps);
+ const microstepsCount = Number(components.microSteps);
if (microstepsCount) {
const microstepsArray = ['ALL', ...Array.from({ length: microstepsCount }, (_, index) => index)];
- this._setMicrosteps(microstepsArray);
+ this.set('microsteps', microstepsArray);
} else {
- this._setMicrosteps([]);
+ this.set('microsteps', []);
}
- const steplistCount = Number(components.MicroSteps);
- this._setSteplist(steplistCount ? components.StepList : []);
+ const steplistCount = Number(components.microSteps);
+ this.set('steplist', steplistCount ? components.stepList : []);
resolve();
- }.bind(this);
+ }
// 同时启动定时器和 fetch 任务
await Promise.all([timerTask(), fetchTask()]);
@@ -288,7 +297,7 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
case tf_graph_common.SelectionType.OP_GRAPH:
case tf_graph_common.SelectionType.CONCEPTUAL_GRAPH: {
// Clear stats about the previous graph.
- this.set('outStats', null)
+ this.set('outStats', null);
const params = new URLSearchParams();
params.set('run', run);
params.set('conceptual', String(selectionType === tf_graph_common.SelectionType.CONCEPTUAL_GRAPH));
@@ -334,21 +343,4 @@ class TfGraphDashboardLoader extends LegacyElementMixin(PolymerElement) {
}.bind(this),
);
}
-
- @observe('selectedFile')
- _selectedFileChanged(): void {
- let e = this.selectedFile;
- if (!e) {
- return;
- }
- const target = (e as any).target as HTMLInputElement;
- const file = target.files?.[0];
- if (!file) {
- return;
- }
- // Clear out the value of the file chooser. This ensures that if the user
- // selects the same file, we'll re-read it.
- target.value = '';
- this._fetchAndConstructHierarchicalGraph(null, file);
- }
}
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_resize_height/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_resize_height/index.ts
index 2e7f8e5e652ebbe21f3281a1a306d99a2a6a2acc..d569317564fc4a16e1a965ccb2cd8386cf51f787 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_resize_height/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_resize_height/index.ts
@@ -19,43 +19,41 @@ import { customElement, property, observe } from '@polymer/decorators';
@customElement('tf-resize-height')
class ResizableTabsheet extends PolymerElement {
- static get template(): HTMLTemplateElement {
- return html`
-
+ .resize-handle:hover {
+ background-color: hsl(214, 100%, 43%);
+ height: 4px;
+ }
+
-
-
-
-
- `;
- }
+
+
+
+
+ `;
@property({
type: Number,
@@ -63,8 +61,8 @@ class ResizableTabsheet extends PolymerElement {
})
height: number = 300;
- _resize: (event: MouseEvent) => void = () => { };
- _stopResize: (this: Document, ev: MouseEvent) => any = () => { };
+ _resize: (event: MouseEvent) => void = () => {};
+ _stopResize: (this: Document, ev: MouseEvent) => any = () => {};
@observe('height')
_updateHeight(newHeight): void {
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaadin_table/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaadin_table/index.ts
index 289ed28a9239ab84d70ec241bfdb0d97f800d99d..ff03bc2e051be53f7c33d88f0b9d55a7da52dd28 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaadin_table/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaadin_table/index.ts
@@ -21,84 +21,82 @@ import '@vaadin/tooltip';
import type { GridEventContext } from '@vaadin/grid';
@customElement('tf-vaadin-table')
class TfVaadinTable extends PolymerElement {
- static get template(): HTMLTemplateElement {
- return html`
-
-
-
-
-
-
-
-
-
-
- 当前节点暂无IO数据
-
- `;
- }
+ static readonly template = html`
+
+
+
+
+
+
+
+
+
+
+ 当前节点暂无IO数据
+
+ `;
@property({ type: Object })
syncGrid?: HTMLElement; // 点击高亮需要同步的表格元素
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaddin_text_table/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaddin_text_table/index.ts
index ac6dec3e1ecbc167e2d6c003b2ef95457ed097f1..9386f9735f771825e4fd2a02f61a392e80818b87 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaddin_text_table/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/components/tf_vaddin_text_table/index.ts
@@ -22,77 +22,75 @@ import type { GridEventContext } from '@vaadin/grid';
import { Notification } from '@vaadin/notification';
@customElement('tf-vaadin-text-table')
class TfVaadinTable extends PolymerElement {
- static get template(): HTMLTemplateElement {
- return html`
-
-
-
-
-
-
- [[item[header]]]
-
-
-
-
-
-
- 当前节点暂无数据
-
- `;
- }
+ static readonly template = html`
+
+
+
+
+
+
+ [[item[header]]]
+
+
+
+
+
+
+ 当前节点暂无数据
+
+ `;
@property({ type: Object })
syncGrid!: HTMLElement; // 点击高亮需要同步的表格元素
diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/index.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/index.ts
index cb394caf5f768d7fe9c9e54fcb85a6491ef6fcae..3951193c5d13246df5ca1340b5bbe255b32d1b0c 100644
--- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/index.ts
+++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph_node_info/index.ts
@@ -30,8 +30,7 @@ import { BENCH_PREFIX, NPU_PREFIX } from '../tf_graph_common/common';
@customElement('tf-graph-vaadin-tab')
class TfGraphNodeInfo extends PolymerElement {
- static get template(): HTMLTemplateElement {
- return html`
+ static readonly template = html`