├── .gitignore ├── assets └── grafonnet-example.png ├── ui ├── style │ ├── app.css │ └── components │ │ ├── editor.css │ │ ├── graph.css │ │ ├── control.css │ │ └── empty.css └── js │ ├── reducers │ ├── index.js │ └── runReducer.js │ ├── actions │ ├── run.js │ ├── types.js │ ├── themeUpdate.js │ ├── wrapText.js │ └── codeUpdate.js │ ├── components │ ├── graph.js │ ├── empty.js │ ├── control.js │ └── editor.js │ ├── app.js │ └── store.js ├── .babelrc ├── webpack.dev.js ├── handlers ├── health.go └── run.go ├── webpack.prod.js ├── public └── index.html ├── grafana ├── models.go ├── cleaner.go ├── priority_queue.go ├── priority_queue_test.go └── grafana.go ├── webpack.common.js ├── Dockerfile ├── main.go ├── README.md ├── go.mod ├── application.yml.sample ├── Makefile ├── package.json ├── config └── config.go ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | application.yml 5 | -------------------------------------------------------------------------------- /assets/grafonnet-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lahsivjar/grafonnet-playground/HEAD/assets/grafonnet-example.png -------------------------------------------------------------------------------- /ui/style/app.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0px; 3 | height: 100%; 4 | } 5 | 6 | .root { 7 | height: 100%; 8 | } 9 | 10 | .app { 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-proposal-class-properties" 8 | ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require("webpack-merge"); 2 | const common = require("./webpack.common.js"); 3 | 4 | module.exports = merge(common, { 5 | devtool: "inline-source-map" 6 | }); 7 | 8 | -------------------------------------------------------------------------------- /ui/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import RunReducer from './runReducer' 3 | 4 | const reducers = combineReducers({ 5 | RunReducer: RunReducer, 6 | }); 7 | 8 | export default reducers; 9 | -------------------------------------------------------------------------------- /ui/style/components/editor.css: -------------------------------------------------------------------------------- 1 | .editor { 2 | height: calc(100vh - 48px); 3 | overflow: auto; 4 | } 5 | 6 | .react-codemirror2 { 7 | height: 100%; 8 | } 9 | 10 | .CodeMirror { 11 | font-family: monospace; 12 | height: 100%; 13 | direction: ltr; 14 | } 15 | -------------------------------------------------------------------------------- /handlers/health.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // HealthCheckHandler provides a health check endpoint for probes 10 | func HealthCheckHandler(c *gin.Context) { 11 | c.JSON(http.StatusOK, gin.H{ 12 | "status": "up", 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /ui/style/components/graph.css: -------------------------------------------------------------------------------- 1 | .graph { 2 | height: 100%; 3 | width: 100%; 4 | } 5 | 6 | .graph-frame { 7 | height: 100%; 8 | width: 100%; 9 | } 10 | 11 | iframe { 12 | margin: none; 13 | padding: none; 14 | border: none; 15 | line-height: 0; 16 | display: block; 17 | } 18 | -------------------------------------------------------------------------------- /ui/js/actions/run.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { RUN } from './types'; 4 | 5 | export function Run(data) { 6 | 7 | return dispatch => dispatch({ 8 | type: RUN, 9 | payload: axios.post('/playground/api/v1/run', data) 10 | .then(res => res.data) 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /ui/js/actions/types.js: -------------------------------------------------------------------------------- 1 | export const RUN = 'RUN'; 2 | export const RUN_PENDING = 'RUN_PENDING'; 3 | export const RUN_FULFILLED = 'RUN_FULFILLED'; 4 | export const RUN_REJECTED = 'RUN_REJECTED'; 5 | 6 | export const CODE_UPDATE = 'CODE_UPDATE'; 7 | export const THEME_UPDATE = 'THEME_UPDATE'; 8 | export const WRAP_TEXT = 'WRAP_TEXT'; 9 | -------------------------------------------------------------------------------- /ui/style/components/control.css: -------------------------------------------------------------------------------- 1 | .control { 2 | height: 48px; 3 | background: #81D4FA; 4 | } 5 | 6 | .control-select-theme { 7 | font-family: '"Roboto", "Helvetica", "Arial", sans-serif'; 8 | font-weight: 400; 9 | font-size: 16px; 10 | padding: 0px 6px; 11 | } 12 | 13 | .control-select-container { 14 | padding: 6px; 15 | } 16 | -------------------------------------------------------------------------------- /ui/js/actions/themeUpdate.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { set } from 'idb-keyval'; 3 | 4 | import { THEME_UPDATE } from './types'; 5 | 6 | export function ThemeUpdate(theme) { 7 | const payload = { 8 | theme: theme, 9 | }; 10 | 11 | set('theme', payload) 12 | .catch(err => console.error('Failed to set theme in local storage')) 13 | 14 | return dispatch => dispatch({ 15 | type: THEME_UPDATE, 16 | payload: payload, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /ui/js/actions/wrapText.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { set } from 'idb-keyval'; 3 | 4 | import { WRAP_TEXT } from './types'; 5 | 6 | export function WrapText(isWrapEnabled) { 7 | const payload = { 8 | wrap: isWrapEnabled, 9 | }; 10 | 11 | set('wrap', payload) 12 | .catch(err => console.error('Failed to set wrap in local storage')) 13 | 14 | return dispatch => dispatch({ 15 | type: WRAP_TEXT, 16 | payload: payload, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require("webpack-merge"); 2 | const TerserPlugin = require('terser-webpack-plugin'); 3 | const common = require("./webpack.common.js"); 4 | const webpack = require("webpack"); 5 | 6 | module.exports = merge(common, { 7 | devtool: "source-map", 8 | plugins: [ 9 | new webpack.DefinePlugin({ 10 | "process.env.NODE_ENV": JSON.stringify("production") 11 | }), 12 | ], 13 | optimization: { 14 | minimizer: [new TerserPlugin()], 15 | }, 16 | }); 17 | 18 | -------------------------------------------------------------------------------- /ui/js/actions/codeUpdate.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { throttle } from 'throttle-debounce'; 3 | import { set } from 'idb-keyval'; 4 | 5 | import { CODE_UPDATE } from './types'; 6 | 7 | export function CodeUpdate(code) { 8 | const payload = { 9 | code: code, 10 | }; 11 | 12 | throttledSave(payload); 13 | 14 | return dispatch => dispatch({ 15 | type: CODE_UPDATE, 16 | payload: payload, 17 | }); 18 | } 19 | 20 | const throttledSave = throttle(100, (payload) => { 21 | set('code', payload) 22 | .catch(err => console.error('Failed to set code in local storage')) 23 | }) 24 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Grafonnet playground 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /grafana/models.go: -------------------------------------------------------------------------------- 1 | package grafana 2 | 3 | // CreateRequest models parameters required for creating/updating dashboard 4 | type CreateRequest struct { 5 | Dashboard interface{} `json:"dashboard"` 6 | Overwrite bool `json:"overwrite"` 7 | FolderID int `json:"folderId,omitempty"` 8 | } 9 | 10 | // CreateResponse models response from grafana for creating/updating dashboard 11 | type CreateResponse struct { 12 | ID int `json:"id"` 13 | UID string `json:"uid"` 14 | URL string `json:"url"` 15 | Status string `json:"status"` 16 | Version int `json:"version"` 17 | } 18 | 19 | // NewCreateRequest returns a new create request model 20 | func NewCreateRequest(folderID int) *CreateRequest { 21 | return &CreateRequest{ 22 | Overwrite: true, 23 | FolderID: folderID, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: { 6 | app: './ui/js/app.js', 7 | }, 8 | output: { 9 | path: path.resolve(process.cwd(), 'dist'), 10 | filename: '[name].js' 11 | }, 12 | plugins: [ 13 | new CleanWebpackPlugin() 14 | ], 15 | module: { 16 | rules: [ 17 | { 18 | test: path.join(__dirname, "./ui/js"), 19 | exclude: /node_modules/, 20 | use: { 21 | loader: "babel-loader" 22 | } 23 | }, 24 | { 25 | test: /\.css$/, 26 | use: [ 27 | "style-loader", 28 | "css-loader" 29 | ] 30 | } 31 | ] 32 | } 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /ui/js/components/graph.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import Empty from './empty'; 5 | import '../../style/components/graph.css'; 6 | 7 | class Graph extends React.Component { 8 | getContent = () => { 9 | if (this.props.url) { 10 | return