Browse Source
- Add package.json with scripts and dependencies for development, testing, and building. - Create Playwright configuration for end-to-end testing. - Add provisioning files for Grafana plugin setup. - Implement core application structure with routing and lazy-loaded components. - Develop pages (PageOne, PageTwo, PageThree, PageFour) with respective functionalities. - Create AppConfig component for plugin configuration with API settings. - Implement tests for App and AppConfig components. - Add utility functions for routing and test fixtures. - Include TypeScript configuration for the project. - Add logo and necessary assets for the application.master
commit
e0c7337ff5
65 changed files with 18468 additions and 0 deletions
@ -0,0 +1,36 @@
|
||||
# Node.js dependencies |
||||
node_modules/ |
||||
dist/ |
||||
build/ |
||||
.env |
||||
.env.local |
||||
|
||||
# Go build artifacts |
||||
*.exe |
||||
*.dll |
||||
*.so |
||||
*.dylib |
||||
*.test |
||||
*.out |
||||
*.log |
||||
|
||||
# Mage build output |
||||
mage_output/ |
||||
bin/ |
||||
pkg/ |
||||
|
||||
# OS files |
||||
.DS_Store |
||||
Thumbs.db |
||||
|
||||
# IDE/editor files |
||||
.vscode/ |
||||
.idea/ |
||||
*.swp |
||||
*.swo |
||||
|
||||
# Grafana plugin dev files |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
coverage/ |
||||
@ -0,0 +1,213 @@
|
||||
# Guida: Come Creare un Plugin Grafana con Frontend e Backend |
||||
|
||||
## Introduzione |
||||
|
||||
Questa guida spiega come creare un plugin Grafana completo che include sia frontend (React/TypeScript) che backend (Go), con funzionalità per salvare e recuperare testi tramite API. |
||||
|
||||
## Cosa abbiamo creato |
||||
|
||||
Un'applicazione Grafana che permette di: |
||||
- Inserire un messaggio di testo tramite un form |
||||
- Salvare il messaggio nel backend (in un file) |
||||
- Caricare e visualizzare il messaggio salvato come messaggio di benvenuto |
||||
- Gestire stati di caricamento ed errori |
||||
|
||||
## Passaggi Realizzati |
||||
|
||||
### 1. Creazione del Plugin Base |
||||
|
||||
```bash |
||||
# Navigazione nella directory di lavoro |
||||
cd /home/enne2/Sviluppo/grafana-plugin |
||||
|
||||
# Creazione del plugin usando lo strumento ufficiale |
||||
npx @grafana/create-plugin@latest |
||||
``` |
||||
|
||||
**Configurazione selezionata:** |
||||
- Tipo di plugin: **app** (applicazione) |
||||
- Backend: **Sì** (per supportare funzionalità server-side) |
||||
- Nome plugin: **fullstack-test** |
||||
- Organizzazione: **enne2 corp** |
||||
|
||||
Il comando ha generato automaticamente: |
||||
- Struttura completa del progetto |
||||
- File di configurazione (package.json, tsconfig.json, docker-compose.yaml) |
||||
- Template per frontend e backend |
||||
- Test end-to-end con Playwright |
||||
- GitHub Actions per CI/CD |
||||
|
||||
### 2. Installazione delle Dipendenze |
||||
|
||||
```bash |
||||
cd enne2corp-fullstacktest-app |
||||
npm install |
||||
``` |
||||
|
||||
### 3. Implementazione del Frontend |
||||
|
||||
#### Modifica di PageOne.tsx |
||||
|
||||
Ho modificato il file `src/pages/PageOne.tsx` per aggiungere: |
||||
|
||||
**Import necessari:** |
||||
```typescript |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Input, Button, Alert } from '@grafana/ui'; |
||||
import { getBackendSrv } from '@grafana/runtime'; |
||||
``` |
||||
|
||||
**Stato del componente:** |
||||
```typescript |
||||
const [textValue, setTextValue] = useState(''); |
||||
const [savedValue, setSavedValue] = useState(''); |
||||
const [loading, setLoading] = useState(false); |
||||
const [error, setError] = useState(''); |
||||
const [success, setSuccess] = useState(''); |
||||
``` |
||||
|
||||
**Funzioni principali:** |
||||
- `loadSavedValue()`: Carica il messaggio salvato dal backend |
||||
- `handleSaveText()`: Invia il nuovo messaggio al backend |
||||
|
||||
**UI implementata:** |
||||
- Messaggio di benvenuto (se presente un valore salvato) |
||||
- Form con input di testo e pulsante |
||||
- Gestione di stati di caricamento, errori e successo |
||||
- Styling personalizzato usando Emotion CSS |
||||
|
||||
### 4. Implementazione del Backend |
||||
|
||||
#### Modifica di resources.go |
||||
|
||||
Ho aggiunto due nuovi endpoint nel file `pkg/plugin/resources.go`: |
||||
|
||||
**1. Endpoint per salvare testo (`/save-text`):** |
||||
```go |
||||
func (a *App) handleSaveText(w http.ResponseWriter, req *http.Request) { |
||||
// Validazione metodo POST |
||||
// Decodifica JSON del messaggio |
||||
// Creazione directory di dati se necessaria |
||||
// Salvataggio in file /tmp/grafana-plugin-data/saved_message.txt |
||||
// Risposta JSON di conferma |
||||
} |
||||
``` |
||||
|
||||
**2. Endpoint per recuperare testo (`/get-text`):** |
||||
```go |
||||
func (a *App) handleGetText(w http.ResponseWriter, req *http.Request) { |
||||
// Validazione metodo GET |
||||
// Controllo esistenza file |
||||
// Lettura contenuto file |
||||
// Risposta JSON con il messaggio |
||||
} |
||||
``` |
||||
|
||||
**Registrazione dei nuovi endpoint:** |
||||
```go |
||||
func (a *App) registerRoutes(mux *http.ServeMux) { |
||||
mux.HandleFunc("/ping", a.handlePing) |
||||
mux.HandleFunc("/echo", a.handleEcho) |
||||
mux.HandleFunc("/save-text", a.handleSaveText) // NUOVO |
||||
mux.HandleFunc("/get-text", a.handleGetText) // NUOVO |
||||
} |
||||
``` |
||||
|
||||
#### Correzione di app.go |
||||
|
||||
Ho dovuto correggere la funzione `NewApp` per rimuovere il parametro `context.Context` che causava un errore di compilazione: |
||||
|
||||
```go |
||||
// PRIMA (errato) |
||||
func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error) |
||||
|
||||
// DOPO (corretto) |
||||
func NewApp(settings backend.AppInstanceSettings) (instancemgmt.Instance, error) |
||||
``` |
||||
|
||||
### 5. Installazione di Go e Mage |
||||
|
||||
```bash |
||||
# Installazione di Go |
||||
sudo apt update && sudo apt install -y golang-go |
||||
|
||||
# Installazione di Mage (build tool) |
||||
go install github.com/magefile/mage@latest |
||||
|
||||
# Aggiunta al PATH |
||||
export PATH=$PATH:$(go env GOPATH)/bin |
||||
``` |
||||
|
||||
### 6. Build del Backend |
||||
|
||||
```bash |
||||
mage -v build:linux |
||||
``` |
||||
|
||||
Questo comando ha compilato il codice Go e creato l'eseguibile `dist/gpx_fullstack_test_linux_amd64`. |
||||
|
||||
## Struttura File Principali |
||||
|
||||
``` |
||||
enne2corp-fullstacktest-app/ |
||||
├── src/ |
||||
│ ├── pages/ |
||||
│ │ └── PageOne.tsx # Frontend con form e logica |
||||
│ └── components/ |
||||
├── pkg/ |
||||
│ ├── main.go # Entry point del backend |
||||
│ └── plugin/ |
||||
│ ├── app.go # Configurazione app |
||||
│ └── resources.go # API endpoints |
||||
├── package.json # Dipendenze frontend |
||||
├── go.mod # Dipendenze backend |
||||
├── docker-compose.yaml # Ambiente di sviluppo |
||||
└── Magefile.go # Build configuration |
||||
``` |
||||
|
||||
## Funzionalità Implementate |
||||
|
||||
### Frontend (React/TypeScript) |
||||
- **Form di input**: Campo testo + pulsante per inserire messaggi |
||||
- **Caricamento automatico**: All'avvio carica il messaggio salvato |
||||
- **Messaggio di benvenuto**: Mostra il testo salvato in evidenza |
||||
- **Gestione stati**: Loading, errori, successo con feedback visuale |
||||
- **Styling**: Design coerente con il tema Grafana |
||||
|
||||
### Backend (Go) |
||||
- **API REST**: Due endpoint `/save-text` e `/get-text` |
||||
- **Persistenza**: Salvataggio in file system (`/tmp/grafana-plugin-data/`) |
||||
- **Validazione**: Controllo input e gestione errori |
||||
- **JSON**: Comunicazione via JSON tra frontend e backend |
||||
|
||||
### Comunicazione Frontend-Backend |
||||
- **Salvataggio**: `POST /api/plugins/enne2corp-fullstacktest-app/resources/save-text` |
||||
- **Recupero**: `GET /api/plugins/enne2corp-fullstacktest-app/resources/get-text` |
||||
- **Grafana Backend Service**: Uso di `getBackendSrv()` per le chiamate API |
||||
|
||||
## Prossimi Passi per Completare |
||||
|
||||
Per testare l'applicazione completa: |
||||
|
||||
1. **Build del frontend:** |
||||
```bash |
||||
npm run dev |
||||
``` |
||||
|
||||
2. **Avvio ambiente di sviluppo:** |
||||
```bash |
||||
npm run server |
||||
``` |
||||
|
||||
3. **Apertura di Grafana:** |
||||
- URL: http://localhost:3000 |
||||
- Navigare al plugin per testare le funzionalità |
||||
|
||||
## Note Tecniche |
||||
|
||||
- **Persistenza**: I dati sono salvati in `/tmp/grafana-plugin-data/saved_message.txt` |
||||
- **CORS**: Gestito automaticamente dal framework Grafana |
||||
- **Autenticazione**: Gestita dal sistema Grafana |
||||
- **Errori**: Logging sia frontend che backend per debugging |
||||
|
||||
Questa implementazione fornisce una base solida per plugin Grafana più complessi che richiedono comunicazione bidirezionale tra frontend e backend. |
||||
@ -0,0 +1,3 @@
|
||||
{ |
||||
"version": "5.26.9" |
||||
} |
||||
@ -0,0 +1,77 @@
|
||||
ARG grafana_version=latest |
||||
ARG grafana_image=grafana-enterprise |
||||
|
||||
FROM grafana/${grafana_image}:${grafana_version} |
||||
|
||||
ARG anonymous_auth_enabled=true |
||||
ARG development=false |
||||
ARG TARGETARCH |
||||
|
||||
ARG GO_VERSION=1.21.6 |
||||
ARG GO_ARCH=${TARGETARCH:-amd64} |
||||
|
||||
ENV DEV "${development}" |
||||
|
||||
# Make it as simple as possible to access the grafana instance for development purposes |
||||
# Do NOT enable these settings in a public facing / production grafana instance |
||||
ENV GF_AUTH_ANONYMOUS_ORG_ROLE "Admin" |
||||
ENV GF_AUTH_ANONYMOUS_ENABLED "${anonymous_auth_enabled}" |
||||
ENV GF_AUTH_BASIC_ENABLED "false" |
||||
# Set development mode so plugins can be loaded without the need to sign |
||||
ENV GF_DEFAULT_APP_MODE "development" |
||||
|
||||
|
||||
LABEL maintainer="Grafana Labs <hello@grafana.com>" |
||||
|
||||
ENV GF_PATHS_HOME="/usr/share/grafana" |
||||
WORKDIR $GF_PATHS_HOME |
||||
|
||||
USER root |
||||
|
||||
# Installing supervisor and inotify-tools |
||||
RUN if [ "${development}" = "true" ]; then \ |
||||
if grep -i -q alpine /etc/issue; then \ |
||||
apk add supervisor inotify-tools git; \ |
||||
elif grep -i -q ubuntu /etc/issue; then \ |
||||
DEBIAN_FRONTEND=noninteractive && \ |
||||
apt-get update && \ |
||||
apt-get install -y supervisor inotify-tools git && \ |
||||
rm -rf /var/lib/apt/lists/*; \ |
||||
else \ |
||||
echo 'ERROR: Unsupported base image' && /bin/false; \ |
||||
fi \ |
||||
fi |
||||
|
||||
COPY supervisord/supervisord.conf /etc/supervisor.d/supervisord.ini |
||||
COPY supervisord/supervisord.conf /etc/supervisor/conf.d/supervisord.conf |
||||
|
||||
|
||||
# Installing Go |
||||
RUN if [ "${development}" = "true" ]; then \ |
||||
curl -O -L https://golang.org/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \ |
||||
rm -rf /usr/local/go && \ |
||||
tar -C /usr/local -xzf go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \ |
||||
echo "export PATH=$PATH:/usr/local/go/bin:~/go/bin" >> ~/.bashrc && \ |
||||
rm -f go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \ |
||||
fi |
||||
|
||||
# Installing delve for debugging |
||||
RUN if [ "${development}" = "true" ]; then \ |
||||
/usr/local/go/bin/go install github.com/go-delve/delve/cmd/dlv@latest; \ |
||||
fi |
||||
|
||||
# Installing mage for plugin (re)building |
||||
RUN if [ "${development}" = "true" ]; then \ |
||||
git clone https://github.com/magefile/mage; \ |
||||
cd mage; \ |
||||
export PATH=$PATH:/usr/local/go/bin; \ |
||||
go run bootstrap.go; \ |
||||
fi |
||||
|
||||
# Inject livereload script into grafana index.html |
||||
RUN sed -i 's|</body>|<script src="http://localhost:35729/livereload.js"></script></body>|g' /usr/share/grafana/public/views/index.html |
||||
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh |
||||
RUN chmod +x /entrypoint.sh |
||||
ENTRYPOINT ["/entrypoint.sh"] |
||||
@ -0,0 +1,43 @@
|
||||
import type { Configuration, ExternalItemFunctionData } from 'webpack'; |
||||
|
||||
type ExternalsType = Configuration['externals']; |
||||
|
||||
export const externals: ExternalsType = [ |
||||
// Required for dynamic publicPath resolution
|
||||
{ 'amd-module': 'module' }, |
||||
'lodash', |
||||
'jquery', |
||||
'moment', |
||||
'slate', |
||||
'emotion', |
||||
'@emotion/react', |
||||
'@emotion/css', |
||||
'prismjs', |
||||
'slate-plain-serializer', |
||||
'@grafana/slate-react', |
||||
'react', |
||||
'react-dom', |
||||
'react-redux', |
||||
'redux', |
||||
'rxjs', |
||||
'i18next', |
||||
'react-router', |
||||
'd3', |
||||
'angular', |
||||
/^@grafana\/ui/i, |
||||
/^@grafana\/runtime/i, |
||||
/^@grafana\/data/i, |
||||
|
||||
// Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
|
||||
({ request }: ExternalItemFunctionData, callback: (error?: Error, result?: string) => void) => { |
||||
const prefix = 'grafana/'; |
||||
const hasPrefix = (request: string) => request.indexOf(prefix) === 0; |
||||
const stripPrefix = (request: string) => request.slice(prefix.length); |
||||
|
||||
if (request && hasPrefix(request)) { |
||||
return callback(undefined, stripPrefix(request)); |
||||
} |
||||
|
||||
callback(); |
||||
}, |
||||
]; |
||||
@ -0,0 +1,31 @@
|
||||
services: |
||||
grafana: |
||||
user: root |
||||
container_name: 'enne2corp-fullstacktest-app' |
||||
|
||||
build: |
||||
context: . |
||||
args: |
||||
grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise} |
||||
grafana_version: ${GRAFANA_VERSION:-12.2.0} |
||||
development: ${DEVELOPMENT:-false} |
||||
anonymous_auth_enabled: ${ANONYMOUS_AUTH_ENABLED:-true} |
||||
ports: |
||||
- 3000:3000/tcp |
||||
- 2345:2345/tcp # delve |
||||
security_opt: |
||||
- 'apparmor:unconfined' |
||||
- 'seccomp:unconfined' |
||||
cap_add: |
||||
- SYS_PTRACE |
||||
volumes: |
||||
- ../dist:/var/lib/grafana/plugins/enne2corp-fullstacktest-app |
||||
- ../provisioning:/etc/grafana/provisioning |
||||
- ..:/root/enne2corp-fullstacktest-app |
||||
|
||||
environment: |
||||
NODE_ENV: development |
||||
GF_LOG_FILTERS: plugin.enne2corp-fullstacktest-app:debug |
||||
GF_LOG_LEVEL: debug |
||||
GF_DATAPROXY_LOGGING: 1 |
||||
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: enne2corp-fullstacktest-app |
||||
@ -0,0 +1,18 @@
|
||||
#!/bin/sh |
||||
|
||||
if [ "${DEV}" = "false" ]; then |
||||
echo "Starting test mode" |
||||
exec /run.sh |
||||
fi |
||||
|
||||
echo "Starting development mode" |
||||
|
||||
if grep -i -q alpine /etc/issue; then |
||||
exec /usr/bin/supervisord -c /etc/supervisord.conf |
||||
elif grep -i -q ubuntu /etc/issue; then |
||||
exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf |
||||
else |
||||
echo 'ERROR: Unsupported base image' |
||||
exit 1 |
||||
fi |
||||
|
||||
@ -0,0 +1,25 @@
|
||||
// Due to the grafana/ui Icon component making fetch requests to
|
||||
// `/public/img/icon/<icon_name>.svg` we need to mock react-inlinesvg to prevent
|
||||
// the failed fetch requests from displaying errors in console.
|
||||
|
||||
import React from 'react'; |
||||
|
||||
type Callback = (...args: any[]) => void; |
||||
|
||||
export interface StorageItem { |
||||
content: string; |
||||
queue: Callback[]; |
||||
status: string; |
||||
} |
||||
|
||||
export const cacheStore: { [key: string]: StorageItem } = Object.create(null); |
||||
|
||||
const SVG_FILE_NAME_REGEX = /(.+)\/(.+)\.svg$/; |
||||
|
||||
const InlineSVG = ({ src }: { src: string }) => { |
||||
// testId will be the file name without extension (e.g. `public/img/icons/angle-double-down.svg` -> `angle-double-down`)
|
||||
const testId = src.replace(SVG_FILE_NAME_REGEX, '$2'); |
||||
return <svg xmlns="http://www.w3.org/2000/svg" data-testid={testId} viewBox="0 0 24 24" />; |
||||
}; |
||||
|
||||
export default InlineSVG; |
||||
@ -0,0 +1,47 @@
|
||||
[supervisord] |
||||
nodaemon=true |
||||
user=root |
||||
|
||||
[program:grafana] |
||||
user=root |
||||
directory=/var/lib/grafana |
||||
command=bash -c 'while [ ! -f /root/enne2corp-fullstacktest-app/dist/gpx_fullstack_test* ]; do sleep 1; done; /run.sh' |
||||
stdout_logfile=/dev/fd/1 |
||||
stdout_logfile_maxbytes=0 |
||||
redirect_stderr=true |
||||
killasgroup=true |
||||
stopasgroup=true |
||||
autostart=true |
||||
|
||||
[program:delve] |
||||
user=root |
||||
command=/bin/bash -c 'pid=""; while [ -z "$pid" ]; do pid=$(pgrep -f gpx_fullstack_test); done; /root/go/bin/dlv attach --api-version=2 --headless --continue --accept-multiclient --listen=:2345 $pid' |
||||
stdout_logfile=/dev/fd/1 |
||||
stdout_logfile_maxbytes=0 |
||||
redirect_stderr=true |
||||
killasgroup=false |
||||
stopasgroup=false |
||||
autostart=true |
||||
autorestart=true |
||||
|
||||
[program:build-watcher] |
||||
user=root |
||||
command=/bin/bash -c 'while inotifywait -e modify,create,delete -r /var/lib/grafana/plugins/enne2corp-fullstacktest-app; do echo "Change detected, restarting delve...";supervisorctl restart delve; done' |
||||
stdout_logfile=/dev/fd/1 |
||||
stdout_logfile_maxbytes=0 |
||||
redirect_stderr=true |
||||
killasgroup=true |
||||
stopasgroup=true |
||||
autostart=true |
||||
|
||||
[program:mage-watcher] |
||||
user=root |
||||
environment=PATH="/usr/local/go/bin:/root/go/bin:%(ENV_PATH)s" |
||||
directory=/root/enne2corp-fullstacktest-app |
||||
command=/bin/bash -c 'git config --global --add safe.directory /root/enne2corp-fullstacktest-app && mage -v watch' |
||||
stdout_logfile=/dev/fd/1 |
||||
stdout_logfile_maxbytes=0 |
||||
redirect_stderr=true |
||||
killasgroup=true |
||||
stopasgroup=true |
||||
autostart=true |
||||
@ -0,0 +1,37 @@
|
||||
// Image declarations
|
||||
declare module '*.gif' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
declare module '*.jpg' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
declare module '*.jpeg' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
declare module '*.png' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
declare module '*.webp' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
declare module '*.svg' { |
||||
const src: string; |
||||
export default src; |
||||
} |
||||
|
||||
// Font declarations
|
||||
declare module '*.woff'; |
||||
declare module '*.woff2'; |
||||
declare module '*.eot'; |
||||
declare module '*.ttf'; |
||||
declare module '*.otf'; |
||||
@ -0,0 +1,83 @@
|
||||
declare module 'replace-in-file-webpack-plugin' { |
||||
import { Compiler, Plugin } from 'webpack'; |
||||
|
||||
interface ReplaceRule { |
||||
search: string | RegExp; |
||||
replace: string | ((match: string) => string); |
||||
} |
||||
|
||||
interface ReplaceOption { |
||||
dir?: string; |
||||
files?: string[]; |
||||
test?: RegExp | RegExp[]; |
||||
rules: ReplaceRule[]; |
||||
} |
||||
|
||||
class ReplaceInFilePlugin extends Plugin { |
||||
constructor(options?: ReplaceOption[]); |
||||
options: ReplaceOption[]; |
||||
apply(compiler: Compiler): void; |
||||
} |
||||
|
||||
export = ReplaceInFilePlugin; |
||||
} |
||||
|
||||
declare module 'webpack-livereload-plugin' { |
||||
import { ServerOptions } from 'https'; |
||||
import { Compiler, Plugin, Stats, Compilation } from 'webpack'; |
||||
|
||||
interface Options extends Pick<ServerOptions, 'cert' | 'key' | 'pfx'> { |
||||
/** |
||||
* protocol for livereload `<script>` src attribute value |
||||
* @default protocol of the page, either `http` or `https` |
||||
*/ |
||||
protocol?: string | undefined; |
||||
/** |
||||
* The desired port for the livereload server. |
||||
* If you define port 0, an available port will be searched for, starting from 35729. |
||||
* @default 35729 |
||||
*/ |
||||
port?: number | undefined; |
||||
/** |
||||
* he desired hostname for the appended `<script>` (if present) to point to |
||||
* @default hostname of the page, like `localhost` or 10.0.2.2 |
||||
*/ |
||||
hostname?: string | undefined; |
||||
/** |
||||
* livereload `<script>` automatically to `<head>`. |
||||
* @default false |
||||
*/ |
||||
appendScriptTag?: boolean | undefined; |
||||
/** |
||||
* RegExp of files to ignore. Null value means ignore nothing. |
||||
* It is also possible to define an array and use multiple anymatch patterns |
||||
*/ |
||||
ignore?: RegExp | RegExp[] | null | undefined; |
||||
/** |
||||
* amount of milliseconds by which to delay the live reload (in case build takes longer) |
||||
* @default 0 |
||||
*/ |
||||
delay?: number | undefined; |
||||
/** |
||||
* create hash for each file source and only notify livereload if hash has changed |
||||
* @default false |
||||
*/ |
||||
useSourceHash?: boolean | undefined; |
||||
} |
||||
|
||||
class LiveReloadPlugin extends Plugin { |
||||
readonly isRunning: boolean; |
||||
constructor(options?: Options); |
||||
|
||||
apply(compiler: Compiler): void; |
||||
|
||||
start(watching: any, cb: () => void): void; |
||||
done(stats: Stats): void; |
||||
failed(): void; |
||||
autoloadJs(): string; |
||||
scriptTag(source: string): string; |
||||
applyCompilation(compilation: Compilation): void; |
||||
} |
||||
|
||||
export = LiveReloadPlugin; |
||||
} |
||||
@ -0,0 +1,33 @@
|
||||
import webpack, { type Compiler } from 'webpack'; |
||||
|
||||
const PLUGIN_NAME = 'BuildModeWebpack'; |
||||
|
||||
export class BuildModeWebpackPlugin { |
||||
apply(compiler: webpack.Compiler) { |
||||
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { |
||||
compilation.hooks.processAssets.tap( |
||||
{ |
||||
name: PLUGIN_NAME, |
||||
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, |
||||
}, |
||||
async () => { |
||||
const assets = compilation.getAssets(); |
||||
for (const asset of assets) { |
||||
if (asset.name.endsWith('plugin.json')) { |
||||
const pluginJsonString = asset.source.source().toString(); |
||||
const pluginJsonWithBuildMode = JSON.stringify( |
||||
{ |
||||
...JSON.parse(pluginJsonString), |
||||
buildMode: compilation.options.mode, |
||||
}, |
||||
null, |
||||
4 |
||||
); |
||||
compilation.updateAsset(asset.name, new webpack.sources.RawSource(pluginJsonWithBuildMode)); |
||||
} |
||||
} |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
} |
||||
@ -0,0 +1,2 @@
|
||||
export const SOURCE_DIR = 'src'; |
||||
export const DIST_DIR = 'dist'; |
||||
@ -0,0 +1,68 @@
|
||||
import fs from 'fs'; |
||||
import process from 'process'; |
||||
import os from 'os'; |
||||
import path from 'path'; |
||||
import { glob } from 'glob'; |
||||
import { SOURCE_DIR } from './constants.ts'; |
||||
|
||||
export function isWSL() { |
||||
if (process.platform !== 'linux') { |
||||
return false; |
||||
} |
||||
|
||||
if (os.release().toLowerCase().includes('microsoft')) { |
||||
return true; |
||||
} |
||||
|
||||
try { |
||||
return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft'); |
||||
} catch { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
function loadJson(path: string) { |
||||
const rawJson = fs.readFileSync(path, 'utf8'); |
||||
return JSON.parse(rawJson); |
||||
} |
||||
|
||||
export function getPackageJson() { |
||||
return loadJson(path.resolve(process.cwd(), 'package.json')); |
||||
} |
||||
|
||||
export function getPluginJson() { |
||||
return loadJson(path.resolve(process.cwd(), `${SOURCE_DIR}/plugin.json`)); |
||||
} |
||||
|
||||
export function getCPConfigVersion() { |
||||
const cprcJson = path.resolve(process.cwd(), './.config', '.cprc.json'); |
||||
return fs.existsSync(cprcJson) ? loadJson(cprcJson).version : { version: 'unknown' }; |
||||
} |
||||
|
||||
export function hasReadme() { |
||||
return fs.existsSync(path.resolve(process.cwd(), SOURCE_DIR, 'README.md')); |
||||
} |
||||
|
||||
// Support bundling nested plugins by finding all plugin.json files in src directory
|
||||
// then checking for a sibling module.[jt]sx? file.
|
||||
export async function getEntries() { |
||||
const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true }); |
||||
|
||||
const plugins = await Promise.all( |
||||
pluginsJson.map((pluginJson) => { |
||||
const folder = path.dirname(pluginJson); |
||||
return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true }); |
||||
}) |
||||
); |
||||
|
||||
return plugins.reduce<Record<string, string>>((result, modules) => { |
||||
return modules.reduce((innerResult, module) => { |
||||
const pluginPath = path.dirname(module); |
||||
const pluginName = path.relative(process.cwd(), pluginPath).replace(/src\/?/i, ''); |
||||
const entryName = pluginName === '' ? 'module' : `${pluginName}/module`; |
||||
|
||||
innerResult[entryName] = module; |
||||
return innerResult; |
||||
}, result); |
||||
}, {}); |
||||
} |
||||
@ -0,0 +1,7 @@
|
||||
{ |
||||
"features": { |
||||
"bundleGrafanaUI": false, |
||||
"useReactRouterV6": true, |
||||
"useExperimentalRspack": false |
||||
} |
||||
} |
||||
@ -0,0 +1,3 @@
|
||||
{ |
||||
"extends": "./.config/.eslintrc" |
||||
} |
||||
@ -0,0 +1,27 @@
|
||||
name: Bundle Stats |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
pull_request: |
||||
branches: |
||||
- main |
||||
push: |
||||
branches: |
||||
- main |
||||
|
||||
permissions: |
||||
contents: write |
||||
pull-requests: write |
||||
actions: read |
||||
|
||||
jobs: |
||||
compare: |
||||
runs-on: ubuntu-latest |
||||
|
||||
steps: |
||||
- name: Checkout repository |
||||
uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
|
||||
- uses: grafana/plugin-actions/bundle-size@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
@ -0,0 +1,241 @@
|
||||
name: CI |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- master |
||||
- main |
||||
pull_request: |
||||
branches: |
||||
- master |
||||
- main |
||||
|
||||
jobs: |
||||
build: |
||||
name: Build, lint and unit tests |
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
contents: read |
||||
outputs: |
||||
plugin-id: ${{ steps.metadata.outputs.plugin-id }} |
||||
plugin-version: ${{ steps.metadata.outputs.plugin-version }} |
||||
has-e2e: ${{ steps.check-for-e2e.outputs.has-e2e }} |
||||
has-backend: ${{ steps.check-for-backend.outputs.has-backend }} |
||||
env: |
||||
GRAFANA_ACCESS_POLICY_TOKEN: ${{ secrets.GRAFANA_ACCESS_POLICY_TOKEN }} |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
- name: Setup Node.js environment |
||||
uses: actions/setup-node@v4 |
||||
with: |
||||
node-version: '22' |
||||
cache: 'npm' |
||||
|
||||
- name: Install dependencies |
||||
run: npm ci |
||||
|
||||
- name: Check types |
||||
run: npm run typecheck |
||||
- name: Lint |
||||
run: npm run lint |
||||
- name: Unit tests |
||||
run: npm run test:ci |
||||
- name: Build frontend |
||||
run: npm run build |
||||
|
||||
- name: Check for backend |
||||
id: check-for-backend |
||||
run: | |
||||
if [ -f "Magefile.go" ] |
||||
then |
||||
echo "has-backend=true" >> $GITHUB_OUTPUT |
||||
fi |
||||
|
||||
- name: Setup Go environment |
||||
if: steps.check-for-backend.outputs.has-backend == 'true' |
||||
uses: actions/setup-go@v5 |
||||
with: |
||||
go-version: '1.22' |
||||
|
||||
- name: Test backend |
||||
if: steps.check-for-backend.outputs.has-backend == 'true' |
||||
uses: magefile/mage-action@6f50bbb8ea47d56e62dee92392788acbc8192d0b # v3.1.0 |
||||
with: |
||||
version: latest |
||||
args: coverage |
||||
|
||||
- name: Build backend |
||||
if: steps.check-for-backend.outputs.has-backend == 'true' |
||||
uses: magefile/mage-action@6f50bbb8ea47d56e62dee92392788acbc8192d0b # v3.1.0 |
||||
with: |
||||
version: latest |
||||
args: buildAll |
||||
|
||||
- name: Check for E2E |
||||
id: check-for-e2e |
||||
run: | |
||||
if [ -f "playwright.config.ts" ] |
||||
then |
||||
echo "has-e2e=true" >> $GITHUB_OUTPUT |
||||
fi |
||||
|
||||
- name: Sign plugin |
||||
run: npm run sign |
||||
if: ${{ env.GRAFANA_ACCESS_POLICY_TOKEN != '' }} |
||||
|
||||
- name: Get plugin metadata |
||||
id: metadata |
||||
run: | |
||||
sudo apt-get install jq |
||||
|
||||
export GRAFANA_PLUGIN_ID=$(cat dist/plugin.json | jq -r .id) |
||||
export GRAFANA_PLUGIN_VERSION=$(cat dist/plugin.json | jq -r .info.version) |
||||
export GRAFANA_PLUGIN_ARTIFACT=${GRAFANA_PLUGIN_ID}-${GRAFANA_PLUGIN_VERSION}.zip |
||||
|
||||
echo "plugin-id=${GRAFANA_PLUGIN_ID}" >> $GITHUB_OUTPUT |
||||
echo "plugin-version=${GRAFANA_PLUGIN_VERSION}" >> $GITHUB_OUTPUT |
||||
echo "archive=${GRAFANA_PLUGIN_ARTIFACT}" >> $GITHUB_OUTPUT |
||||
|
||||
- name: Package plugin |
||||
id: package-plugin |
||||
run: | |
||||
mv dist ${PLUGIN_ID} |
||||
zip ${ARCHIVE} ${PLUGIN_ID} -r |
||||
env: |
||||
ARCHIVE: ${{ steps.metadata.outputs.archive }} |
||||
PLUGIN_ID: ${{ steps.metadata.outputs.plugin-id }} |
||||
|
||||
- name: Check plugin.json |
||||
run: | |
||||
docker run --pull=always \ |
||||
-v $PWD/${ARCHIVE}:/archive.zip \ |
||||
grafana/plugin-validator-cli -analyzer=metadatavalid /archive.zip |
||||
env: |
||||
ARCHIVE: ${{ steps.metadata.outputs.archive }} |
||||
|
||||
- name: Archive Build |
||||
uses: actions/upload-artifact@v4 |
||||
with: |
||||
name: ${{ steps.metadata.outputs.plugin-id }}-${{ steps.metadata.outputs.plugin-version }} |
||||
path: ${{ steps.metadata.outputs.plugin-id }} |
||||
retention-days: 5 |
||||
|
||||
resolve-versions: |
||||
name: Resolve e2e images |
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
contents: read |
||||
timeout-minutes: 3 |
||||
needs: build |
||||
if: ${{ needs.build.outputs.has-e2e == 'true' }} |
||||
outputs: |
||||
matrix: ${{ steps.resolve-versions.outputs.matrix }} |
||||
steps: |
||||
- name: Checkout |
||||
uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
|
||||
- name: Resolve Grafana E2E versions |
||||
id: resolve-versions |
||||
uses: grafana/plugin-actions/e2e-version@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
|
||||
playwright-tests: |
||||
needs: [resolve-versions, build] |
||||
timeout-minutes: 15 |
||||
permissions: |
||||
contents: read |
||||
id-token: write |
||||
pull-requests: write |
||||
strategy: |
||||
fail-fast: false |
||||
matrix: |
||||
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}} |
||||
name: e2e test ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }} |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
|
||||
- name: Download plugin |
||||
uses: actions/download-artifact@v4 |
||||
with: |
||||
path: dist |
||||
name: ${{ needs.build.outputs.plugin-id }}-${{ needs.build.outputs.plugin-version }} |
||||
|
||||
- name: Execute permissions on binary |
||||
if: needs.build.outputs.has-backend == 'true' |
||||
run: | |
||||
chmod +x ./dist/gpx_* |
||||
|
||||
- name: Setup Node.js environment |
||||
uses: actions/setup-node@v4 |
||||
with: |
||||
node-version: '22' |
||||
cache: 'npm' |
||||
|
||||
- name: Install dev dependencies |
||||
run: npm ci |
||||
|
||||
- name: Start Grafana |
||||
run: | |
||||
docker compose pull |
||||
ANONYMOUS_AUTH_ENABLED=false DEVELOPMENT=false GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker compose up -d |
||||
|
||||
- name: Wait for grafana server |
||||
uses: grafana/plugin-actions/wait-for-grafana@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
with: |
||||
url: http://localhost:3000/login |
||||
|
||||
- name: Install Playwright Browsers |
||||
run: npm exec playwright install chromium --with-deps |
||||
|
||||
- name: Run Playwright tests |
||||
id: run-tests |
||||
run: npm run e2e |
||||
|
||||
- name: Upload e2e test summary |
||||
uses: grafana/plugin-actions/playwright-gh-pages/upload-report-artifacts@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
if: ${{ always() && !cancelled() }} |
||||
with: |
||||
upload-report: false |
||||
github-token: ${{ secrets.GITHUB_TOKEN }} |
||||
test-outcome: ${{ steps.run-tests.outcome }} |
||||
|
||||
- name: Docker logs |
||||
if: ${{ always() && steps.run-tests.outcome == 'failure' }} |
||||
run: | |
||||
docker logs enne2corp-fullstacktest-app >& grafana-server.log |
||||
|
||||
- name: Stop grafana docker |
||||
run: docker compose down |
||||
|
||||
# Uncomment this step to upload the server log to Github artifacts. Remember Github artifacts are public on the Internet if the repository is public. |
||||
# - name: Upload server log |
||||
# uses: actions/upload-artifact@v4 |
||||
# if: ${{ always() && steps.run-tests.outcome == 'failure' }} |
||||
# with: |
||||
# name: ${{ matrix.GRAFANA_IMAGE.NAME }}-v${{ matrix.GRAFANA_IMAGE.VERSION }}-${{github.run_id}}-server-log |
||||
# path: grafana-server.log |
||||
# retention-days: 5 |
||||
|
||||
publish-report: |
||||
if: ${{ always() && !cancelled() }} |
||||
permissions: |
||||
contents: write |
||||
id-token: write |
||||
pull-requests: write |
||||
needs: [playwright-tests] |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
with: |
||||
# required for playwright-gh-pages |
||||
persist-credentials: true |
||||
- name: Publish report |
||||
uses: grafana/plugin-actions/playwright-gh-pages/deploy-report-pages@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
with: |
||||
github-token: ${{ secrets.GITHUB_TOKEN }} |
||||
@ -0,0 +1,26 @@
|
||||
name: Create Plugin Update |
||||
|
||||
on: |
||||
workflow_dispatch: |
||||
schedule: |
||||
- cron: '0 0 1 * *' # run once a month on the 1st day |
||||
|
||||
# To use the default github token with the following elevated permissions make sure to check: |
||||
# **Allow GitHub Actions to create and approve pull requests** in https://github.com/ORG_NAME/REPO_NAME/settings/actions. |
||||
# Alternatively create a fine-grained personal access token for your repository with |
||||
# `contents: read and write` and `pull requests: read and write` and pass it to the action. |
||||
|
||||
permissions: |
||||
contents: write |
||||
pull-requests: write |
||||
|
||||
jobs: |
||||
release: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: grafana/plugin-actions/create-plugin-update@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
# Uncomment to use a fine-grained personal access token instead of default github token |
||||
# (For more info on how to generate the token see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) |
||||
# with: |
||||
# Make sure to save the token in your repository secrets |
||||
# token: $ |
||||
@ -0,0 +1,37 @@
|
||||
name: Latest Grafana API compatibility check |
||||
on: [pull_request] |
||||
|
||||
jobs: |
||||
compatibilitycheck: |
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
contents: read |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
|
||||
- name: Setup Node.js environment |
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 |
||||
with: |
||||
node-version: '22' |
||||
cache: 'npm' |
||||
|
||||
- name: Install dependencies |
||||
run: npm ci |
||||
|
||||
- name: Build plugin |
||||
run: npm run build |
||||
|
||||
- name: Find module.ts or module.tsx |
||||
id: find-module-ts |
||||
run: | |
||||
MODULETS="$(find ./src -type f \( -name "module.ts" -o -name "module.tsx" \))" |
||||
echo "modulets=${MODULETS}" >> $GITHUB_OUTPUT |
||||
|
||||
- name: Compatibility check |
||||
uses: grafana/plugin-actions/is-compatible@main |
||||
with: |
||||
module: ${{ steps.find-module-ts.outputs.modulets }} |
||||
comment-pr: 'no' |
||||
fail-if-incompatible: 'yes' |
||||
@ -0,0 +1,29 @@
|
||||
# This GitHub Action automates the process of building Grafana plugins. |
||||
# (For more information, see https://github.com/grafana/plugin-actions/blob/main/build-plugin/README.md) |
||||
name: Release |
||||
|
||||
on: |
||||
push: |
||||
tags: |
||||
- 'v*' # Run workflow on version tags, e.g. v1.0.0. |
||||
|
||||
permissions: read-all |
||||
|
||||
jobs: |
||||
release: |
||||
permissions: |
||||
contents: write |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v4 |
||||
with: |
||||
persist-credentials: false |
||||
|
||||
- uses: grafana/plugin-actions/build-plugin@main # zizmor: ignore[unpinned-uses] provided by grafana |
||||
# Uncomment to enable plugin signing |
||||
# (For more info on how to generate the access policy token see https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin#generate-an-access-policy-token) |
||||
#with: |
||||
# Make sure to save the token in your repository secrets |
||||
#policy_token: $ |
||||
# Usage of GRAFANA_API_KEY is deprecated, prefer `policy_token` option above |
||||
#grafana_token: $ |
||||
@ -0,0 +1,46 @@
|
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
.pnpm-debug.log* |
||||
|
||||
node_modules/ |
||||
|
||||
# yarn |
||||
.yarn/cache |
||||
.yarn/unplugged |
||||
.yarn/build-state.yml |
||||
.yarn/install-state.gz |
||||
.pnp.* |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
dist/ |
||||
artifacts/ |
||||
work/ |
||||
ci/ |
||||
|
||||
# e2e test directories |
||||
/test-results/ |
||||
/playwright-report/ |
||||
/blob-report/ |
||||
/playwright/.cache/ |
||||
/playwright/.auth/ |
||||
|
||||
# Editor |
||||
.idea |
||||
|
||||
.eslintcache |
||||
@ -0,0 +1,4 @@
|
||||
module.exports = { |
||||
// Prettier configuration provided by Grafana scaffolding
|
||||
...require('./.config/.prettierrc.js'), |
||||
}; |
||||
@ -0,0 +1,5 @@
|
||||
# Changelog |
||||
|
||||
## 1.0.0 (Unreleased) |
||||
|
||||
Initial release. |
||||
@ -0,0 +1,201 @@
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "{}" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright {yyyy} {name of copyright owner} |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
||||
@ -0,0 +1,12 @@
|
||||
//go:build mage
|
||||
// +build mage
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
// mage:import
|
||||
build "github.com/grafana/grafana-plugin-sdk-go/build" |
||||
) |
||||
|
||||
// Default configures the default target.
|
||||
var Default = build.BuildAll |
||||
@ -0,0 +1,136 @@
|
||||
# Grafana app plugin template |
||||
|
||||
This template is a starting point for building an app plugin for Grafana. |
||||
|
||||
## What are Grafana app plugins? |
||||
|
||||
App plugins can let you create a custom out-of-the-box monitoring experience by custom pages, nested data sources and panel plugins. |
||||
|
||||
## Get started |
||||
|
||||
### Backend |
||||
|
||||
1. Update [Grafana plugin SDK for Go](https://grafana.com/developers/plugin-tools/key-concepts/backend-plugins/grafana-plugin-sdk-for-go) dependency to the latest minor version: |
||||
|
||||
```bash |
||||
go get -u github.com/grafana/grafana-plugin-sdk-go |
||||
go mod tidy |
||||
``` |
||||
|
||||
2. Build plugin backend binaries for Linux, Windows and Darwin: |
||||
|
||||
```bash |
||||
mage -v |
||||
``` |
||||
|
||||
3. List all available Mage targets for additional commands: |
||||
|
||||
```bash |
||||
mage -l |
||||
``` |
||||
|
||||
### Frontend |
||||
|
||||
1. Install dependencies |
||||
|
||||
```bash |
||||
npm install |
||||
``` |
||||
|
||||
2. Build plugin in development mode and run in watch mode |
||||
|
||||
```bash |
||||
npm run dev |
||||
``` |
||||
|
||||
3. Build plugin in production mode |
||||
|
||||
```bash |
||||
npm run build |
||||
``` |
||||
|
||||
4. Run the tests (using Jest) |
||||
|
||||
```bash |
||||
# Runs the tests and watches for changes, requires git init first |
||||
npm run test |
||||
|
||||
# Exits after running all the tests |
||||
npm run test:ci |
||||
``` |
||||
|
||||
5. Spin up a Grafana instance and run the plugin inside it (using Docker) |
||||
|
||||
```bash |
||||
npm run server |
||||
``` |
||||
|
||||
6. Run the E2E tests (using Playwright) |
||||
|
||||
```bash |
||||
# Spins up a Grafana instance first that we tests against |
||||
npm run server |
||||
|
||||
# If you wish to start a certain Grafana version. If not specified will use latest by default |
||||
GRAFANA_VERSION=11.3.0 npm run server |
||||
|
||||
# Starts the tests |
||||
npm run e2e |
||||
``` |
||||
|
||||
7. Run the linter |
||||
|
||||
```bash |
||||
npm run lint |
||||
|
||||
# or |
||||
|
||||
npm run lint:fix |
||||
``` |
||||
|
||||
# Distributing your plugin |
||||
|
||||
When distributing a Grafana plugin either within the community or privately the plugin must be signed so the Grafana application can verify its authenticity. This can be done with the `@grafana/sign-plugin` package. |
||||
|
||||
_Note: It's not necessary to sign a plugin during development. The docker development environment that is scaffolded with `@grafana/create-plugin` caters for running the plugin without a signature._ |
||||
|
||||
## Initial steps |
||||
|
||||
Before signing a plugin please read the Grafana [plugin publishing and signing criteria](https://grafana.com/legal/plugins/#plugin-publishing-and-signing-criteria) documentation carefully. |
||||
|
||||
`@grafana/create-plugin` has added the necessary commands and workflows to make signing and distributing a plugin via the grafana plugins catalog as straightforward as possible. |
||||
|
||||
Before signing a plugin for the first time please consult the Grafana [plugin signature levels](https://grafana.com/legal/plugins/#what-are-the-different-classifications-of-plugins) documentation to understand the differences between the types of signature level. |
||||
|
||||
1. Create a [Grafana Cloud account](https://grafana.com/signup). |
||||
2. Make sure that the first part of the plugin ID matches the slug of your Grafana Cloud account. |
||||
- _You can find the plugin ID in the `plugin.json` file inside your plugin directory. For example, if your account slug is `acmecorp`, you need to prefix the plugin ID with `acmecorp-`._ |
||||
3. Create a Grafana Cloud API key with the `PluginPublisher` role. |
||||
4. Keep a record of this API key as it will be required for signing a plugin |
||||
|
||||
## Signing a plugin |
||||
|
||||
### Using Github actions release workflow |
||||
|
||||
If the plugin is using the github actions supplied with `@grafana/create-plugin` signing a plugin is included out of the box. The [release workflow](./.github/workflows/release.yml) can prepare everything to make submitting your plugin to Grafana as easy as possible. Before being able to sign the plugin however a secret needs adding to the Github repository. |
||||
|
||||
1. Please navigate to "settings > secrets > actions" within your repo to create secrets. |
||||
2. Click "New repository secret" |
||||
3. Name the secret "GRAFANA_API_KEY" |
||||
4. Paste your Grafana Cloud API key in the Secret field |
||||
5. Click "Add secret" |
||||
|
||||
#### Push a version tag |
||||
|
||||
To trigger the workflow we need to push a version tag to github. This can be achieved with the following steps: |
||||
|
||||
1. Run `npm version <major|minor|patch>` |
||||
2. Run `git push origin main --follow-tags` |
||||
|
||||
## Learn more |
||||
|
||||
Below you can find source code for existing app plugins and other related documentation. |
||||
|
||||
- [Basic app plugin example](https://github.com/grafana/grafana-plugin-examples/tree/master/examples/app-basic#readme) |
||||
- [`plugin.json` documentation](https://grafana.com/developers/plugin-tools/reference/plugin-jsonplugin-json) |
||||
- [Sign a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/sign-a-plugin) |
||||
@ -0,0 +1,5 @@
|
||||
services: |
||||
grafana: |
||||
extends: |
||||
file: .config/docker-compose-base.yaml |
||||
service: grafana |
||||
@ -0,0 +1,66 @@
|
||||
module github.com/enne2-corp/fullstack-test |
||||
|
||||
go 1.22 |
||||
|
||||
require github.com/grafana/grafana-plugin-sdk-go v0.156.0 |
||||
|
||||
require ( |
||||
github.com/BurntSushi/toml v1.2.1 // indirect |
||||
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect |
||||
github.com/beorn7/perks v1.0.1 // indirect |
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect |
||||
github.com/cheekybits/genny v1.0.0 // indirect |
||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect |
||||
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect |
||||
github.com/fatih/color v1.7.0 // indirect |
||||
github.com/getkin/kin-openapi v0.94.0 // indirect |
||||
github.com/ghodss/yaml v1.0.0 // indirect |
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect |
||||
github.com/go-openapi/swag v0.19.15 // indirect |
||||
github.com/golang/protobuf v1.5.2 // indirect |
||||
github.com/golang/snappy v0.0.3 // indirect |
||||
github.com/google/flatbuffers v2.0.0+incompatible // indirect |
||||
github.com/google/go-cmp v0.5.9 // indirect |
||||
github.com/google/uuid v1.3.0 // indirect |
||||
github.com/gorilla/mux v1.8.0 // indirect |
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect |
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect |
||||
github.com/hashicorp/go-hclog v0.14.1 // indirect |
||||
github.com/hashicorp/go-plugin v1.4.3 // indirect |
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect |
||||
github.com/josharian/intern v1.0.0 // indirect |
||||
github.com/json-iterator/go v1.1.12 // indirect |
||||
github.com/klauspost/compress v1.13.1 // indirect |
||||
github.com/magefile/mage v1.14.0 // indirect |
||||
github.com/mailru/easyjson v0.7.7 // indirect |
||||
github.com/mattetti/filebuffer v1.0.1 // indirect |
||||
github.com/mattn/go-colorable v0.1.4 // indirect |
||||
github.com/mattn/go-isatty v0.0.10 // indirect |
||||
github.com/mattn/go-runewidth v0.0.9 // indirect |
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect |
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect |
||||
github.com/modern-go/reflect2 v1.0.2 // indirect |
||||
github.com/oklog/run v1.0.0 // indirect |
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect |
||||
github.com/pierrec/lz4/v4 v4.1.8 // indirect |
||||
github.com/prometheus/client_golang v1.14.0 // indirect |
||||
github.com/prometheus/client_model v0.3.0 // indirect |
||||
github.com/prometheus/common v0.40.0 // indirect |
||||
github.com/prometheus/procfs v0.8.0 // indirect |
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect |
||||
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect |
||||
github.com/unknwon/com v1.0.1 // indirect |
||||
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 // indirect |
||||
github.com/urfave/cli v1.22.12 // indirect |
||||
golang.org/x/net v0.8.0 // indirect |
||||
golang.org/x/sys v0.6.0 // indirect |
||||
golang.org/x/text v0.8.0 // indirect |
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect |
||||
google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79 // indirect |
||||
google.golang.org/grpc v1.41.0 // indirect |
||||
google.golang.org/protobuf v1.28.1 // indirect |
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect |
||||
gopkg.in/yaml.v2 v2.4.0 // indirect |
||||
) |
||||
@ -0,0 +1,405 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= |
||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= |
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= |
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= |
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= |
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= |
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= |
||||
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= |
||||
github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= |
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= |
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= |
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= |
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= |
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= |
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= |
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= |
||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 h1:XCdvHbz3LhewBHN7+mQPx0sg/Hxil/1USnBmxkjHcmY= |
||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= |
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= |
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= |
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= |
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= |
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= |
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac h1:XDAn206aIqKPdF5YczuuJXSQPx+WOen0Pxbxp5Fq8Pg= |
||||
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= |
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= |
||||
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= |
||||
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= |
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= |
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= |
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= |
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= |
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= |
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= |
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= |
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= |
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= |
||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= |
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= |
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= |
||||
github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo= |
||||
github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q= |
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= |
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= |
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= |
||||
github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= |
||||
github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= |
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= |
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= |
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= |
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= |
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= |
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= |
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= |
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= |
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= |
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= |
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= |
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= |
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= |
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= |
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= |
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= |
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= |
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= |
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= |
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= |
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= |
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= |
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= |
||||
github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= |
||||
github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= |
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= |
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= |
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= |
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= |
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= |
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= |
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= |
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= |
||||
github.com/grafana/grafana-plugin-sdk-go v0.156.0 h1:LPR5gpROmdwkkvm5oGihl0mSKyWNHWIjAjzzTxk3k7M= |
||||
github.com/grafana/grafana-plugin-sdk-go v0.156.0/go.mod h1:Wj4WD+sfoUB/q5UG016IvUQBM7HBJaNEAc88b++4szs= |
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= |
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= |
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= |
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= |
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= |
||||
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= |
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= |
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= |
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= |
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= |
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= |
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= |
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= |
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= |
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= |
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= |
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= |
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= |
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= |
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= |
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= |
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= |
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= |
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
||||
github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ= |
||||
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= |
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= |
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= |
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= |
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= |
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= |
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= |
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= |
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= |
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= |
||||
github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= |
||||
github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= |
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= |
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= |
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= |
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= |
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= |
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= |
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= |
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= |
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= |
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= |
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= |
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= |
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= |
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= |
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= |
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= |
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= |
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= |
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= |
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= |
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= |
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= |
||||
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= |
||||
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= |
||||
github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4= |
||||
github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= |
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= |
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= |
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= |
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= |
||||
github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q= |
||||
github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE= |
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= |
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= |
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= |
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= |
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= |
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= |
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= |
||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= |
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= |
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= |
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= |
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= |
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= |
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= |
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= |
||||
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= |
||||
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= |
||||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= |
||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= |
||||
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3 h1:4EYQaWAatQokdji3zqZloVIW/Ke1RQjYw2zHULyrHJg= |
||||
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= |
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= |
||||
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= |
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= |
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= |
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= |
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= |
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= |
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= |
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= |
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs= |
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= |
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= |
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= |
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= |
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= |
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= |
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= |
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= |
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= |
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= |
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= |
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= |
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= |
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= |
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= |
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= |
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= |
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= |
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= |
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= |
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= |
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= |
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= |
||||
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= |
||||
gonum.org/v1/gonum v0.9.3 h1:DnoIG+QAMaF5NvxnGe/oKsgKcAc6PcUyl8q0VetfQ8s= |
||||
gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= |
||||
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= |
||||
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= |
||||
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= |
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= |
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= |
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= |
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= |
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= |
||||
google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79 h1:s1jFTXJryg4a1mew7xv03VZD8N9XjxFhk1o4Js4WvPQ= |
||||
google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= |
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= |
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= |
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= |
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= |
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= |
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= |
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= |
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= |
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= |
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= |
||||
google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= |
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= |
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= |
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= |
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= |
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= |
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= |
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= |
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= |
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= |
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= |
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= |
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= |
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= |
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= |
||||
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= |
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= |
||||
@ -0,0 +1,2 @@
|
||||
// Jest setup provided by Grafana scaffolding
|
||||
import './.config/jest-setup'; |
||||
@ -0,0 +1,8 @@
|
||||
// force timezone to UTC to allow tests to work regardless of local timezone
|
||||
// generally used by snapshots, but can affect specific tests
|
||||
process.env.TZ = 'UTC'; |
||||
|
||||
module.exports = { |
||||
// Jest configuration provided by Grafana scaffolding
|
||||
...require('./.config/jest.config'), |
||||
}; |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@
|
||||
{ |
||||
"name": "fullstack-test", |
||||
"version": "1.0.0", |
||||
"scripts": { |
||||
"build": "webpack -c ./.config/webpack/webpack.config.ts --env production", |
||||
"dev": "webpack -w -c ./.config/webpack/webpack.config.ts --env development", |
||||
"test": "jest --watch --onlyChanged", |
||||
"test:ci": "jest --passWithNoTests --maxWorkers 4", |
||||
"typecheck": "tsc --noEmit", |
||||
"lint": "eslint --cache --ignore-path ./.gitignore --ext .js,.jsx,.ts,.tsx .", |
||||
"lint:fix": "npm run lint -- --fix && prettier --write --list-different .", |
||||
"e2e": "playwright test", |
||||
"server": "docker compose up --build", |
||||
"sign": "npx --yes @grafana/sign-plugin@latest" |
||||
}, |
||||
"author": "Enne2 corp", |
||||
"license": "Apache-2.0", |
||||
"devDependencies": { |
||||
"@grafana/eslint-config": "^8.2.0", |
||||
"@grafana/plugin-e2e": "^2.1.13", |
||||
"@grafana/tsconfig": "^2.0.0", |
||||
"@playwright/test": "^1.52.0", |
||||
"@stylistic/eslint-plugin-ts": "^2.9.0", |
||||
"@swc/core": "^1.3.90", |
||||
"@swc/helpers": "^0.5.0", |
||||
"@swc/jest": "^0.2.26", |
||||
"@testing-library/jest-dom": "6.1.4", |
||||
"@testing-library/react": "14.0.0", |
||||
"@types/jest": "^29.5.0", |
||||
"@types/node": "^20.8.7", |
||||
"@types/testing-library__jest-dom": "5.14.8", |
||||
"@typescript-eslint/eslint-plugin": "^8.3.0", |
||||
"@typescript-eslint/parser": "^8.3.0", |
||||
"copy-webpack-plugin": "^11.0.0", |
||||
"css-loader": "^6.7.3", |
||||
"eslint": "^8.0.0", |
||||
"eslint-config-prettier": "^8.8.0", |
||||
"eslint-plugin-jsdoc": "^46.8.0", |
||||
"eslint-plugin-react": "^7.33.0", |
||||
"eslint-plugin-react-hooks": "^4.6.0", |
||||
"eslint-webpack-plugin": "^4.0.1", |
||||
"fork-ts-checker-webpack-plugin": "^8.0.0", |
||||
"glob": "^10.2.7", |
||||
"identity-obj-proxy": "3.0.0", |
||||
"imports-loader": "^5.0.0", |
||||
"jest": "^29.5.0", |
||||
"jest-environment-jsdom": "^29.5.0", |
||||
"prettier": "^2.8.7", |
||||
"replace-in-file-webpack-plugin": "^1.0.6", |
||||
"sass": "1.63.2", |
||||
"sass-loader": "13.3.1", |
||||
"semver": "^7.6.3", |
||||
"style-loader": "3.3.3", |
||||
"swc-loader": "^0.2.3", |
||||
"terser-webpack-plugin": "^5.3.10", |
||||
"ts-node": "^10.9.2", |
||||
"typescript": "5.5.4", |
||||
"webpack": "^5.94.0", |
||||
"webpack-cli": "^5.1.4", |
||||
"webpack-livereload-plugin": "^3.0.2", |
||||
"webpack-subresource-integrity": "^5.1.0", |
||||
"webpack-virtual-modules": "^0.6.2" |
||||
}, |
||||
"engines": { |
||||
"node": ">=22" |
||||
}, |
||||
"dependencies": { |
||||
"@emotion/css": "11.10.6", |
||||
"@grafana/data": "^12.2.0", |
||||
"@grafana/i18n": "^12.2.0", |
||||
"@grafana/runtime": "^12.2.0", |
||||
"@grafana/ui": "^12.2.0", |
||||
"@grafana/schema": "^12.2.0", |
||||
"react": "18.2.0", |
||||
"react-dom": "18.2.0", |
||||
"react-router-dom": "^6.22.0", |
||||
"rxjs": "7.8.2" |
||||
}, |
||||
"packageManager": "npm@9.2.0" |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
import type { PluginOptions } from '@grafana/plugin-e2e'; |
||||
import { defineConfig, devices } from '@playwright/test'; |
||||
import { dirname } from 'node:path'; |
||||
|
||||
const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`; |
||||
|
||||
/** |
||||
* Read environment variables from file. |
||||
* https://github.com/motdotla/dotenv
|
||||
*/ |
||||
// require('dotenv').config();
|
||||
|
||||
/** |
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/ |
||||
export default defineConfig<PluginOptions>({ |
||||
testDir: './tests', |
||||
/* Run tests in files in parallel */ |
||||
fullyParallel: true, |
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */ |
||||
forbidOnly: !!process.env.CI, |
||||
/* Retry on CI only */ |
||||
retries: process.env.CI ? 2 : 0, |
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ |
||||
reporter: 'html', |
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ |
||||
use: { |
||||
/* Base URL to use in actions like `await page.goto('/')`. */ |
||||
baseURL: process.env.GRAFANA_URL || 'http://localhost:3000', |
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ |
||||
trace: 'on-first-retry', |
||||
}, |
||||
|
||||
/* Configure projects for major browsers */ |
||||
projects: [ |
||||
// 1. Login to Grafana and store the cookie on disk for use in other tests.
|
||||
{ |
||||
name: 'auth', |
||||
testDir: pluginE2eAuth, |
||||
testMatch: [/.*\.js/], |
||||
}, |
||||
// 2. Run tests in Google Chrome. Every test will start authenticated as admin user.
|
||||
{ |
||||
name: 'chromium', |
||||
use: { |
||||
...devices['Desktop Chrome'], |
||||
storageState: 'playwright/.auth/admin.json', |
||||
}, |
||||
dependencies: ['auth'], |
||||
}, |
||||
], |
||||
}); |
||||
@ -0,0 +1,4 @@
|
||||
For more information see our documentation: |
||||
|
||||
- [Provision Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/) |
||||
- [Provision a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/provide-test-environment) |
||||
@ -0,0 +1,11 @@
|
||||
apiVersion: 1 |
||||
|
||||
apps: |
||||
- type: 'enne2corp-fullstacktest-app' |
||||
org_id: 1 |
||||
org_name: 'enne2 corp' |
||||
disabled: false |
||||
jsonData: |
||||
apiUrl: http://default-url.com |
||||
secureJsonData: |
||||
apiKey: secret-key |
||||
@ -0,0 +1,50 @@
|
||||
<!-- This README file is going to be the one displayed on the Grafana.com website for your plugin. Uncomment and replace the content here before publishing. |
||||
|
||||
Remove any remaining comments before publishing as these may be displayed on Grafana.com --> |
||||
|
||||
# Fullstack-Test |
||||
|
||||
<!-- To help maximize the impact of your README and improve usability for users, we propose the following loose structure: |
||||
|
||||
**BEFORE YOU BEGIN** |
||||
- Ensure all links are absolute URLs so that they will work when the README is displayed within Grafana and Grafana.com |
||||
- Be inspired ✨ |
||||
- [grafana-polystat-panel](https://github.com/grafana/grafana-polystat-panel) |
||||
- [volkovlabs-variable-panel](https://github.com/volkovlabs/volkovlabs-variable-panel) |
||||
|
||||
**ADD SOME BADGES** |
||||
|
||||
Badges convey useful information at a glance for users whether in the Catalog or viewing the source code. You can use the generator on [Shields.io](https://shields.io/badges/dynamic-json-badge) together with the Grafana.com API |
||||
to create dynamic badges that update automatically when you publish a new version to the marketplace. |
||||
|
||||
- For the URL parameter use `https://grafana.com/api/plugins/your-plugin-id`. |
||||
- Example queries: |
||||
- Downloads: `$.downloads` |
||||
- Catalog Version: `$.version` |
||||
- Grafana Dependency: `$.grafanaDependency` |
||||
- Signature Type: `$.versionSignatureType` |
||||
- Optionally, for the logo parameter use `grafana`. |
||||
|
||||
Full example:  |
||||
|
||||
Consider other [badges](https://shields.io/badges) as you feel appropriate for your project. |
||||
|
||||
## Overview / Introduction |
||||
Provide one or more paragraphs as an introduction to your plugin to help users understand why they should use it. |
||||
|
||||
Consider including screenshots: |
||||
- in [plugin.json](https://grafana.com/developers/plugin-tools/reference/plugin-json#info) include them as relative links. |
||||
- in the README ensure they are absolute URLs. |
||||
|
||||
## Requirements |
||||
List any requirements or dependencies they may need to run the plugin. |
||||
|
||||
## Getting Started |
||||
Provide a quick start on how to configure and use the plugin. |
||||
|
||||
## Documentation |
||||
If your project has dedicated documentation available for users, provide links here. For help in following Grafana's style recommendations for technical documentation, refer to our [Writer's Toolkit](https://grafana.com/docs/writers-toolkit/). |
||||
|
||||
## Contributing |
||||
Do you want folks to contribute to the plugin or provide feedback through specific means? If so, tell them how! |
||||
--> |
||||
@ -0,0 +1,38 @@
|
||||
import React from 'react'; |
||||
import { MemoryRouter } from 'react-router-dom'; |
||||
import { AppRootProps, PluginType } from '@grafana/data'; |
||||
import { render, waitFor } from '@testing-library/react'; |
||||
import App from './App'; |
||||
|
||||
describe('Components/App', () => { |
||||
let props: AppRootProps; |
||||
|
||||
beforeEach(() => { |
||||
jest.resetAllMocks(); |
||||
|
||||
props = { |
||||
basename: 'a/sample-app', |
||||
meta: { |
||||
id: 'sample-app', |
||||
name: 'Sample App', |
||||
type: PluginType.app, |
||||
enabled: true, |
||||
jsonData: {}, |
||||
}, |
||||
query: {}, |
||||
path: '', |
||||
onNavChanged: jest.fn(), |
||||
} as unknown as AppRootProps; |
||||
}); |
||||
|
||||
test('renders without an error"', async () => { |
||||
const { queryByText } = render( |
||||
<MemoryRouter> |
||||
<App {...props} /> |
||||
</MemoryRouter> |
||||
); |
||||
|
||||
// Application is lazy loaded, so we need to wait for the component and routes to be rendered
|
||||
await waitFor(() => expect(queryByText(/this is page one./i)).toBeInTheDocument(), { timeout: 2000 }); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,25 @@
|
||||
import React from 'react'; |
||||
import { Route, Routes } from 'react-router-dom'; |
||||
import { AppRootProps } from '@grafana/data'; |
||||
import { ROUTES } from '../../constants'; |
||||
const PageOne = React.lazy(() => import('../../pages/PageOne')); |
||||
const PageTwo = React.lazy(() => import('../../pages/PageTwo')); |
||||
const PageThree = React.lazy(() => import('../../pages/PageThree')); |
||||
const PageFour = React.lazy(() => import('../../pages/PageFour')); |
||||
|
||||
function App(props: AppRootProps) { |
||||
return ( |
||||
<Routes> |
||||
<Route path={ROUTES.Two} element={<PageTwo />} /> |
||||
<Route path={`${ROUTES.Three}/:id?`} element={<PageThree />} /> |
||||
|
||||
{/* Full-width page (this page will have no side navigation) */} |
||||
<Route path={ROUTES.Four} element={<PageFour />} /> |
||||
|
||||
{/* Default page */} |
||||
<Route path="*" element={<PageOne />} /> |
||||
</Routes> |
||||
); |
||||
} |
||||
|
||||
export default App; |
||||
@ -0,0 +1,38 @@
|
||||
import React from 'react'; |
||||
import { render, screen } from '@testing-library/react'; |
||||
import { PluginType } from '@grafana/data'; |
||||
import AppConfig, { AppConfigProps } from './AppConfig'; |
||||
import { testIds } from 'components/testIds'; |
||||
|
||||
describe('Components/AppConfig', () => { |
||||
let props: AppConfigProps; |
||||
|
||||
beforeEach(() => { |
||||
jest.resetAllMocks(); |
||||
|
||||
props = { |
||||
plugin: { |
||||
meta: { |
||||
id: 'sample-app', |
||||
name: 'Sample App', |
||||
type: PluginType.app, |
||||
enabled: true, |
||||
jsonData: {}, |
||||
}, |
||||
}, |
||||
query: {}, |
||||
} as unknown as AppConfigProps; |
||||
}); |
||||
|
||||
test('renders the "API Settings" fieldset with API key, API url inputs and button', () => { |
||||
const plugin = { meta: { ...props.plugin.meta, enabled: false } }; |
||||
|
||||
// @ts-ignore - We don't need to provide `addConfigPage()` and `setChannelSupport()` for these tests
|
||||
render(<AppConfig plugin={plugin} query={props.query} />); |
||||
|
||||
expect(screen.queryByRole('group', { name: /api settings/i })).toBeInTheDocument(); |
||||
expect(screen.queryByTestId(testIds.appConfig.apiKey)).toBeInTheDocument(); |
||||
expect(screen.queryByTestId(testIds.appConfig.apiUrl)).toBeInTheDocument(); |
||||
expect(screen.queryByRole('button', { name: /save api settings/i })).toBeInTheDocument(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,140 @@
|
||||
import React, { ChangeEvent, useState } from 'react'; |
||||
import { lastValueFrom } from 'rxjs'; |
||||
import { css } from '@emotion/css'; |
||||
import { AppPluginMeta, GrafanaTheme2, PluginConfigPageProps, PluginMeta } from '@grafana/data'; |
||||
import { getBackendSrv } from '@grafana/runtime'; |
||||
import { Button, Field, FieldSet, Input, SecretInput, useStyles2 } from '@grafana/ui'; |
||||
import { testIds } from '../testIds'; |
||||
|
||||
type AppPluginSettings = { |
||||
apiUrl?: string; |
||||
}; |
||||
|
||||
type State = { |
||||
// The URL to reach our custom API.
|
||||
apiUrl: string; |
||||
// Tells us if the API key secret is set.
|
||||
isApiKeySet: boolean; |
||||
// A secret key for our custom API.
|
||||
apiKey: string; |
||||
}; |
||||
|
||||
export interface AppConfigProps extends PluginConfigPageProps<AppPluginMeta<AppPluginSettings>> {} |
||||
|
||||
const AppConfig = ({ plugin }: AppConfigProps) => { |
||||
const s = useStyles2(getStyles); |
||||
const { enabled, pinned, jsonData, secureJsonFields } = plugin.meta; |
||||
const [state, setState] = useState<State>({ |
||||
apiUrl: jsonData?.apiUrl || '', |
||||
apiKey: '', |
||||
isApiKeySet: Boolean(secureJsonFields?.apiKey), |
||||
}); |
||||
|
||||
const isSubmitDisabled = Boolean(!state.apiUrl || (!state.isApiKeySet && !state.apiKey)); |
||||
|
||||
const onResetApiKey = () => |
||||
setState({ |
||||
...state, |
||||
apiKey: '', |
||||
isApiKeySet: false, |
||||
}); |
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => { |
||||
setState({ |
||||
...state, |
||||
[event.target.name]: event.target.value.trim(), |
||||
}); |
||||
}; |
||||
|
||||
const onSubmit = () => { |
||||
if (isSubmitDisabled) { |
||||
return; |
||||
} |
||||
|
||||
updatePluginAndReload(plugin.meta.id, { |
||||
enabled, |
||||
pinned, |
||||
jsonData: { |
||||
apiUrl: state.apiUrl, |
||||
}, |
||||
// This cannot be queried later by the frontend.
|
||||
// We don't want to override it in case it was set previously and left untouched now.
|
||||
secureJsonData: state.isApiKeySet |
||||
? undefined |
||||
: { |
||||
apiKey: state.apiKey, |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
return ( |
||||
<form onSubmit={onSubmit}> |
||||
<FieldSet label="API Settings"> |
||||
<Field label="API Key" description="A secret key for authenticating to our custom API"> |
||||
<SecretInput |
||||
width={60} |
||||
id="config-api-key" |
||||
data-testid={testIds.appConfig.apiKey} |
||||
name="apiKey" |
||||
value={state.apiKey} |
||||
isConfigured={state.isApiKeySet} |
||||
placeholder={'Your secret API key'} |
||||
onChange={onChange} |
||||
onReset={onResetApiKey} |
||||
/> |
||||
</Field> |
||||
|
||||
<Field label="API Url" description="" className={s.marginTop}> |
||||
<Input |
||||
width={60} |
||||
name="apiUrl" |
||||
id="config-api-url" |
||||
data-testid={testIds.appConfig.apiUrl} |
||||
value={state.apiUrl} |
||||
placeholder={`E.g.: http://mywebsite.com/api/v1`} |
||||
onChange={onChange} |
||||
/> |
||||
</Field> |
||||
|
||||
<div className={s.marginTop}> |
||||
<Button type="submit" data-testid={testIds.appConfig.submit} disabled={isSubmitDisabled}> |
||||
Save API settings |
||||
</Button> |
||||
</div> |
||||
</FieldSet> |
||||
</form> |
||||
); |
||||
}; |
||||
|
||||
export default AppConfig; |
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({ |
||||
colorWeak: css` |
||||
color: ${theme.colors.text.secondary}; |
||||
`,
|
||||
marginTop: css` |
||||
margin-top: ${theme.spacing(3)}; |
||||
`,
|
||||
}); |
||||
|
||||
const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta<AppPluginSettings>>) => { |
||||
try { |
||||
await updatePlugin(pluginId, data); |
||||
|
||||
// Reloading the page as the changes made here wouldn't be propagated to the actual plugin otherwise.
|
||||
// This is not ideal, however unfortunately currently there is no supported way for updating the plugin state.
|
||||
window.location.reload(); |
||||
} catch (e) { |
||||
console.error('Error while updating the plugin', e); |
||||
} |
||||
}; |
||||
|
||||
const updatePlugin = async (pluginId: string, data: Partial<PluginMeta>) => { |
||||
const response = await getBackendSrv().fetch({ |
||||
url: `/api/plugins/${pluginId}/settings`, |
||||
method: 'POST', |
||||
data, |
||||
}); |
||||
|
||||
return lastValueFrom(response); |
||||
}; |
||||
@ -0,0 +1,21 @@
|
||||
export const testIds = { |
||||
appConfig: { |
||||
apiKey: 'data-testid ac-api-key', |
||||
apiUrl: 'data-testid ac-api-url', |
||||
submit: 'data-testid ac-submit-form', |
||||
}, |
||||
pageOne: { |
||||
container: 'data-testid pg-one-container', |
||||
navigateToFour: 'data-testid navigate-to-four', |
||||
}, |
||||
pageTwo: { |
||||
container: 'data-testid pg-two-container', |
||||
}, |
||||
pageThree: { |
||||
container: 'data-testid pg-three-container', |
||||
}, |
||||
pageFour: { |
||||
container: 'data-testid pg-four-container', |
||||
navigateBack: 'data-testid navigate-back', |
||||
}, |
||||
}; |
||||
@ -0,0 +1,10 @@
|
||||
import pluginJson from './plugin.json'; |
||||
|
||||
export const PLUGIN_BASE_URL = `/a/${pluginJson.id}`; |
||||
|
||||
export enum ROUTES { |
||||
One = 'one', |
||||
Two = 'two', |
||||
Three = 'three', |
||||
Four = 'four', |
||||
} |
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,26 @@
|
||||
import React, { Suspense, lazy } from 'react'; |
||||
import { AppPlugin, type AppRootProps } from '@grafana/data'; |
||||
import { LoadingPlaceholder } from '@grafana/ui'; |
||||
import type { AppConfigProps } from './components/AppConfig/AppConfig'; |
||||
|
||||
const LazyApp = lazy(() => import('./components/App/App')); |
||||
const LazyAppConfig = lazy(() => import('./components/AppConfig/AppConfig')); |
||||
|
||||
const App = (props: AppRootProps) => ( |
||||
<Suspense fallback={<LoadingPlaceholder text="" />}> |
||||
<LazyApp {...props} /> |
||||
</Suspense> |
||||
); |
||||
|
||||
const AppConfig = (props: AppConfigProps) => ( |
||||
<Suspense fallback={<LoadingPlaceholder text="" />}> |
||||
<LazyAppConfig {...props} /> |
||||
</Suspense> |
||||
); |
||||
|
||||
export const plugin = new AppPlugin<{}>().setRootPage(App).addConfigPage({ |
||||
title: 'Configuration', |
||||
icon: 'cog', |
||||
body: AppConfig, |
||||
id: 'configuration', |
||||
}); |
||||
@ -0,0 +1,44 @@
|
||||
import React from 'react'; |
||||
import { css } from '@emotion/css'; |
||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; |
||||
import { LinkButton, useStyles2 } from '@grafana/ui'; |
||||
import { ROUTES } from '../constants'; |
||||
import { prefixRoute } from '../utils/utils.routing'; |
||||
import { testIds } from '../components/testIds'; |
||||
import { PluginPage } from '@grafana/runtime'; |
||||
|
||||
function PageFour() { |
||||
const s = useStyles2(getStyles); |
||||
|
||||
return ( |
||||
<PluginPage layout={PageLayoutType.Canvas}> |
||||
<div className={s.page} data-testid={testIds.pageFour.container}> |
||||
<div className={s.container}> |
||||
<LinkButton data-testid={testIds.pageFour.navigateBack} icon="arrow-left" href={prefixRoute(ROUTES.One)}> |
||||
Back |
||||
</LinkButton> |
||||
<div className={s.content}>This is a full-width page without a navigation bar.</div> |
||||
</div> |
||||
</div> |
||||
</PluginPage> |
||||
); |
||||
} |
||||
|
||||
export default PageFour; |
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({ |
||||
page: css` |
||||
padding: ${theme.spacing(3)}; |
||||
background-color: ${theme.colors.background.secondary}; |
||||
display: flex; |
||||
justify-content: center; |
||||
`,
|
||||
container: css` |
||||
width: 900px; |
||||
max-width: 100%; |
||||
min-height: 500px; |
||||
`,
|
||||
content: css` |
||||
margin-top: ${theme.spacing(6)}; |
||||
`,
|
||||
}); |
||||
@ -0,0 +1,165 @@
|
||||
import React, { useState, useEffect } from 'react'; |
||||
import { css } from '@emotion/css'; |
||||
import { GrafanaTheme2 } from '@grafana/data'; |
||||
import { LinkButton, useStyles2, Input, Button, Alert } from '@grafana/ui'; |
||||
import { prefixRoute } from '../utils/utils.routing'; |
||||
import { ROUTES } from '../constants'; |
||||
import { testIds } from '../components/testIds'; |
||||
import { PluginPage, getBackendSrv } from '@grafana/runtime'; |
||||
|
||||
function PageOne() { |
||||
const s = useStyles2(getStyles); |
||||
const [textValue, setTextValue] = useState(''); |
||||
const [savedValue, setSavedValue] = useState(''); |
||||
const [loading, setLoading] = useState(false); |
||||
const [error, setError] = useState(''); |
||||
const [success, setSuccess] = useState(''); |
||||
|
||||
// Load saved value when component mounts
|
||||
useEffect(() => { |
||||
loadSavedValue(); |
||||
}, []); |
||||
|
||||
const loadSavedValue = async () => { |
||||
try { |
||||
const response = await getBackendSrv().get('/api/plugins/enne2corp-fullstacktest-app/resources/get-text'); |
||||
setSavedValue(response.message || ''); |
||||
} catch (err) { |
||||
console.log('No saved value found or error loading:', err); |
||||
setSavedValue(''); |
||||
} |
||||
}; |
||||
|
||||
const handleSaveText = async () => { |
||||
if (!textValue.trim()) { |
||||
setError('Please enter some text'); |
||||
return; |
||||
} |
||||
|
||||
setLoading(true); |
||||
setError(''); |
||||
setSuccess(''); |
||||
|
||||
try { |
||||
await getBackendSrv().post('/api/plugins/enne2corp-fullstacktest-app/resources/save-text', { |
||||
message: textValue, |
||||
}); |
||||
|
||||
setSuccess('Text saved successfully!'); |
||||
setSavedValue(textValue); |
||||
setTextValue(''); |
||||
|
||||
// Clear success message after 3 seconds
|
||||
setTimeout(() => setSuccess(''), 3000); |
||||
} catch (err) { |
||||
setError('Failed to save text. Please try again.'); |
||||
console.error('Error saving text:', err); |
||||
} finally { |
||||
setLoading(false); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<PluginPage> |
||||
<div data-testid={testIds.pageOne.container}> |
||||
<h2>Welcome Message App</h2> |
||||
|
||||
{savedValue && ( |
||||
<div className={s.welcomeMessage}> |
||||
<h3>Welcome! Your saved message is:</h3> |
||||
<p className={s.savedText}>{savedValue}</p> |
||||
</div> |
||||
)} |
||||
|
||||
{!savedValue && ( |
||||
<div className={s.noMessage}> |
||||
<p>No message saved yet. Enter a message below to get started!</p> |
||||
</div> |
||||
)} |
||||
|
||||
<div className={s.form}> |
||||
<h4>Save a new message:</h4> |
||||
<Input |
||||
placeholder="Enter your message here..." |
||||
value={textValue} |
||||
onChange={(e) => setTextValue(e.currentTarget.value)} |
||||
disabled={loading} |
||||
className={s.input} |
||||
/> |
||||
|
||||
<Button |
||||
onClick={handleSaveText} |
||||
disabled={loading || !textValue.trim()} |
||||
className={s.button} |
||||
> |
||||
{loading ? 'Saving...' : 'Save Message'} |
||||
</Button> |
||||
</div> |
||||
|
||||
{error && ( |
||||
<Alert title="Error" severity="error" className={s.alert}> |
||||
{error} |
||||
</Alert> |
||||
)} |
||||
|
||||
{success && ( |
||||
<Alert title="Success" severity="success" className={s.alert}> |
||||
{success} |
||||
</Alert> |
||||
)} |
||||
|
||||
<div className={s.marginTop}> |
||||
<LinkButton data-testid={testIds.pageOne.navigateToFour} href={prefixRoute(ROUTES.Four)}> |
||||
Full-width page example |
||||
</LinkButton> |
||||
</div> |
||||
</div> |
||||
</PluginPage> |
||||
); |
||||
} |
||||
|
||||
export default PageOne; |
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({ |
||||
marginTop: css` |
||||
margin-top: ${theme.spacing(2)}; |
||||
`,
|
||||
welcomeMessage: css` |
||||
background: ${theme.colors.background.secondary}; |
||||
padding: ${theme.spacing(2)}; |
||||
border-radius: ${theme.shape.borderRadius()}; |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
border-left: 4px solid ${theme.colors.primary.main}; |
||||
`,
|
||||
savedText: css` |
||||
font-size: ${theme.typography.h4.fontSize}; |
||||
font-weight: ${theme.typography.fontWeightMedium}; |
||||
color: ${theme.colors.primary.text}; |
||||
margin: 0; |
||||
`,
|
||||
noMessage: css` |
||||
background: ${theme.colors.warning.transparent}; |
||||
padding: ${theme.spacing(2)}; |
||||
border-radius: ${theme.shape.borderRadius()}; |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
border-left: 4px solid ${theme.colors.warning.main}; |
||||
`,
|
||||
form: css` |
||||
background: ${theme.colors.background.secondary}; |
||||
padding: ${theme.spacing(2)}; |
||||
border-radius: ${theme.shape.borderRadius()}; |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
`,
|
||||
input: css` |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
width: 100%; |
||||
max-width: 400px; |
||||
`,
|
||||
button: css` |
||||
margin-top: ${theme.spacing(1)}; |
||||
`,
|
||||
alert: css` |
||||
margin-top: ${theme.spacing(2)}; |
||||
margin-bottom: ${theme.spacing(2)}; |
||||
`,
|
||||
}); |
||||
@ -0,0 +1,49 @@
|
||||
import React from 'react'; |
||||
import { css } from '@emotion/css'; |
||||
import { useParams, Link } from 'react-router-dom'; |
||||
import { GrafanaTheme2 } from '@grafana/data'; |
||||
import { useStyles2 } from '@grafana/ui'; |
||||
import { prefixRoute } from '../utils/utils.routing'; |
||||
import { ROUTES } from '../constants'; |
||||
import { testIds } from '../components/testIds'; |
||||
import { PluginPage } from '@grafana/runtime'; |
||||
|
||||
function PageThree() { |
||||
const s = useStyles2(getStyles); |
||||
const { id } = useParams<{ id: string }>(); |
||||
|
||||
return ( |
||||
<PluginPage> |
||||
<div data-testid={testIds.pageThree.container}> |
||||
This is page three. |
||||
<br /> |
||||
<br /> |
||||
{/* The ID parameter is set */} |
||||
{id && ( |
||||
<> |
||||
<strong>ID:</strong> {id} |
||||
</> |
||||
)} |
||||
{/* No ID parameter */} |
||||
{!id && ( |
||||
<> |
||||
<strong>No id parameter is set in the URL.</strong> <br /> |
||||
Try the following link: <br /> |
||||
<Link className={s.link} to={prefixRoute(`${ROUTES.Three}/123456789`)}> |
||||
{prefixRoute(`${ROUTES.Three}/123456789`)} |
||||
</Link> |
||||
</> |
||||
)} |
||||
</div> |
||||
</PluginPage> |
||||
); |
||||
} |
||||
|
||||
export default PageThree; |
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({ |
||||
link: css` |
||||
color: ${theme.colors.text.link}; |
||||
text-decoration: underline; |
||||
`,
|
||||
}); |
||||
@ -0,0 +1,15 @@
|
||||
import React from 'react'; |
||||
import { testIds } from '../components/testIds'; |
||||
import { PluginPage } from '@grafana/runtime'; |
||||
|
||||
function PageTwo() { |
||||
return ( |
||||
<PluginPage> |
||||
<div data-testid={testIds.pageTwo.container}> |
||||
<p>This is page two.</p> |
||||
</div> |
||||
</PluginPage> |
||||
); |
||||
} |
||||
|
||||
export default PageTwo; |
||||
@ -0,0 +1,64 @@
|
||||
{ |
||||
"$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json", |
||||
"type": "app", |
||||
"name": "Fullstack-Test", |
||||
"id": "enne2corp-fullstacktest-app", |
||||
"backend": true, |
||||
"executable": "gpx_fullstack_test", |
||||
"info": { |
||||
"keywords": ["app"], |
||||
"description": "", |
||||
"author": { |
||||
"name": "Enne2 corp" |
||||
}, |
||||
"logos": { |
||||
"small": "img/logo.svg", |
||||
"large": "img/logo.svg" |
||||
}, |
||||
"screenshots": [], |
||||
"version": "%VERSION%", |
||||
"updated": "%TODAY%" |
||||
}, |
||||
"includes": [ |
||||
{ |
||||
"type": "page", |
||||
"name": "Page One", |
||||
"path": "/a/%PLUGIN_ID%/one", |
||||
"addToNav": true, |
||||
"defaultNav": true |
||||
}, |
||||
{ |
||||
"type": "page", |
||||
"name": "Page Two", |
||||
"path": "/a/%PLUGIN_ID%/two", |
||||
"addToNav": true, |
||||
"defaultNav": false |
||||
}, |
||||
{ |
||||
"type": "page", |
||||
"name": "Page Three", |
||||
"path": "/a/%PLUGIN_ID%/three", |
||||
"addToNav": true, |
||||
"defaultNav": false |
||||
}, |
||||
{ |
||||
"type": "page", |
||||
"name": "Page Four", |
||||
"path": "/a/%PLUGIN_ID%/four", |
||||
"addToNav": true, |
||||
"defaultNav": false |
||||
}, |
||||
{ |
||||
"type": "page", |
||||
"icon": "cog", |
||||
"name": "Configuration", |
||||
"path": "/plugins/%PLUGIN_ID%", |
||||
"role": "Admin", |
||||
"addToNav": true |
||||
} |
||||
], |
||||
"dependencies": { |
||||
"grafanaDependency": ">=10.4.0", |
||||
"plugins": [] |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
|
||||
import { PLUGIN_BASE_URL } from '../constants'; |
||||
|
||||
// Prefixes the route with the base URL of the plugin
|
||||
export function prefixRoute(route: string): string { |
||||
return `${PLUGIN_BASE_URL}/${route}`; |
||||
} |
||||
@ -0,0 +1,19 @@
|
||||
import { test, expect } from './fixtures'; |
||||
|
||||
test('should be possible to save app configuration', async ({ appConfigPage, page }) => { |
||||
const saveButton = page.getByRole('button', { name: /Save API settings/i }); |
||||
|
||||
// reset the configured secret
|
||||
await page.getByRole('button', { name: /reset/i }).click(); |
||||
|
||||
// enter some valid values
|
||||
await page.getByRole('textbox', { name: 'API Key' }).fill('secret-api-key'); |
||||
await page.getByRole('textbox', { name: 'API Url' }).clear(); |
||||
await page.getByRole('textbox', { name: 'API Url' }).fill('http://www.my-awsome-grafana-app.com/api'); |
||||
|
||||
// listen for the server response on the saved form
|
||||
const saveResponse = appConfigPage.waitForSettingsResponse(); |
||||
|
||||
await saveButton.click(); |
||||
await expect(saveResponse).toBeOK(); |
||||
}); |
||||
@ -0,0 +1,31 @@
|
||||
import { test, expect } from './fixtures'; |
||||
import { ROUTES } from '../src/constants'; |
||||
|
||||
test.describe('navigating app', () => { |
||||
test('page one should render successfully', async ({ gotoPage, page }) => { |
||||
await gotoPage(`/${ROUTES.One}`); |
||||
await expect(page.getByText('This is page one.')).toBeVisible(); |
||||
}); |
||||
|
||||
test('page two should render successfully', async ({ gotoPage, page }) => { |
||||
await gotoPage(`/${ROUTES.Two}`); |
||||
await expect(page.getByText('This is page two.')).toBeVisible(); |
||||
}); |
||||
|
||||
test('page three should support an id parameter', async ({ gotoPage, page }) => { |
||||
await gotoPage(`/${ROUTES.Three}/123456`); |
||||
await expect(page.getByText('ID: 123456')).toBeVisible(); |
||||
}); |
||||
|
||||
test('page three should render sucessfully', async ({ gotoPage, page }) => { |
||||
// wait for page to successfully render
|
||||
await gotoPage(`/${ROUTES.One}`); |
||||
await expect(page.getByText('This is page one.')).toBeVisible(); |
||||
|
||||
// navigating to page four with full width layout without sidebar menu
|
||||
await page.getByText('Full-width page example').click(); |
||||
|
||||
// navigate back to page one
|
||||
await page.getByRole('link', { name: 'Back', exact: true }).click(); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,26 @@
|
||||
import { AppConfigPage, AppPage, test as base } from '@grafana/plugin-e2e'; |
||||
import pluginJson from '../src/plugin.json'; |
||||
|
||||
type AppTestFixture = { |
||||
appConfigPage: AppConfigPage; |
||||
gotoPage: (path?: string) => Promise<AppPage>; |
||||
}; |
||||
|
||||
export const test = base.extend<AppTestFixture>({ |
||||
appConfigPage: async ({ gotoAppConfigPage }, use) => { |
||||
const configPage = await gotoAppConfigPage({ |
||||
pluginId: pluginJson.id, |
||||
}); |
||||
await use(configPage); |
||||
}, |
||||
gotoPage: async ({ gotoAppPage }, use) => { |
||||
await use((path) => |
||||
gotoAppPage({ |
||||
path, |
||||
pluginId: pluginJson.id, |
||||
}) |
||||
); |
||||
}, |
||||
}); |
||||
|
||||
export { expect } from '@grafana/plugin-e2e'; |
||||
Loading…
Reference in new issue