mirror of https://github.com/grafana/grafana
commit
68bd82f1b6
@ -1,3 +1,4 @@ |
||||
{ |
||||
"version": "2.1.1" |
||||
"stable": "2.6.0", |
||||
"testing": "3.0.0-beta5" |
||||
} |
||||
|
@ -0,0 +1,46 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"net" |
||||
"net/http" |
||||
"net/http/httputil" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/middleware" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
var gNetProxyTransport = &http.Transport{ |
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, |
||||
Proxy: http.ProxyFromEnvironment, |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 30 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
TLSHandshakeTimeout: 10 * time.Second, |
||||
} |
||||
|
||||
func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy { |
||||
director := func(req *http.Request) { |
||||
req.URL.Scheme = "https" |
||||
req.URL.Host = "grafana.net" |
||||
req.Host = "grafana.net" |
||||
|
||||
req.URL.Path = util.JoinUrlFragments("https://grafana.net/api", proxyPath) |
||||
|
||||
// clear cookie headers
|
||||
req.Header.Del("Cookie") |
||||
req.Header.Del("Set-Cookie") |
||||
} |
||||
|
||||
return &httputil.ReverseProxy{Director: director} |
||||
} |
||||
|
||||
func ProxyGnetRequest(c *middleware.Context) { |
||||
proxyPath := c.Params("*") |
||||
proxy := ReverseProxyGnetReq(proxyPath) |
||||
proxy.Transport = gNetProxyTransport |
||||
proxy.ServeHTTP(c.Resp, c.Req.Request) |
||||
c.Resp.Header().Del("Set-Cookie") |
||||
} |
@ -0,0 +1,119 @@ |
||||
package plugins |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/log" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
type GrafanaNetPlugin struct { |
||||
Slug string `json:"slug"` |
||||
Version string `json:"version"` |
||||
} |
||||
|
||||
type GithubLatest struct { |
||||
Stable string `json:"stable"` |
||||
Testing string `json:"testing"` |
||||
} |
||||
|
||||
func StartPluginUpdateChecker() { |
||||
if !setting.CheckForUpdates { |
||||
return |
||||
} |
||||
|
||||
// do one check directly
|
||||
go checkForUpdates() |
||||
|
||||
ticker := time.NewTicker(time.Minute * 10) |
||||
for { |
||||
select { |
||||
case <-ticker.C: |
||||
checkForUpdates() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func getAllExternalPluginSlugs() string { |
||||
str := "" |
||||
|
||||
for _, plug := range Plugins { |
||||
if plug.IsCorePlugin { |
||||
continue |
||||
} |
||||
|
||||
str += plug.Id + "," |
||||
} |
||||
|
||||
return str |
||||
} |
||||
|
||||
func checkForUpdates() { |
||||
log.Trace("Checking for updates") |
||||
|
||||
client := http.Client{Timeout: time.Duration(5 * time.Second)} |
||||
|
||||
pluginSlugs := getAllExternalPluginSlugs() |
||||
resp, err := client.Get("https://grafana.net/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion) |
||||
|
||||
if err != nil { |
||||
log.Trace("Failed to get plugins repo from grafana.net, %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
log.Trace("Update check failed, reading response from grafana.net, %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
gNetPlugins := []GrafanaNetPlugin{} |
||||
err = json.Unmarshal(body, &gNetPlugins) |
||||
if err != nil { |
||||
log.Trace("Failed to unmarshal plugin repo, reading response from grafana.net, %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
for _, plug := range Plugins { |
||||
for _, gplug := range gNetPlugins { |
||||
if gplug.Slug == plug.Id { |
||||
plug.GrafanaNetVersion = gplug.Version |
||||
plug.GrafanaNetHasUpdate = plug.Info.Version != plug.GrafanaNetVersion |
||||
} |
||||
} |
||||
} |
||||
|
||||
resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json") |
||||
if err != nil { |
||||
log.Trace("Failed to get lates.json repo from github: %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
defer resp2.Body.Close() |
||||
body, err = ioutil.ReadAll(resp2.Body) |
||||
if err != nil { |
||||
log.Trace("Update check failed, reading response from github.net, %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
var githubLatest GithubLatest |
||||
err = json.Unmarshal(body, &githubLatest) |
||||
if err != nil { |
||||
log.Trace("Failed to unmarshal github latest, reading response from github: %v", err.Error()) |
||||
return |
||||
} |
||||
|
||||
if strings.Contains(setting.BuildVersion, "-") { |
||||
GrafanaLatestVersion = githubLatest.Testing |
||||
GrafanaHasUpdate = !strings.HasPrefix(setting.BuildVersion, githubLatest.Testing) |
||||
} else { |
||||
GrafanaLatestVersion = githubLatest.Stable |
||||
GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
<div class="modal-body"> |
||||
<div class="modal-header"> |
||||
<h2 class="modal-header-title"> |
||||
<i class="fa fa-cloud-download"></i> |
||||
<span class="p-l-1">Update Plugin</span> |
||||
</h2> |
||||
|
||||
<a class="modal-header-close" ng-click="dismiss();"> |
||||
<i class="fa fa-remove"></i> |
||||
</a> |
||||
</div> |
||||
|
||||
<div class="modal-content"> |
||||
<div class="gf-form-group"> |
||||
<p>Type the following on the command line to update {{plugin.name}}.</p> |
||||
<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre> |
||||
<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span> |
||||
</div> |
||||
<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div> |
||||
</div> |
||||
</div> |
@ -1,73 +0,0 @@ |
||||
// Type definitions for es6-promise
|
||||
// Project: https://github.com/jakearchibald/ES6-Promise
|
||||
// Definitions by: François de Campredon <https://github.com/fdecampredon/>, vvakame <https://github.com/vvakame>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface Thenable<R> { |
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>; |
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>; |
||||
} |
||||
|
||||
declare class Promise<R> implements Thenable<R> { |
||||
/** |
||||
* If you call resolve in the body of the callback passed to the constructor, |
||||
* your promise is fulfilled with result object passed to resolve. |
||||
* If you call reject your promise is rejected with the object passed to resolve. |
||||
* For consistency and debugging (eg stack traces), obj should be an instanceof Error. |
||||
* Any errors thrown in the constructor callback will be implicitly passed to reject(). |
||||
*/ |
||||
constructor(callback: (resolve : (value?: R | Thenable<R>) => void, reject: (error?: any) => void) => void); |
||||
|
||||
/** |
||||
* onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. |
||||
* Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. |
||||
* Both callbacks have a single parameter , the fulfillment value or rejection reason. |
||||
* "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. |
||||
* If an error is thrown in the callback, the returned promise rejects with that error. |
||||
* |
||||
* @param onFulfilled called when/if "promise" resolves |
||||
* @param onRejected called when/if "promise" rejects |
||||
*/ |
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Promise<U>; |
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Promise<U>; |
||||
|
||||
/** |
||||
* Sugar for promise.then(undefined, onRejected) |
||||
* |
||||
* @param onRejected called when/if "promise" rejects |
||||
*/ |
||||
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Promise<U>; |
||||
} |
||||
|
||||
declare module Promise { |
||||
/** |
||||
* Make a new promise from the thenable. |
||||
* A thenable is promise-like in as far as it has a "then" method. |
||||
*/ |
||||
function resolve<R>(value?: R | Thenable<R>): Promise<R>; |
||||
|
||||
/** |
||||
* Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error |
||||
*/ |
||||
function reject(error: any): Promise<any>; |
||||
|
||||
/** |
||||
* Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. |
||||
* the array passed to all can be a mixture of promise-like objects and other objects. |
||||
* The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value. |
||||
*/ |
||||
function all<R>(promises: (R | Thenable<R>)[]): Promise<R[]>; |
||||
|
||||
/** |
||||
* Make a Promise that fulfills when any item fulfills, and rejects if any item rejects. |
||||
*/ |
||||
function race<R>(promises: (R | Thenable<R>)[]): Promise<R>; |
||||
} |
||||
|
||||
declare module 'es6-promise' { |
||||
var foo: typeof Promise; // Temp variable to reference Promise in local context
|
||||
module rsvp { |
||||
export var Promise: typeof foo; |
||||
} |
||||
export = rsvp; |
||||
} |
@ -1,22 +1,27 @@ |
||||
<h3 class="page-heading">CloudWatch details</h3> |
||||
|
||||
<div class="gf-form-group"> |
||||
<div class="gf-form-group max-width-30"> |
||||
<div class="gf-form"> |
||||
<label class="gf-form-label width-14"> |
||||
Credentials profile name<tip>Credentials profile name, as specified in ~/.aws/credentials, leave blank for default</tip> |
||||
</label> |
||||
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.database' placeholder="default"></input> |
||||
<label class="gf-form-label width-13">Credentials profile name</label> |
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.database' placeholder="default"></input> |
||||
<info-popover mode="right-absolute"> |
||||
Credentials profile name, as specified in ~/.aws/credentials, leave blank for default |
||||
</info-popover> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<label class="gf-form-label width-14"> |
||||
Default Region<tip>Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.</tip> |
||||
</label> |
||||
<div class="gf-form-select-wrapper"> |
||||
<select class="gf-form-input max-width-15" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select> |
||||
<label class="gf-form-label width-13">Default Region</label> |
||||
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon"> |
||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select> |
||||
<info-popover mode="right-absolute"> |
||||
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region. |
||||
</info-popover> |
||||
</div> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<label class="gf-form-label width-14">Custom Metrics namespace<tip>Namespaces of Custom Metrics</tip></label> |
||||
<input type="text" class="gf-form-input max-width-15" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input> |
||||
<label class="gf-form-label width-13">Custom Metrics namespace</label> |
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input> |
||||
<info-popover mode="right-absolute"> |
||||
Namespaces of Custom Metrics |
||||
</info-popover> |
||||
</div> |
||||
</div> |
||||
|
@ -1,40 +1,32 @@ |
||||
<div class="gf-form-group"> |
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Mode</span> |
||||
<div class="gf-form-select-wrapper max-width-10"> |
||||
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select> |
||||
</div> |
||||
</div> |
||||
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'"> |
||||
<span class="gf-form-label"> |
||||
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<div class="section gf-form-group"> |
||||
<h5 class="section-heading">Options</h5> |
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Search options</span> |
||||
<span class="gf-form-label">Query</span> |
||||
<gf-form-switch class="gf-form" label="Starred" label-class="width-9" checked="ctrl.panel.starred" on-change="ctrl.refresh()"></gf-form-switch> |
||||
<gf-form-switch class="gf-form" label="Recently viewed" label-class="width-9" checked="ctrl.panel.recent" on-change="ctrl.refresh()"></gf-form-switch> |
||||
<gf-form-switch class="gf-form" label="Search" label-class="width-9" checked="ctrl.panel.search" on-change="ctrl.refresh()"></gf-form-switch> |
||||
|
||||
<input type="text" class="gf-form-input" placeholder="title query" |
||||
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur> |
||||
<gf-form-switch class="gf-form" label="Show headings" label-class="width-9" checked="ctrl.panel.headings" on-change="ctrl.refresh()"></gf-form-switch> |
||||
|
||||
</div> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-9">Max items</span> |
||||
<input class="gf-form-input max-width-5" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()"> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form"> |
||||
<span class="gf-form-label">Tags</span> |
||||
<div class="section gf-form-group"> |
||||
<h5 class="section-heading">Search</h5> |
||||
|
||||
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()"> |
||||
</bootstrap-tagsinput> |
||||
</div> |
||||
</div> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-6">Query</span> |
||||
<input type="text" class="gf-form-input" placeholder="title query" ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur> |
||||
</div> |
||||
|
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-6">Tags</span> |
||||
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()"> |
||||
</bootstrap-tagsinput> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Limit number to</span> |
||||
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
@ -1,12 +1,17 @@ |
||||
<div class="dashlist"> |
||||
<div class="dashlist-item" ng-repeat="dash in ctrl.dashList"> |
||||
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}"> |
||||
<span class="dashlist-title"> |
||||
{{dash.title}} |
||||
</span> |
||||
<span class="dashlist-star"> |
||||
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i> |
||||
</span> |
||||
</a> |
||||
</div> |
||||
<div class="dashlist" ng-repeat="group in ctrl.groups"> |
||||
<div class="dashlist-section" ng-if="group.show"> |
||||
<h6 class="dashlist-section-header" ng-show="ctrl.panel.headings"> |
||||
{{group.header}} |
||||
</h6> |
||||
<div class="dashlist-item" ng-repeat="dash in group.list"> |
||||
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}"> |
||||
<span class="dashlist-title"> |
||||
{{dash.title}} |
||||
</span> |
||||
<span class="dashlist-star"> |
||||
<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i> |
||||
</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
@ -0,0 +1,2 @@ |
||||
# Plugin List Panel - Native Plugin |
||||
|
@ -0,0 +1,40 @@ |
||||
<div class="gf-form-group"> |
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Mode</span> |
||||
<div class="gf-form-select-wrapper max-width-10"> |
||||
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select> |
||||
</div> |
||||
</div> |
||||
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'"> |
||||
<span class="gf-form-label"> |
||||
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Search options</span> |
||||
<span class="gf-form-label">Query</span> |
||||
|
||||
<input type="text" class="gf-form-input" placeholder="title query" |
||||
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur> |
||||
|
||||
</div> |
||||
|
||||
<div class="gf-form"> |
||||
<span class="gf-form-label">Tags</span> |
||||
|
||||
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()"> |
||||
</bootstrap-tagsinput> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="gf-form-inline"> |
||||
<div class="gf-form"> |
||||
<span class="gf-form-label width-10">Limit number to</span> |
||||
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()"> |
||||
</div> |
||||
</div> |
||||
</div> |
After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,30 @@ |
||||
<div class="pluginlist"> |
||||
<div class="pluginlist-section" ng-repeat="category in ctrl.viewModel"> |
||||
<h6 class="pluginlist-section-header"> |
||||
{{category.header}} |
||||
</h6> |
||||
<div class="pluginlist-item" ng-repeat="plugin in category.list"> |
||||
<a class="pluginlist-link pluginlist-link-{{plugin.state}} pointer" href="plugins/{{plugin.id}}/edit"> |
||||
<span> |
||||
<img ng-src="{{plugin.info.logos.small}}" class="pluginlist-image"> |
||||
<span class="pluginlist-title">{{plugin.name}}</span> |
||||
<span class="pluginlist-version">v{{plugin.info.version}}</span> |
||||
</span> |
||||
<span class="pluginlist-message pluginlist-message--update" ng-show="plugin.hasUpdate" ng-click="ctrl.updateAvailable(plugin, $event)" bs-tooltip="'New version: ' + plugin.latestVersion"> |
||||
Update available! |
||||
</span> |
||||
<span class="pluginlist-message pluginlist-message--enable" ng-show="!plugin.enabled && !plugin.hasUpdate"> |
||||
Enable now |
||||
</span> |
||||
<span class="pluginlist-message pluginlist-message--no-update" ng-show="plugin.enabled && !plugin.hasUpdate"> |
||||
Up to date |
||||
</span> |
||||
</a> |
||||
</div> |
||||
<div class="pluginlist-item" ng-show="category.list.length === 0"> |
||||
<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana.net/plugins/"> |
||||
<span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,74 @@ |
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash'; |
||||
import config from 'app/core/config'; |
||||
import {PanelCtrl} from '../../../features/panel/panel_ctrl'; |
||||
|
||||
// Set and populate defaults
|
||||
var panelDefaults = { |
||||
}; |
||||
|
||||
class PluginListCtrl extends PanelCtrl { |
||||
static templateUrl = 'module.html'; |
||||
|
||||
pluginList: any[]; |
||||
viewModel: any; |
||||
|
||||
/** @ngInject */ |
||||
constructor($scope, $injector, private backendSrv, private $location) { |
||||
super($scope, $injector); |
||||
_.defaults(this.panel, panelDefaults); |
||||
|
||||
this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); |
||||
this.pluginList = []; |
||||
this.viewModel = [ |
||||
{header: "Installed Apps", list: [], type: 'app'}, |
||||
{header: "Installed Panels", list: [], type: 'panel'}, |
||||
{header: "Installed Datasources", list: [], type: 'datasource'}, |
||||
]; |
||||
|
||||
this.update(); |
||||
} |
||||
|
||||
onInitEditMode() { |
||||
this.editorTabIndex = 1; |
||||
this.addEditorTab('Options', 'public/app/plugins/panel/pluginlist/editor.html'); |
||||
} |
||||
|
||||
gotoPlugin(plugin, evt) { |
||||
if (evt) { evt.stopPropagation(); } |
||||
this.$location.url(`plugins/${plugin.id}/edit`); |
||||
} |
||||
|
||||
updateAvailable(plugin, $event) { |
||||
$event.stopPropagation(); |
||||
$event.preventDefault(); |
||||
|
||||
var modalScope = this.$scope.$new(true); |
||||
modalScope.plugin = plugin; |
||||
|
||||
this.publishAppEvent('show-modal', { |
||||
src: 'public/app/features/plugins/partials/update_instructions.html', |
||||
scope: modalScope |
||||
}); |
||||
} |
||||
|
||||
update() { |
||||
this.backendSrv.get('api/plugins', {embedded: 0, core: 0}).then(plugins => { |
||||
this.pluginList = plugins; |
||||
this.viewModel[0].list = _.filter(plugins, {type: 'app'}); |
||||
this.viewModel[1].list = _.filter(plugins, {type: 'panel'}); |
||||
this.viewModel[2].list = _.filter(plugins, {type: 'datasource'}); |
||||
|
||||
for (let plugin of this.pluginList) { |
||||
if (plugin.hasUpdate) { |
||||
plugin.state = 'has-update'; |
||||
} else if (!plugin.enabled) { |
||||
plugin.state = 'not-enabled'; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
export {PluginListCtrl, PluginListCtrl as PanelCtrl} |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"type": "panel", |
||||
"name": "Plugin list", |
||||
"id": "pluginlist", |
||||
|
||||
"info": { |
||||
"author": { |
||||
"name": "Grafana Project", |
||||
"url": "http://grafana.org" |
||||
}, |
||||
"logos": { |
||||
"small": "img/icn-dashlist-panel.svg", |
||||
"large": "img/icn-dashlist-panel.svg" |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue