|
|
|
@@ -1,17 +1,4 @@
|
|
|
|
|
import {
|
|
|
|
|
cloneDeep,
|
|
|
|
|
defaults as _defaults,
|
|
|
|
|
each,
|
|
|
|
|
filter,
|
|
|
|
|
find,
|
|
|
|
|
findIndex,
|
|
|
|
|
indexOf,
|
|
|
|
|
isEqual,
|
|
|
|
|
map,
|
|
|
|
|
maxBy,
|
|
|
|
|
pull,
|
|
|
|
|
some,
|
|
|
|
|
} from 'lodash';
|
|
|
|
|
import { cloneDeep, defaults as _defaults, filter, indexOf, isEqual, map, maxBy, pull } from 'lodash';
|
|
|
|
|
import { Subscription } from 'rxjs';
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
@@ -194,14 +181,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addBuiltInAnnotationQuery() {
|
|
|
|
|
let found = false;
|
|
|
|
|
for (const item of this.annotations.list) {
|
|
|
|
|
if (item.builtIn === 1) {
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const found = this.annotations.list.some((item) => item.builtIn === 1);
|
|
|
|
|
if (found) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@@ -267,9 +247,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
|
|
|
|
|
// sort by keys
|
|
|
|
|
copy = sortedDeepCloneWithoutNulls(copy);
|
|
|
|
|
copy.getVariables = () => {
|
|
|
|
|
return copy.templating.list;
|
|
|
|
|
};
|
|
|
|
|
copy.getVariables = () => copy.templating.list;
|
|
|
|
|
|
|
|
|
|
return copy;
|
|
|
|
|
}
|
|
|
|
@@ -296,24 +274,11 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
|
|
|
|
|
private getPanelSaveModels() {
|
|
|
|
|
return this.panels
|
|
|
|
|
.filter((panel: PanelModel) => {
|
|
|
|
|
if (this.isSnapshotTruthy()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (panel.type === 'add-panel') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// skip repeated panels in the saved model
|
|
|
|
|
if (panel.repeatPanelId) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// skip repeated rows in the saved model
|
|
|
|
|
if (panel.repeatedByRow) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
})
|
|
|
|
|
.map((panel: PanelModel) => {
|
|
|
|
|
.filter(
|
|
|
|
|
(panel) =>
|
|
|
|
|
this.isSnapshotTruthy() || !(panel.type === 'add-panel' || panel.repeatPanelId || panel.repeatedByRow)
|
|
|
|
|
)
|
|
|
|
|
.map((panel) => {
|
|
|
|
|
// If we save while editing we should include the panel in edit mode instead of the
|
|
|
|
|
// unmodified source panel
|
|
|
|
|
if (this.panelInEdit && this.panelInEdit.id === panel.id) {
|
|
|
|
@@ -358,18 +323,19 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!defaults.saveVariables) {
|
|
|
|
|
for (let i = 0; i < copy.templating.list.length; i++) {
|
|
|
|
|
const current = copy.templating.list[i];
|
|
|
|
|
const original: any = find(originalVariables, { name: current.name, type: current.type });
|
|
|
|
|
for (const current of copy.templating.list) {
|
|
|
|
|
const original = originalVariables.find(
|
|
|
|
|
({ name, type }: any) => name === current.name && type === current.type
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!original) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (current.type === 'adhoc') {
|
|
|
|
|
copy.templating.list[i].filters = original.filters;
|
|
|
|
|
current.filters = original.filters;
|
|
|
|
|
} else {
|
|
|
|
|
copy.templating.list[i].current = original.current;
|
|
|
|
|
current.current = original.current;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -384,18 +350,14 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
this.events.publish(new RefreshEvent());
|
|
|
|
|
this.lastRefresh = Date.now();
|
|
|
|
|
|
|
|
|
|
if (this.panelInEdit) {
|
|
|
|
|
if (event.refreshAll || event.panelIds.includes(this.panelInEdit.id)) {
|
|
|
|
|
this.panelInEdit.refresh();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this.panelInEdit && (event.refreshAll || event.panelIds.includes(this.panelInEdit.id))) {
|
|
|
|
|
this.panelInEdit.refresh();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
if (!this.otherPanelInFullscreen(panel)) {
|
|
|
|
|
if (event.refreshAll || event.panelIds.includes(panel.id)) {
|
|
|
|
|
panel.refresh();
|
|
|
|
|
}
|
|
|
|
|
if (!this.otherPanelInFullscreen(panel) && (event.refreshAll || event.panelIds.includes(panel.id))) {
|
|
|
|
|
panel.refresh();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -453,51 +415,40 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ensurePanelsHaveIds() {
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
if (!panel.id) {
|
|
|
|
|
panel.id = this.getNextPanelId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (panel.panels) {
|
|
|
|
|
for (const rowPanel of panel.panels) {
|
|
|
|
|
if (!rowPanel.id) {
|
|
|
|
|
rowPanel.id = this.getNextPanelId();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let nextPanelId = this.getNextPanelId();
|
|
|
|
|
for (const panel of this.panelIterator()) {
|
|
|
|
|
panel.id ??= nextPanelId++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ensureListExist(data: any) {
|
|
|
|
|
if (!data) {
|
|
|
|
|
data = {};
|
|
|
|
|
}
|
|
|
|
|
if (!data.list) {
|
|
|
|
|
data.list = [];
|
|
|
|
|
}
|
|
|
|
|
private ensureListExist(data: any = {}) {
|
|
|
|
|
data.list ??= [];
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getNextPanelId() {
|
|
|
|
|
let max = 0;
|
|
|
|
|
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
for (const panel of this.panelIterator()) {
|
|
|
|
|
if (panel.id > max) {
|
|
|
|
|
max = panel.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (panel.collapsed) {
|
|
|
|
|
for (const rowPanel of panel.panels) {
|
|
|
|
|
if (rowPanel.id > max) {
|
|
|
|
|
max = rowPanel.id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return max + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*panelIterator() {
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
yield panel;
|
|
|
|
|
|
|
|
|
|
const rowPanels = panel.panels ?? [];
|
|
|
|
|
for (const rowPanel of rowPanels) {
|
|
|
|
|
yield rowPanel as PanelModel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
forEachPanel(callback: (panel: PanelModel, index: number) => void) {
|
|
|
|
|
for (let i = 0; i < this.panels.length; i++) {
|
|
|
|
|
callback(this.panels[i], i);
|
|
|
|
@@ -509,13 +460,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
return this.panelInEdit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
if (panel.id === id) {
|
|
|
|
|
return panel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
return this.panels.find((p) => p.id === id) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canEditPanel(panel?: PanelModel | null): boolean | undefined | null {
|
|
|
|
@@ -553,13 +498,12 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasUnsavedChanges() {
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
if (panel.hasChanged) {
|
|
|
|
|
console.log('Panel has changed', panel);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
const changedPanel = this.panels.find((p) => p.hasChanged);
|
|
|
|
|
if (changedPanel) {
|
|
|
|
|
console.log('Panel has changed', changedPanel);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return Boolean(changedPanel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanUpRepeats() {
|
|
|
|
@@ -568,17 +512,12 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.iteration = (this.iteration || new Date().getTime()) + 1;
|
|
|
|
|
const panelsToRemove = [];
|
|
|
|
|
|
|
|
|
|
// cleanup scopedVars
|
|
|
|
|
deleteScopeVars(this.panels);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < this.panels.length; i++) {
|
|
|
|
|
const panel = this.panels[i];
|
|
|
|
|
if ((!panel.repeat || panel.repeatedByRow) && panel.repeatPanelId && panel.repeatIteration !== this.iteration) {
|
|
|
|
|
panelsToRemove.push(panel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const panelsToRemove = this.panels.filter(
|
|
|
|
|
(p) => (!p.repeat || p.repeatedByRow) && p.repeatPanelId && p.repeatIteration !== this.iteration
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// remove panels
|
|
|
|
|
pull(this.panels, ...panelsToRemove);
|
|
|
|
@@ -607,13 +546,8 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanUpRowRepeats(rowPanels: PanelModel[]) {
|
|
|
|
|
const panelsToRemove = [];
|
|
|
|
|
for (let i = 0; i < rowPanels.length; i++) {
|
|
|
|
|
const panel = rowPanels[i];
|
|
|
|
|
if (!panel.repeat && panel.repeatPanelId) {
|
|
|
|
|
panelsToRemove.push(panel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const panelsToRemove = rowPanels.filter((p) => !p.repeat && p.repeatPanelId);
|
|
|
|
|
|
|
|
|
|
pull(rowPanels, ...panelsToRemove);
|
|
|
|
|
pull(this.panels, ...panelsToRemove);
|
|
|
|
|
}
|
|
|
|
@@ -623,18 +557,17 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let rowPanels = row.panels;
|
|
|
|
|
let rowPanels = row.panels ?? [];
|
|
|
|
|
if (!row.collapsed) {
|
|
|
|
|
const rowPanelIndex = findIndex(this.panels, (p: PanelModel) => p.id === row.id);
|
|
|
|
|
const rowPanelIndex = this.panels.findIndex((p) => p.id === row.id);
|
|
|
|
|
rowPanels = this.getRowPanels(rowPanelIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.cleanUpRowRepeats(rowPanels);
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < rowPanels.length; i++) {
|
|
|
|
|
const panel = rowPanels[i];
|
|
|
|
|
for (const panel of rowPanels) {
|
|
|
|
|
if (panel.repeat) {
|
|
|
|
|
const panelIndex = findIndex(this.panels, (p: PanelModel) => p.id === panel.id);
|
|
|
|
|
const panelIndex = this.panels.findIndex((p) => p.id === panel.id);
|
|
|
|
|
this.repeatPanel(panel, panelIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -679,13 +612,13 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
// for row clones we need to figure out panels under row to clone and where to insert clone
|
|
|
|
|
let rowPanels: PanelModel[], insertPos: number;
|
|
|
|
|
if (sourceRowPanel.collapsed) {
|
|
|
|
|
rowPanels = cloneDeep(sourceRowPanel.panels);
|
|
|
|
|
rowPanels = cloneDeep(sourceRowPanel.panels) ?? [];
|
|
|
|
|
clone.panels = rowPanels;
|
|
|
|
|
// insert copied row after preceding row
|
|
|
|
|
insertPos = sourcePanelIndex + valueIndex;
|
|
|
|
|
} else {
|
|
|
|
|
rowPanels = this.getRowPanels(sourcePanelIndex);
|
|
|
|
|
clone.panels = map(rowPanels, (panel: PanelModel) => panel.getSaveModel());
|
|
|
|
|
clone.panels = rowPanels.map((panel) => panel.getSaveModel());
|
|
|
|
|
// insert copied row after preceding row's panels
|
|
|
|
|
insertPos = sourcePanelIndex + (rowPanels.length + 1) * valueIndex;
|
|
|
|
|
}
|
|
|
|
@@ -696,7 +629,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repeatPanel(panel: PanelModel, panelIndex: number) {
|
|
|
|
|
const variable: any = this.getPanelRepeatVariable(panel);
|
|
|
|
|
const variable = this.getPanelRepeatVariable(panel);
|
|
|
|
|
if (!variable) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@@ -717,7 +650,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
let copy;
|
|
|
|
|
|
|
|
|
|
copy = this.getPanelRepeatClone(panel, index, panelIndex);
|
|
|
|
|
copy.scopedVars = copy.scopedVars || {};
|
|
|
|
|
copy.scopedVars ??= {};
|
|
|
|
|
copy.scopedVars[variable.name] = option;
|
|
|
|
|
|
|
|
|
|
if (panel.repeatDirection === REPEAT_DIR_VERTICAL) {
|
|
|
|
@@ -746,12 +679,12 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
const yOffset = yPos - panel.gridPos.y;
|
|
|
|
|
if (yOffset > 0) {
|
|
|
|
|
const panelBelowIndex = panelIndex + selectedOptions.length;
|
|
|
|
|
for (let i = panelBelowIndex; i < this.panels.length; i++) {
|
|
|
|
|
if (isOnTheSameGridRow(panel, this.panels[i])) {
|
|
|
|
|
for (const curPanel of this.panels.slice(panelBelowIndex)) {
|
|
|
|
|
if (isOnTheSameGridRow(panel, curPanel)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.panels[i].gridPos.y += yOffset;
|
|
|
|
|
curPanel.gridPos.y += yOffset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -761,7 +694,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
let yPos = panel.gridPos.y;
|
|
|
|
|
|
|
|
|
|
function setScopedVars(panel: PanelModel, variableOption: any) {
|
|
|
|
|
panel.scopedVars = panel.scopedVars || {};
|
|
|
|
|
panel.scopedVars ??= {};
|
|
|
|
|
panel.scopedVars[variable.name] = variableOption;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -776,19 +709,19 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
|
|
|
|
|
if (panel.collapsed) {
|
|
|
|
|
// For collapsed row just copy its panels and set scoped vars and proper IDs
|
|
|
|
|
each(rowPanels, (rowPanel: PanelModel, i: number) => {
|
|
|
|
|
for (const rowPanel of rowPanels) {
|
|
|
|
|
setScopedVars(rowPanel, option);
|
|
|
|
|
if (optionIndex > 0) {
|
|
|
|
|
this.updateRepeatedPanelIds(rowPanel, true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
rowCopy.gridPos.y += optionIndex;
|
|
|
|
|
yPos += optionIndex;
|
|
|
|
|
panelBelowIndex = panelIndex + optionIndex + 1;
|
|
|
|
|
} else {
|
|
|
|
|
// insert after 'row' panel
|
|
|
|
|
const insertPos = panelIndex + (rowPanels.length + 1) * optionIndex + 1;
|
|
|
|
|
each(rowPanels, (rowPanel: PanelModel, i: number) => {
|
|
|
|
|
rowPanels.forEach((rowPanel: PanelModel, i: number) => {
|
|
|
|
|
setScopedVars(rowPanel, option);
|
|
|
|
|
if (optionIndex > 0) {
|
|
|
|
|
const cloneRowPanel = new PanelModel(rowPanel);
|
|
|
|
@@ -806,8 +739,8 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
|
|
|
|
|
// Update gridPos for panels below if we inserted more than 1 repeated row panel
|
|
|
|
|
if (selectedOptions.length > 1) {
|
|
|
|
|
for (let i = panelBelowIndex; i < this.panels.length; i++) {
|
|
|
|
|
this.panels[i].gridPos.y += yPos;
|
|
|
|
|
for (const panel of this.panels.slice(panelBelowIndex)) {
|
|
|
|
|
panel.gridPos.y += yPos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -839,11 +772,10 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
if (!rowPanel.panels || rowPanel.panels.length === 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rowYPos = rowPanel.gridPos.y;
|
|
|
|
|
const positions = map(rowPanel.panels, 'gridPos');
|
|
|
|
|
const maxPos = maxBy(positions, (pos: GridPos) => {
|
|
|
|
|
return pos.y + pos.h;
|
|
|
|
|
});
|
|
|
|
|
const maxPos = maxBy(positions, (pos: GridPos) => pos.y + pos.h);
|
|
|
|
|
return maxPos!.y + maxPos!.h - rowYPos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -853,9 +785,9 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeRow(row: PanelModel, removePanels: boolean) {
|
|
|
|
|
const needToogle = (!removePanels && row.collapsed) || (removePanels && !row.collapsed);
|
|
|
|
|
const needToggle = (!removePanels && row.collapsed) || (removePanels && !row.collapsed);
|
|
|
|
|
|
|
|
|
|
if (needToogle) {
|
|
|
|
|
if (needToggle) {
|
|
|
|
|
this.toggleRow(row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -863,60 +795,30 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expandRows() {
|
|
|
|
|
for (let i = 0; i < this.panels.length; i++) {
|
|
|
|
|
const panel = this.panels[i];
|
|
|
|
|
|
|
|
|
|
if (panel.type !== 'row') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (panel.collapsed) {
|
|
|
|
|
this.toggleRow(panel);
|
|
|
|
|
}
|
|
|
|
|
const collapsedRows = this.panels.filter((p) => p.type === 'row' && p.collapsed);
|
|
|
|
|
for (const row of collapsedRows) {
|
|
|
|
|
this.toggleRow(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
collapseRows() {
|
|
|
|
|
for (let i = 0; i < this.panels.length; i++) {
|
|
|
|
|
const panel = this.panels[i];
|
|
|
|
|
|
|
|
|
|
if (panel.type !== 'row') {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!panel.collapsed) {
|
|
|
|
|
this.toggleRow(panel);
|
|
|
|
|
}
|
|
|
|
|
const collapsedRows = this.panels.filter((p) => p.type === 'row' && !p.collapsed);
|
|
|
|
|
for (const row of collapsedRows) {
|
|
|
|
|
this.toggleRow(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isSubMenuVisible() {
|
|
|
|
|
if (this.links.length > 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.getVariables().find((variable) => variable.hide !== 2)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.annotations.list.find((annotation) => annotation.hide !== true)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
return (
|
|
|
|
|
this.links.length > 0 ||
|
|
|
|
|
this.getVariables().some((variable) => variable.hide !== 2) ||
|
|
|
|
|
this.annotations.list.some((annotation) => !annotation.hide)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getPanelInfoById(panelId: number) {
|
|
|
|
|
for (let i = 0; i < this.panels.length; i++) {
|
|
|
|
|
if (this.panels[i].id === panelId) {
|
|
|
|
|
return {
|
|
|
|
|
panel: this.panels[i],
|
|
|
|
|
index: i,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
const panelIndex = this.panels.findIndex((p) => p.id === panelId);
|
|
|
|
|
return panelIndex >= 0 ? { panel: this.panels[panelIndex], index: panelIndex } : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
duplicatePanel(panel: PanelModel) {
|
|
|
|
@@ -962,64 +864,64 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
toggleRow(row: PanelModel) {
|
|
|
|
|
const rowIndex = indexOf(this.panels, row);
|
|
|
|
|
|
|
|
|
|
if (row.collapsed) {
|
|
|
|
|
row.collapsed = false;
|
|
|
|
|
const hasRepeat = some(row.panels as PanelModel[], (p: PanelModel) => p.repeat);
|
|
|
|
|
if (!row.collapsed) {
|
|
|
|
|
const rowPanels = this.getRowPanels(rowIndex);
|
|
|
|
|
|
|
|
|
|
if (row.panels.length > 0) {
|
|
|
|
|
// Use first panel to figure out if it was moved or pushed
|
|
|
|
|
// If the panel doesn't have gridPos.y, use the row gridPos.y instead.
|
|
|
|
|
// This can happen for some generated dashboards.
|
|
|
|
|
const firstPanelYPos = row.panels[0].gridPos.y ?? row.gridPos.y;
|
|
|
|
|
const yDiff = firstPanelYPos - (row.gridPos.y + row.gridPos.h);
|
|
|
|
|
|
|
|
|
|
// start inserting after row
|
|
|
|
|
let insertPos = rowIndex + 1;
|
|
|
|
|
// y max will represent the bottom y pos after all panels have been added
|
|
|
|
|
// needed to know home much panels below should be pushed down
|
|
|
|
|
let yMax = row.gridPos.y;
|
|
|
|
|
|
|
|
|
|
for (const panel of row.panels) {
|
|
|
|
|
// set the y gridPos if it wasn't already set
|
|
|
|
|
panel.gridPos.y ?? (panel.gridPos.y = row.gridPos.y); // (Safari 13.1 lacks ??= support)
|
|
|
|
|
// make sure y is adjusted (in case row moved while collapsed)
|
|
|
|
|
panel.gridPos.y -= yDiff;
|
|
|
|
|
// insert after row
|
|
|
|
|
this.panels.splice(insertPos, 0, new PanelModel(panel));
|
|
|
|
|
// update insert post and y max
|
|
|
|
|
insertPos += 1;
|
|
|
|
|
yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pushDownAmount = yMax - row.gridPos.y - 1;
|
|
|
|
|
|
|
|
|
|
// push panels below down
|
|
|
|
|
for (let panelIndex = insertPos; panelIndex < this.panels.length; panelIndex++) {
|
|
|
|
|
this.panels[panelIndex].gridPos.y += pushDownAmount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
row.panels = [];
|
|
|
|
|
|
|
|
|
|
if (hasRepeat) {
|
|
|
|
|
this.processRowRepeats(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sort panels
|
|
|
|
|
this.sortPanelsByGridPos();
|
|
|
|
|
// remove panels
|
|
|
|
|
pull(this.panels, ...rowPanels);
|
|
|
|
|
// save panel models inside row panel
|
|
|
|
|
row.panels = rowPanels.map((panel: PanelModel) => panel.getSaveModel());
|
|
|
|
|
row.collapsed = true;
|
|
|
|
|
|
|
|
|
|
// emit change event
|
|
|
|
|
this.events.publish(new DashboardPanelsChangedEvent());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const rowPanels = this.getRowPanels(rowIndex);
|
|
|
|
|
row.collapsed = false;
|
|
|
|
|
const rowPanels = row.panels ?? [];
|
|
|
|
|
const hasRepeat = rowPanels.some((p: PanelModel) => p.repeat);
|
|
|
|
|
if (rowPanels.length > 0) {
|
|
|
|
|
// Use first panel to figure out if it was moved or pushed
|
|
|
|
|
// If the panel doesn't have gridPos.y, use the row gridPos.y instead.
|
|
|
|
|
// This can happen for some generated dashboards.
|
|
|
|
|
const firstPanelYPos = rowPanels[0].gridPos.y ?? row.gridPos.y;
|
|
|
|
|
const yDiff = firstPanelYPos - (row.gridPos.y + row.gridPos.h);
|
|
|
|
|
|
|
|
|
|
// remove panels
|
|
|
|
|
pull(this.panels, ...rowPanels);
|
|
|
|
|
// save panel models inside row panel
|
|
|
|
|
row.panels = map(rowPanels, (panel: PanelModel) => panel.getSaveModel());
|
|
|
|
|
row.collapsed = true;
|
|
|
|
|
// start inserting after row
|
|
|
|
|
let insertPos = rowIndex + 1;
|
|
|
|
|
// y max will represent the bottom y pos after all panels have been added
|
|
|
|
|
// needed to know home much panels below should be pushed down
|
|
|
|
|
let yMax = row.gridPos.y;
|
|
|
|
|
|
|
|
|
|
for (const panel of rowPanels) {
|
|
|
|
|
// set the y gridPos if it wasn't already set
|
|
|
|
|
panel.gridPos.y ?? (panel.gridPos.y = row.gridPos.y); // (Safari 13.1 lacks ??= support)
|
|
|
|
|
// make sure y is adjusted (in case row moved while collapsed)
|
|
|
|
|
panel.gridPos.y -= yDiff;
|
|
|
|
|
// insert after row
|
|
|
|
|
this.panels.splice(insertPos, 0, new PanelModel(panel));
|
|
|
|
|
// update insert post and y max
|
|
|
|
|
insertPos += 1;
|
|
|
|
|
yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pushDownAmount = yMax - row.gridPos.y - 1;
|
|
|
|
|
|
|
|
|
|
// push panels below down
|
|
|
|
|
for (const panel of this.panels.slice(insertPos)) {
|
|
|
|
|
panel.gridPos.y += pushDownAmount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
row.panels = [];
|
|
|
|
|
|
|
|
|
|
if (hasRepeat) {
|
|
|
|
|
this.processRowRepeats(row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sort panels
|
|
|
|
|
this.sortPanelsByGridPos();
|
|
|
|
|
|
|
|
|
|
// emit change event
|
|
|
|
|
this.events.publish(new DashboardPanelsChangedEvent());
|
|
|
|
@@ -1029,19 +931,11 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
* Will return all panels after rowIndex until it encounters another row
|
|
|
|
|
*/
|
|
|
|
|
getRowPanels(rowIndex: number): PanelModel[] {
|
|
|
|
|
const rowPanels = [];
|
|
|
|
|
const panelsBelowRow = this.panels.slice(rowIndex + 1);
|
|
|
|
|
const nextRowIndex = panelsBelowRow.findIndex((p) => p.type === 'row');
|
|
|
|
|
|
|
|
|
|
for (let index = rowIndex + 1; index < this.panels.length; index++) {
|
|
|
|
|
const panel = this.panels[index];
|
|
|
|
|
|
|
|
|
|
// break when encountering another row
|
|
|
|
|
if (panel.type === 'row') {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this panel must belong to row
|
|
|
|
|
rowPanels.push(panel);
|
|
|
|
|
}
|
|
|
|
|
// Take all panels up to next row, or all panels if there are no other rows
|
|
|
|
|
const rowPanels = panelsBelowRow.slice(0, nextRowIndex >= 0 ? nextRowIndex : this.panels.length);
|
|
|
|
|
|
|
|
|
|
return rowPanels;
|
|
|
|
|
}
|
|
|
|
@@ -1095,14 +989,12 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
|
|
|
|
|
hasTimeChanged() {
|
|
|
|
|
const { time, originalTime } = this;
|
|
|
|
|
if (isEqual(time, originalTime)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare momemt values vs strings values
|
|
|
|
|
// Compare moment values vs strings values
|
|
|
|
|
return !(
|
|
|
|
|
isEqual(dateTime(time?.from), dateTime(originalTime?.from)) &&
|
|
|
|
|
isEqual(dateTime(time?.to), dateTime(originalTime?.to))
|
|
|
|
|
isEqual(time, originalTime) ||
|
|
|
|
|
(isEqual(dateTime(time?.from), dateTime(originalTime?.from)) &&
|
|
|
|
|
isEqual(dateTime(time?.to), dateTime(originalTime?.to)))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1120,11 +1012,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autoFitPanels(viewHeight: number, kioskMode?: UrlQueryValue) {
|
|
|
|
|
const currentGridHeight = Math.max(
|
|
|
|
|
...this.panels.map((panel) => {
|
|
|
|
|
return panel.gridPos.h + panel.gridPos.y;
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
const currentGridHeight = Math.max(...this.panels.map((panel) => panel.gridPos.h + panel.gridPos.y));
|
|
|
|
|
|
|
|
|
|
const navbarHeight = 55;
|
|
|
|
|
const margin = 20;
|
|
|
|
@@ -1145,10 +1033,10 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
const visibleGridHeight = Math.floor(visibleHeight / (GRID_CELL_HEIGHT + GRID_CELL_VMARGIN));
|
|
|
|
|
const scaleFactor = currentGridHeight / visibleGridHeight;
|
|
|
|
|
|
|
|
|
|
this.panels.forEach((panel, i) => {
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
panel.gridPos.y = Math.round(panel.gridPos.y / scaleFactor) || 1;
|
|
|
|
|
panel.gridPos.h = Math.round(panel.gridPos.h / scaleFactor) || 1;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
templateVariableValueUpdated() {
|
|
|
|
@@ -1160,39 +1048,32 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
const panelId = parseInt(panelUrlId ?? '0', 10);
|
|
|
|
|
|
|
|
|
|
// First try to find it in a collapsed row and exand it
|
|
|
|
|
for (const panel of this.panels) {
|
|
|
|
|
if (panel.collapsed) {
|
|
|
|
|
for (const rowPanel of panel.panels) {
|
|
|
|
|
if (rowPanel.id === panelId) {
|
|
|
|
|
this.toggleRow(panel);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const collapsedPanels = this.panels.filter((p) => p.collapsed);
|
|
|
|
|
for (const panel of collapsedPanels) {
|
|
|
|
|
const hasPanel = panel.panels?.some((rp: any) => rp.id === panelId);
|
|
|
|
|
hasPanel && this.toggleRow(panel);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.getPanelById(panelId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleLegendsForAll() {
|
|
|
|
|
const panelsWithLegends = this.panels.filter((panel) => {
|
|
|
|
|
return panel.legend !== undefined && panel.legend !== null;
|
|
|
|
|
});
|
|
|
|
|
const panelsWithLegends = this.panels.filter(isPanelWithLegend);
|
|
|
|
|
|
|
|
|
|
// determine if more panels are displaying legends or not
|
|
|
|
|
const onCount = panelsWithLegends.filter((panel) => panel.legend!.show).length;
|
|
|
|
|
const onCount = panelsWithLegends.filter((panel) => panel.legend.show).length;
|
|
|
|
|
const offCount = panelsWithLegends.length - onCount;
|
|
|
|
|
const panelLegendsOn = onCount >= offCount;
|
|
|
|
|
|
|
|
|
|
for (const panel of panelsWithLegends) {
|
|
|
|
|
panel.legend!.show = !panelLegendsOn;
|
|
|
|
|
panel.legend.show = !panelLegendsOn;
|
|
|
|
|
panel.render();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getVariables = () => {
|
|
|
|
|
getVariables() {
|
|
|
|
|
return this.getVariablesFromState(this.uid);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canEditAnnotations(dashboardId: number) {
|
|
|
|
|
let canEdit = true;
|
|
|
|
@@ -1209,13 +1090,8 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canAddAnnotations() {
|
|
|
|
|
let canAdd = true;
|
|
|
|
|
|
|
|
|
|
// if RBAC is enabled there are additional conditions to check
|
|
|
|
|
if (contextSrv.accessControlEnabled()) {
|
|
|
|
|
canAdd = !!this.meta.annotationsPermissions?.dashboard.canAdd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If RBAC is enabled there are additional conditions to check
|
|
|
|
|
const canAdd = !contextSrv.accessControlEnabled() || this.meta.annotationsPermissions?.dashboard.canAdd;
|
|
|
|
|
return this.canEditDashboard() && canAdd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1247,27 +1123,23 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = map(currentVariables, (variable: any) => {
|
|
|
|
|
return {
|
|
|
|
|
name: variable.name,
|
|
|
|
|
type: variable.type,
|
|
|
|
|
current: cloneDeep(variable.current),
|
|
|
|
|
filters: cloneDeep(variable.filters),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
const updated = currentVariables.map((variable: any) => ({
|
|
|
|
|
name: variable.name,
|
|
|
|
|
type: variable.type,
|
|
|
|
|
current: cloneDeep(variable.current),
|
|
|
|
|
filters: cloneDeep(variable.filters),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return !isEqual(updated, originalVariables);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private cloneVariablesFrom(variables: any[]): any[] {
|
|
|
|
|
return variables.map((variable) => {
|
|
|
|
|
return {
|
|
|
|
|
name: variable.name,
|
|
|
|
|
type: variable.type,
|
|
|
|
|
current: cloneDeep(variable.current),
|
|
|
|
|
filters: cloneDeep(variable.filters),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
return variables.map((variable) => ({
|
|
|
|
|
name: variable.name,
|
|
|
|
|
type: variable.type,
|
|
|
|
|
current: cloneDeep(variable.current),
|
|
|
|
|
filters: cloneDeep(variable.filters),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private variablesTimeRangeProcessDoneHandler(event: VariablesTimeRangeProcessDone) {
|
|
|
|
@@ -1299,3 +1171,7 @@ export class DashboardModel implements TimeModel {
|
|
|
|
|
this.startRefresh(event.payload);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isPanelWithLegend(panel: PanelModel): panel is PanelModel & Pick<Required<PanelModel>, 'legend'> {
|
|
|
|
|
return Boolean(panel.legend);
|
|
|
|
|
}
|
|
|
|
|