diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fdd7fb0..3764addb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.1.0 (unreleased) - **checklist**: use new GraphNG component, show units in graphs, update help texts +- **all**: replace Angular.js based config component with React, ensure Grafana 8.0 compatibility - **dashboards**: PCP Vector Host Overview: add pmproxy URL and hostspec variables - **dashboards**: mark all dashboards as readonly - **vector, bpftrace**: use `pcp://127.0.0.1` as default hostspec (no functional change) diff --git a/Makefile b/Makefile index 6d7adbe6..3ca5242d 100644 --- a/Makefile +++ b/Makefile @@ -96,6 +96,7 @@ test-e2e: test-e2e-start-container ## Run End-to-End tests test-e2e-ui: test-e2e-start-container ## Run End-to-End tests with a browser UI GRAFANA_URL="http://127.0.0.1:3001" HEADLESS=false node_modules/.bin/jest --config jest.config.e2e.js --runInBand + ##@ Helpers sign: ## Sign the plugin diff --git a/docs/installation.rst b/docs/installation.rst index 736eb01e..98025d3d 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -37,7 +37,7 @@ Replace X.Y.Z with the version of grafana-pcp you wish to install. .. code-block:: console - $ podman run -e GF_INSTALL_PLUGINS="https://github.com/performancecopilot/grafana-pcp/releases/download/vX.Y.Z/performancecopilot-pcp-app-X.Y.Z.zip;performancecopilot-pcp-app" -p 3000:3000 grafana/grafana + $ podman run -e GF_INSTALL_PLUGINS="https://github.com/performancecopilot/grafana-pcp/releases/download/vX.Y.Z/performancecopilot-pcp-app-X.Y.Z.zip;performancecopilot-pcp-app" -p 3000:3000 docker.io/grafana/grafana .. code-block:: console diff --git a/src/components/appconfig/config.html b/src/components/appconfig/config.html deleted file mode 100644 index 77697726..00000000 --- a/src/components/appconfig/config.html +++ /dev/null @@ -1,13 +0,0 @@ -

Performance Co-Pilot App

-This app integrates metrics from Performance Co-Pilot.

- -It includes the following datasources: - - -
- Plugin enabled. Please configure the datasources now. -
diff --git a/src/components/appconfig/config.ts b/src/components/appconfig/config.ts deleted file mode 100644 index 1d55d5b8..00000000 --- a/src/components/appconfig/config.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { PluginMeta } from '@grafana/data'; -import { getBackendSrv } from '@grafana/runtime'; -import appPluginConfig from '../../plugin.json'; -import './css/config.css'; - -export class PCPAppConfigCtrl { - static templateUrl = 'components/appconfig/config.html'; - appEditCtrl: any; - appModel?: PluginMeta; - - constructor() { - this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this)); - } - - async getPcpFolder() { - return await getBackendSrv() - .fetch({ - method: 'GET', - url: '/api/folders/performancecopilot-pcp-app', - showErrorAlert: false, - }) - .toPromise(); - } - - async createPcpFolder(): Promise { - try { - const folder = await this.getPcpFolder(); - return folder.data.id; - } catch (error) { - // folder does not exist - const folder = await getBackendSrv().post('/api/folders', { - uid: 'performancecopilot-pcp-app', - title: 'Performance Co-Pilot', - }); - return folder.id; - } - } - - async moveDashboardToFolder(dashboardUid: string, folderId: number) { - const getDashboardResponse = await getBackendSrv().get(`/api/dashboards/uid/${dashboardUid}`); - if (getDashboardResponse.meta.folderId !== folderId) { - await getBackendSrv().post('/api/dashboards/db', { - dashboard: getDashboardResponse.dashboard, - folderId, - overwrite: true, - }); - } - } - - async moveDashboardsToPcpFolder() { - const pcpFolderId = await this.createPcpFolder(); - const dashboardUids = appPluginConfig.includes - .filter(i => i.type === 'dashboard') - .map(d => d.path?.match(/\/([^/]*)\.json/)![1]); - - await Promise.all(dashboardUids.map(dashboardUid => this.moveDashboardToFolder(dashboardUid!, pcpFolderId))); - } - - async deletePcpFolderIfEmpty() { - let folder; - try { - folder = await this.getPcpFolder(); - } catch (error) { - // folder does not exist - return; - } - - const searchResponse = await getBackendSrv().get('/api/search', { folderIds: folder.data.id }); - if (searchResponse.length === 0) { - // delete folder only if empty - await getBackendSrv().delete('/api/folders/performancecopilot-pcp-app'); - } - } - - async postUpdate() { - if (this.appModel?.enabled) { - await this.moveDashboardsToPcpFolder(); - } else { - await this.deletePcpFolderIfEmpty(); - } - } -} diff --git a/src/components/appconfig/config.tsx b/src/components/appconfig/config.tsx new file mode 100644 index 00000000..72121e6d --- /dev/null +++ b/src/components/appconfig/config.tsx @@ -0,0 +1,85 @@ +import { AppPluginMeta, PluginConfigPageProps } from '@grafana/data'; +import { BackendSrv, getBackendSrv } from '@grafana/runtime'; +import { Button, Icon } from '@grafana/ui'; +import { css } from 'emotion'; +import React, { PureComponent } from 'react'; +import { AppSettings } from './types'; + +interface Props extends PluginConfigPageProps> {} + +export class AppConfig extends PureComponent { + private backendSrv: BackendSrv; + + constructor(props: Props) { + super(props); + this.backendSrv = getBackendSrv(); + this.onEnable = this.onEnable.bind(this); + this.onDisable = this.onDisable.bind(this); + } + + async updatePluginSettings(settings: { enabled: boolean; jsonData: any; pinned: boolean }) { + return this.backendSrv.post(`/api/plugins/${this.props.plugin.meta.id}/settings`, settings); + } + + async onEnable() { + await this.updatePluginSettings({ enabled: true, jsonData: {}, pinned: true }); + window.location.reload(); + } + + async onDisable() { + await this.updatePluginSettings({ enabled: false, jsonData: {}, pinned: false }); + window.location.reload(); + } + + render() { + const isEnabled = this.props.plugin.meta.enabled; + return ( + <> +

Performance Co-Pilot App

+ This app integrates metrics from Performance Co-Pilot. +
+
+ It includes the following datasources: +
    +
  • + PCP Redis for fast, scalable time series aggregation across multiple hosts +
  • +
  • + PCP Vector for live, on-host metrics analysis, with container support +
  • +
  • + PCP bpftrace for system introspection using bpftrace scripts +
  • +
+ {isEnabled && ( +
+ {' '} + Plugin enabled. Please configure the datasources now. +
+ )} +
+ {isEnabled ? ( + + ) : ( + + )} +
+ + ); + } +} diff --git a/src/components/appconfig/css/config.css b/src/components/appconfig/css/config.css deleted file mode 100644 index 3cfde78b..00000000 --- a/src/components/appconfig/css/config.css +++ /dev/null @@ -1,14 +0,0 @@ -ul.pcp { - margin-left: 2em; -} - -.pcp.pluginconfig-message { - margin-top: 1.5em; -} - -.pcp-icon-success { - color: #10a345; - font-size: 24px; - text-decoration: none; - vertical-align: sub; -} \ No newline at end of file diff --git a/src/components/appconfig/types.ts b/src/components/appconfig/types.ts new file mode 100644 index 00000000..9222e355 --- /dev/null +++ b/src/components/appconfig/types.ts @@ -0,0 +1 @@ +export interface AppSettings {} diff --git a/src/module.ts b/src/module.ts index c351345c..0e89426b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,9 +1,8 @@ import { AppPlugin } from '@grafana/data'; -import { PCPAppConfigCtrl } from './components/appconfig/config'; +import { AppConfig } from './components/appconfig/config'; +import { AppSettings } from './components/appconfig/types'; import { Search } from './components/search/Search'; -export { PCPAppConfigCtrl as ConfigCtrl }; - -class CustomApp extends AppPlugin<{}> {} - -export const plugin = new CustomApp().setRootPage(Search); +export const plugin = new AppPlugin() + .addConfigPage({ id: 'config', title: 'Config', icon: 'cog', body: AppConfig }) + .setRootPage(Search);