= {};\n\n constructor(\n private panel: { styles: ColumnStyle[]; pageSize: number },\n private table: TableRenderModel,\n private timeZone: TimeZone,\n private sanitize: (v: any) => any,\n private templateSrv: TemplateSrv = getTemplateSrv(),\n private theme: GrafanaTheme2\n ) {\n this.initColumns();\n }\n\n setTable(table: TableRenderModel) {\n this.table = table;\n\n this.initColumns();\n }\n\n initColumns() {\n this.formatters = [];\n this.colorState = {};\n\n for (let colIndex = 0; colIndex < this.table.columns.length; colIndex++) {\n const column = this.table.columns[colIndex];\n column.title = column.text;\n\n for (let i = 0; i < this.panel.styles.length; i++) {\n const style = this.panel.styles[i];\n\n const escapedPattern = stringStartsAsRegEx(style.pattern)\n ? style.pattern\n : escapeStringForRegex(unEscapeStringFromRegex(style.pattern));\n const regex = stringToJsRegex(escapedPattern);\n if (column.text.match(regex)) {\n column.style = style;\n\n if (style.alias) {\n column.title = textUtil.escapeHtml(column.text.replace(regex, style.alias));\n }\n\n break;\n }\n }\n\n this.formatters[colIndex] = this.createColumnFormatter(column);\n }\n }\n\n getColorForValue(value: number, style: ColumnStyle) {\n if (!style.thresholds || !style.colors) {\n return null;\n }\n for (let i = style.thresholds.length; i > 0; i--) {\n if (value >= style.thresholds[i - 1]) {\n return this.theme.visualization.getColorByName(style.colors[i]);\n }\n }\n return this.theme.visualization.getColorByName(first(style.colors));\n }\n\n defaultCellFormatter(v: any, style: ColumnStyle) {\n if (v === null || v === void 0 || v === undefined) {\n return '';\n }\n\n if (isArray(v)) {\n v = v.join(', ');\n }\n\n if (style && style.sanitize) {\n return this.sanitize(v);\n } else {\n return escape(v);\n }\n }\n\n createColumnFormatter(column: ColumnRender) {\n if (!column.style) {\n return this.defaultCellFormatter;\n }\n\n if (column.style.type === 'hidden') {\n return (v: any): undefined => undefined;\n }\n\n if (column.style.type === 'date') {\n return (v: any) => {\n if (v === undefined || v === null) {\n return '-';\n }\n\n if (isArray(v)) {\n v = v[0];\n }\n\n // if is an epoch (numeric string and len > 12)\n if (isString(v) && !isNaN(v as any) && v.length > 12) {\n v = parseInt(v, 10);\n }\n\n if (!column.style.dateFormat) {\n return dateTimeFormatISO(v, {\n timeZone: this.timeZone,\n });\n }\n\n return dateTimeFormat(v, {\n format: column.style.dateFormat,\n timeZone: this.timeZone,\n });\n };\n }\n\n if (column.style.type === 'string') {\n return (v: any) => {\n if (isArray(v)) {\n v = v.join(', ');\n }\n\n const mappingType = column.style.mappingType || 0;\n\n if (mappingType === 1 && column.style.valueMaps) {\n for (let i = 0; i < column.style.valueMaps.length; i++) {\n const map = column.style.valueMaps[i];\n\n if (v === null) {\n if (map.value === 'null') {\n return map.text;\n }\n continue;\n }\n\n // Allow both numeric and string values to be mapped\n if ((!isString(v) && Number(map.value) === Number(v)) || map.value === v) {\n this.setColorState(v, column.style);\n return this.defaultCellFormatter(map.text, column.style);\n }\n }\n }\n\n if (mappingType === 2 && column.style.rangeMaps) {\n for (let i = 0; i < column.style.rangeMaps.length; i++) {\n const map = column.style.rangeMaps[i];\n\n if (v === null) {\n if (map.from === 'null' && map.to === 'null') {\n return map.text;\n }\n continue;\n }\n\n if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) {\n this.setColorState(v, column.style);\n return this.defaultCellFormatter(map.text, column.style);\n }\n }\n }\n\n if (v === null || v === void 0) {\n return '-';\n }\n\n this.setColorState(v, column.style);\n return this.defaultCellFormatter(v, column.style);\n };\n }\n\n if (column.style.type === 'number') {\n const valueFormatter = getValueFormat(column.unit || column.style.unit);\n\n return (v: any) => {\n if (v === null || v === void 0) {\n return '-';\n }\n\n if (isNaN(v) || isArray(v)) {\n return this.defaultCellFormatter(v, column.style);\n }\n\n this.setColorState(v, column.style);\n return formattedValueToString(valueFormatter(v, column.style.decimals, null));\n };\n }\n\n return (value: any) => {\n return this.defaultCellFormatter(value, column.style);\n };\n }\n\n setColorState(value: unknown, style: ColumnStyle) {\n if (!style.colorMode) {\n return;\n }\n\n if (value === null || value === void 0 || isArray(value)) {\n return;\n }\n\n const numericValue = Number(value);\n if (isNaN(numericValue)) {\n return;\n }\n\n this.colorState[style.colorMode] = this.getColorForValue(numericValue, style);\n }\n\n renderRowVariables(rowIndex: number) {\n const scopedVars: ScopedVars = {};\n let cellVariable;\n const row = this.table.rows[rowIndex];\n for (let i = 0; i < row.length; i++) {\n cellVariable = `__cell_${i}`;\n scopedVars[cellVariable] = { value: row[i], text: row[i] ? row[i].toString() : '' };\n }\n return scopedVars;\n }\n\n formatColumnValue(colIndex: number, value: any) {\n const fmt = this.formatters[colIndex];\n if (fmt) {\n return fmt(value);\n }\n return value;\n }\n\n renderCell(columnIndex: number, rowIndex: number, value: any, addWidthHack = false) {\n value = this.formatColumnValue(columnIndex, value);\n\n const column = this.table.columns[columnIndex];\n const cellStyles = [];\n let cellStyle = '';\n const cellClasses = [];\n let cellClass = '';\n\n if (this.colorState.cell) {\n cellStyles.push('background-color:' + this.colorState.cell);\n cellClasses.push('table-panel-color-cell');\n this.colorState.cell = null;\n } else if (this.colorState.value) {\n cellStyles.push('color:' + this.colorState.value);\n this.colorState.value = null;\n }\n // because of the fixed table headers css only solution\n // there is an issue if header cell is wider the cell\n // this hack adds header content to cell (not visible)\n let columnHtml = '';\n if (addWidthHack) {\n columnHtml = '' + this.table.columns[columnIndex].title + '
';\n }\n\n if (value === undefined) {\n cellStyles.push('display:none');\n column.hidden = true;\n } else {\n column.hidden = false;\n }\n\n if (column.hidden === true) {\n return '';\n }\n\n if (column.style && column.style.preserveFormat) {\n cellClasses.push('table-panel-cell-pre');\n }\n\n if (column.style && column.style.align) {\n const textAlign = find(ColumnOptionsCtrl.alignTypesEnum, ['text', column.style.align]);\n if (textAlign && textAlign['value']) {\n cellStyles.push(`text-align:${textAlign['value']}`);\n }\n }\n\n if (cellStyles.length) {\n cellStyle = ' style=\"' + cellStyles.join(';') + '\"';\n }\n\n if (column.style && column.style.link) {\n // Render cell as link\n const scopedVars = this.renderRowVariables(rowIndex);\n scopedVars['__cell'] = { value: value, text: value ? value.toString() : '' };\n\n const cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars, encodeURIComponent);\n const sanitizedCellLink = textUtil.sanitizeUrl(cellLink);\n\n const cellLinkTooltip = textUtil.escapeHtml(this.templateSrv.replace(column.style.linkTooltip, scopedVars));\n const cellTarget = column.style.linkTargetBlank ? '_blank' : '';\n\n cellClasses.push('table-panel-cell-link');\n\n columnHtml += ``;\n columnHtml += `${value}`;\n columnHtml += ``;\n } else {\n columnHtml += value;\n }\n\n if (column.filterable) {\n cellClasses.push('table-panel-cell-filterable');\n columnHtml += ``;\n columnHtml += ``;\n columnHtml += ``;\n columnHtml += ``;\n columnHtml += ``;\n columnHtml += ``;\n }\n\n if (cellClasses.length) {\n cellClass = ' class=\"' + cellClasses.join(' ') + '\"';\n }\n\n columnHtml = '' + columnHtml + ' | ';\n return columnHtml;\n }\n\n render(page: number) {\n const pageSize = this.panel.pageSize || 100;\n const startPos = page * pageSize;\n const endPos = Math.min(startPos + pageSize, this.table.rows.length);\n let html = '';\n\n for (let y = startPos; y < endPos; y++) {\n const row = this.table.rows[y];\n let cellHtml = '';\n let rowStyle = '';\n const rowClasses = [];\n let rowClass = '';\n for (let i = 0; i < this.table.columns.length; i++) {\n cellHtml += this.renderCell(i, y, row[i], y === startPos);\n }\n\n if (this.colorState.row) {\n rowStyle = ' style=\"background-color:' + this.colorState.row + '\"';\n rowClasses.push('table-panel-color-row');\n this.colorState.row = null;\n }\n\n if (rowClasses.length) {\n rowClass = ' class=\"' + rowClasses.join(' ') + '\"';\n }\n\n html += '' + cellHtml + '
';\n }\n\n return html;\n }\n\n render_values() {\n const rows = [];\n const visibleColumns = this.table.columns.filter((column) => !column.hidden);\n\n for (let y = 0; y < this.table.rows.length; y++) {\n const row = this.table.rows[y];\n const newRow = [];\n for (let i = 0; i < this.table.columns.length; i++) {\n if (!this.table.columns[i].hidden) {\n newRow.push(this.formatColumnValue(i, row[i]));\n }\n }\n rows.push(newRow);\n }\n return {\n columns: visibleColumns,\n rows: rows,\n };\n }\n}\n","import { IScope, IAngularStatic } from 'angular';\nimport $ from 'jquery';\nimport { defaults } from 'lodash';\n\nimport { isTableData, PanelEvents, PanelPlugin } from '@grafana/data';\nimport { AnnotationsSrv } from 'app/angular/services/annotations_srv';\nimport config from 'app/core/config';\nimport { applyFilterFromTable } from 'app/features/variables/adhoc/actions';\nimport { MetricsPanelCtrl } from 'app/plugins/sdk';\nimport { dispatch } from 'app/store/store';\n\nimport { columnOptionsTab } from './column_options';\nimport { tablePanelEditor } from './editor';\nimport { TableRenderer } from './renderer';\nimport { transformDataToTable } from './transformers';\n\nexport class TablePanelCtrl extends MetricsPanelCtrl {\n static templateUrl = 'module.html';\n\n pageIndex: number;\n dataRaw: any;\n table: any;\n renderer: any;\n panelHasRowColorMode: boolean;\n panelHasLinks: boolean;\n\n panelDefaults = {\n targets: [{}],\n transform: 'timeseries_to_columns',\n pageSize: null,\n showHeader: true,\n styles: [\n {\n type: 'date',\n pattern: 'Time',\n alias: 'Time',\n dateFormat: 'YYYY-MM-DD HH:mm:ss',\n align: 'auto',\n },\n {\n unit: 'short',\n type: 'number',\n alias: '',\n decimals: 2,\n colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'],\n colorMode: null,\n pattern: '/.*/',\n thresholds: [],\n align: 'right',\n },\n ],\n columns: [],\n\n fontSize: '100%',\n sort: { col: 0, desc: true },\n };\n\n static $inject = ['$scope', '$injector', 'annotationsSrv', '$sanitize'];\n\n constructor(\n $scope: IScope,\n $injector: IAngularStatic['injector'],\n private annotationsSrv: AnnotationsSrv,\n private $sanitize: any\n ) {\n super($scope, $injector);\n\n this.pageIndex = 0;\n\n if (this.panel.styles === void 0) {\n this.panel.styles = this.panel.columns;\n this.panel.columns = this.panel.fields;\n delete this.panel.columns;\n delete this.panel.fields;\n }\n\n defaults(this.panel, this.panelDefaults);\n\n this.panelHasRowColorMode = Boolean(this.panel.styles.find((style: any) => style.colorMode === 'row'));\n this.panelHasLinks = Boolean(this.panel.styles.find((style: any) => style.link));\n\n this.events.on(PanelEvents.dataReceived, this.onDataReceived.bind(this));\n this.events.on(PanelEvents.dataSnapshotLoad, this.onDataReceived.bind(this));\n this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));\n }\n\n onInitEditMode() {\n this.addEditorTab('Options', tablePanelEditor, 2);\n this.addEditorTab('Column Styles', columnOptionsTab, 3);\n }\n\n migrateToPanel(type: string) {\n this.onPluginTypeChange(config.panels[type]);\n }\n\n issueQueries(datasource: any) {\n this.pageIndex = 0;\n\n if (this.panel.transform === 'annotations') {\n return this.annotationsSrv\n .getAnnotations({\n dashboard: this.dashboard,\n panel: this.panel,\n range: this.range,\n })\n .then((anno) => {\n this.loading = false;\n this.dataRaw = anno;\n this.pageIndex = 0;\n this.render();\n });\n }\n\n return super.issueQueries(datasource);\n }\n\n onDataReceived(dataList: any) {\n this.dataRaw = dataList;\n this.pageIndex = 0;\n\n // automatically correct transform mode based on data\n if (this.dataRaw && this.dataRaw.length) {\n if (isTableData(this.dataRaw[0])) {\n this.panel.transform = 'table';\n } else {\n if (this.dataRaw[0].type === 'docs') {\n this.panel.transform = 'json';\n } else {\n if (this.panel.transform === 'table' || this.panel.transform === 'json') {\n this.panel.transform = 'timeseries_to_rows';\n }\n }\n }\n }\n\n this.render();\n }\n\n render() {\n this.table = transformDataToTable(this.dataRaw, this.panel);\n this.table.sort(this.panel.sort);\n\n this.renderer = new TableRenderer(\n this.panel,\n this.table,\n this.dashboard.getTimezone(),\n this.$sanitize,\n this.templateSrv,\n config.theme2\n );\n\n return super.render(this.table);\n }\n\n toggleColumnSort(col: any, colIndex: any) {\n // remove sort flag from current column\n if (this.table.columns[this.panel.sort.col]) {\n this.table.columns[this.panel.sort.col].sort = false;\n }\n\n if (this.panel.sort.col === colIndex) {\n if (this.panel.sort.desc) {\n this.panel.sort.desc = false;\n } else {\n this.panel.sort.col = null;\n }\n } else {\n this.panel.sort.col = colIndex;\n this.panel.sort.desc = true;\n }\n this.render();\n }\n\n link(scope: IScope, elem: JQuery, attrs: any, ctrl: TablePanelCtrl) {\n let data: any;\n const panel = ctrl.panel;\n let pageCount = 0;\n\n function getTableHeight() {\n let panelHeight = ctrl.height;\n\n if (pageCount > 1) {\n panelHeight -= 26;\n }\n\n return panelHeight - 31 + 'px';\n }\n\n function appendTableRows(tbodyElem: JQuery) {\n ctrl.renderer.setTable(data);\n tbodyElem.empty();\n tbodyElem.html(ctrl.renderer.render(ctrl.pageIndex));\n }\n\n function switchPage(e: JQueryEventObject) {\n const el = $(e.currentTarget);\n ctrl.pageIndex = parseInt(el.text(), 10) - 1;\n renderPanel();\n }\n\n function appendPaginationControls(footerElem: JQuery) {\n footerElem.empty();\n\n const pageSize = panel.pageSize || 100;\n pageCount = Math.ceil(data.rows.length / pageSize);\n if (pageCount === 1) {\n return;\n }\n\n const startPage = Math.max(ctrl.pageIndex - 3, 0);\n const endPage = Math.min(pageCount, startPage + 9);\n\n const paginationList = $('');\n\n for (let i = startPage; i < endPage; i++) {\n const activeClass = i === ctrl.pageIndex ? 'active' : '';\n const pageLinkElem = $(\n '' + (i + 1) + ''\n );\n paginationList.append(pageLinkElem);\n }\n\n footerElem.append(paginationList);\n }\n\n function renderPanel() {\n const panelElem = elem.parents('.panel-content');\n const rootElem = elem.find('.table-panel-scroll');\n const tbodyElem = elem.find('tbody');\n const footerElem = elem.find('.table-panel-footer');\n\n elem.css({ 'font-size': panel.fontSize });\n panelElem.addClass('table-panel-content');\n\n appendTableRows(tbodyElem);\n appendPaginationControls(footerElem);\n\n rootElem.css({ 'max-height': getTableHeight() });\n }\n\n // hook up link tooltips\n elem.tooltip({\n selector: '[data-link-tooltip]',\n });\n\n function addFilterClicked(e: JQueryEventObject) {\n const filterData = $(e.currentTarget).data();\n const options = {\n datasource: panel.datasource,\n key: data.columns[filterData.column].text,\n value: data.rows[filterData.row][filterData.column],\n operator: filterData.operator,\n };\n\n dispatch(applyFilterFromTable(options));\n }\n\n elem.on('click', '.table-panel-page-link', switchPage);\n elem.on('click', '.table-panel-filter-link', addFilterClicked);\n\n const unbindDestroy = scope.$on('$destroy', () => {\n elem.off('click', '.table-panel-page-link');\n elem.off('click', '.table-panel-filter-link');\n unbindDestroy();\n });\n\n ctrl.events.on(PanelEvents.render, (renderData: unknown) => {\n data = renderData || data;\n if (data) {\n renderPanel();\n }\n ctrl.renderingCompleted();\n });\n }\n}\n\nexport const plugin = new PanelPlugin(null);\nplugin.angularPanelCtrl = TablePanelCtrl;\nplugin.setNoPadding();\n","import { makeClassES5Compatible } from '@grafana/data';\nimport { loadPluginCss } from '@grafana/runtime';\nimport { MetricsPanelCtrl as MetricsPanelCtrlES6 } from 'app/angular/panel/metrics_panel_ctrl';\nimport { PanelCtrl as PanelCtrlES6 } from 'app/angular/panel/panel_ctrl';\nimport { QueryCtrl as QueryCtrlES6 } from 'app/angular/panel/query_ctrl';\n\nconst PanelCtrl = makeClassES5Compatible(PanelCtrlES6);\nconst MetricsPanelCtrl = makeClassES5Compatible(MetricsPanelCtrlES6);\nconst QueryCtrl = makeClassES5Compatible(QueryCtrlES6);\n\nexport { PanelCtrl, MetricsPanelCtrl, QueryCtrl, loadPluginCss };\n"],"names":["DEFAULT_PORTS","AngularLocationWrapper","fn","replacement","self","newHash","pathname","location","parsedPath","url","search","paramValue","newQuery","key","updatedUrl","state","newUrl","MetricsPanelCtrl","$scope","$injector","data","timeInfo","legacy","v","queryRunner","err","datasource","newTimeData","panel","frame","result","PanelCtrl","plugin","event","payload","title","directiveFn","index","icon","editorTab","menu","QueryCtrl","col","column","value","newStyleRule","styles","stylesCount","indexToInsert","style","ref","copy","colorIndex","newColor","ColumnOptionsCtrl","columnOptionsTab","transformers","timeSeriesFormatFilterer","acc","series","tableDataFormatFilterer","model","filteredData","i","y","dp","points","timeKey","time","point","values","cells","evt","columnNames","text","noTableIndex","d","names","maxDocs","doc","flattened","flatten","propName","z","tableCol","transformDataToTable","TableModel","transformer","TablePanelEditorCtrl","uiSegmentSrv","columns","segments","c","plusButton","tablePanelEditor","TableRenderer","table","timeZone","sanitize","templateSrv","theme","colIndex","escapedPattern","string","regex","mappingType","map","valueFormatter","numericValue","rowIndex","scopedVars","cellVariable","row","fmt","columnIndex","addWidthHack","cellStyles","cellStyle","cellClasses","cellClass","columnHtml","textAlign","cellLink","sanitizedCellLink","cellLinkTooltip","cellTarget","page","pageSize","startPos","endPos","html","cellHtml","rowStyle","rowClasses","rowClass","rows","visibleColumns","newRow","TablePanelCtrl","annotationsSrv","$sanitize","type","config","anno","dataList","scope","elem","attrs","ctrl","pageCount","getTableHeight","panelHeight","appendTableRows","tbodyElem","switchPage","e","el","renderPanel","appendPaginationControls","footerElem","startPage","endPage","paginationList","activeClass","pageLinkElem","panelElem","rootElem","addFilterClicked","filterData","options","unbindDestroy","renderData","PanelPlugin"],"sourceRoot":""}