Commit c433597f by Johannes Schill

Merge branch 'develop' into 9879-login

parents b8bfe51b daf32c57
......@@ -221,7 +221,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Url: setting.AppSubUrl + "/datasources",
},
{
Text: "Members",
Text: "Users",
Id: "users",
Description: "Manage org members",
Icon: "icon-gf icon-gf-fw icon-gf-users",
......
......@@ -23,9 +23,7 @@ func SetRepository(rep Repository) {
}
type SaveDashboardItem struct {
TitleLower string
OrgId int64
Folder string
UpdatedAt time.Time
UserId int64
Message string
......@@ -57,6 +55,8 @@ func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.D
OrgId: json.OrgId,
Overwrite: json.Overwrite,
UserId: json.UserId,
FolderId: dashboard.FolderId,
IsFolder: dashboard.IsFolder,
}
if !json.UpdatedAt.IsZero() {
......
package dashboards
import (
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
......@@ -23,11 +22,9 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das
dash := &dashboards.SaveDashboardItem{}
dash.Dashboard = models.NewDashboardFromJson(data)
dash.TitleLower = strings.ToLower(dash.Dashboard.Title)
dash.UpdatedAt = lastModified
dash.Overwrite = true
dash.OrgId = cfg.OrgId
dash.Folder = cfg.Folder
dash.Dashboard.Data.Set("editable", cfg.Editable)
if dash.Dashboard.Title == "" {
......
......@@ -76,8 +76,8 @@ export default class PageHeader extends React.Component<IProps, any> {
const breadcrumbsResult = [];
for (let i = 0; i < breadcrumbs.length; i++) {
const bc = breadcrumbs[i];
if (bc.uri) {
breadcrumbsResult.push(<a className="text-link" key={i} href={bc.uri}>{bc.title}</a>);
if (bc.url) {
breadcrumbsResult.push(<a className="text-link" key={i} href={bc.url}>{bc.title}</a>);
} else {
breadcrumbsResult.push(<span key={i}> / {bc.title}</span>);
}
......
......@@ -9,7 +9,6 @@ export class SearchResultsCtrl {
/** @ngInject */
constructor(private $location) {
}
toggleFolderExpand(section) {
......
......@@ -83,6 +83,11 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'FolderPermissionsCtrl',
controllerAs: 'ctrl',
})
.when('/dashboards/folder/:folderId/:type/:slug/settings', {
templateUrl: 'public/app/features/dashboard/partials/folder_settings.html',
controller : 'FolderSettingsCtrl',
controllerAs: 'ctrl',
})
.when('/dashboards/folder/:folderId/:type/:slug', {
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
controller : 'FolderDashboardsCtrl',
......
......@@ -32,11 +32,13 @@ import coreModule from 'app/core/core_module';
import {DashboardListCtrl} from './dashboard_list_ctrl';
import {FolderDashboardsCtrl} from './folder_dashboards_ctrl';
import {FolderPermissionsCtrl} from './folder_permissions_ctrl';
import {FolderSettingsCtrl} from './folder_settings_ctrl';
import {DashboardImportCtrl} from './dashboard_import_ctrl';
import {CreateFolderCtrl} from './create_folder_ctrl';
coreModule.controller('DashboardListCtrl', DashboardListCtrl);
coreModule.controller('FolderDashboardsCtrl', FolderDashboardsCtrl);
coreModule.controller('FolderPermissionsCtrl', FolderPermissionsCtrl);
coreModule.controller('FolderSettingsCtrl', FolderSettingsCtrl);
coreModule.controller('DashboardImportCtrl', DashboardImportCtrl);
coreModule.controller('CreateFolderCtrl', CreateFolderCtrl);
......@@ -14,10 +14,7 @@ export class DashNavCtrl {
private $rootScope,
private dashboardSrv,
private $location,
public playlistSrv,
navModelSrv) {
this.navModel = navModelSrv.getDashboardNav(this.dashboard, this);
public playlistSrv) {
appEvents.on('save-dashboard', this.saveDashboard.bind(this), $scope);
if (this.dashboard.meta.isSnapshot) {
......
......@@ -12,7 +12,7 @@ export class FolderPageLoader {
url: '/fsdfds',
text: '',
breadcrumbs: [
{ title: 'Dashboards', uri: '/dashboards' },
{ title: 'Dashboards', url: '/dashboards' },
{ title: ' ' },
],
children: [
......@@ -29,12 +29,19 @@ export class FolderPageLoader {
id: 'manage-folder-permissions',
text: 'Permissions',
url: '/dashboards/permissions'
},
{
active: activeChildId === 'manage-folder-settings',
icon: 'fa fa-fw fa-cog',
id: 'manage-folder-settings',
text: 'Settings',
url: '/dashboards/settings'
}
]
}
};
this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
return this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => {
const folderTitle = result.dashboard.title;
ctrl.navModel.main.text = '';
ctrl.navModel.main.breadcrumbs = [
......@@ -42,13 +49,22 @@ export class FolderPageLoader {
{ title: folderTitle }
];
const folderUrl = `/dashboards/folder/${folderId}/${result.meta.type}/${result.meta.slug}`;
const folderUrl = this.createFolderUrl(folderId, result.meta.type, result.meta.slug);
const dashTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-dashboards' });
dashTab.url = folderUrl;
const permTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-permissions' });
permTab.url = folderUrl + '/permissions';
const settingsTab = _.find(ctrl.navModel.main.children, { id: 'manage-folder-settings' });
settingsTab.url = folderUrl + '/settings';
return result;
});
}
createFolderUrl(folderId: number, type: string, slug: string) {
return `/dashboards/folder/${folderId}/${type}/${slug}`;
}
}
import {FolderPageLoader} from './folder_page_loader';
import appEvents from 'app/core/app_events';
export class FolderSettingsCtrl {
folderPageLoader: FolderPageLoader;
navModel: any;
folderId: number;
canSave = false;
dashboard: any;
meta: any;
/** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams, private $location) {
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) {
this.folderId = $routeParams.folderId;
this.folderPageLoader = new FolderPageLoader(this.backendSrv, this.$routeParams);
this.folderPageLoader.load(this, this.folderId, 'manage-folder-settings')
.then(result => {
this.dashboard = result.dashboard;
this.meta = result.meta;
this.canSave = result.meta.canSave;
});
}
}
save() {
return this.backendSrv.saveDashboard(this.dashboard, {overwrite: false})
.then(result => {
var folderUrl = this.folderPageLoader.createFolderUrl(this.folderId, this.meta.type, result.slug);
if (folderUrl !== this.$location.path()) {
this.$location.url(folderUrl + '/settings');
}
appEvents.emit('dashboard-saved');
appEvents.emit('alert-success', ['Folder saved']);
})
.catch(this.handleSaveFolderError);
}
delete(evt) {
if (evt) {
evt.stopPropagation();
evt.preventDefault();
}
appEvents.emit('confirm-modal', {
title: 'Delete',
text: `Do you want to delete this folder and all its dashboards?`,
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: () => {
return this.backendSrv.delete(`/api/dashboards/${this.meta.type}/${this.meta.slug}`).then(() => {
appEvents.emit('alert-success', ['Folder deleted']);
this.$location.url('/dashboards');
});
}
});
}
handleSaveFolderError(err) {
if (err.data && err.data.status === 'version-mismatch') {
err.isHandled = true;
appEvents.emit('confirm-modal', {
title: 'Conflict',
text: 'Someone else has updated this folder.',
text2: 'Would you still like to save this folder?',
yesText: 'Save & Overwrite',
icon: 'fa-warning',
onConfirm: () => {
this.backendSrv.saveDashboard(this.dashboard, {overwrite: true});
}
});
}
if (err.data && err.data.status === 'name-exists') {
err.isHandled = true;
appEvents.emit('alert-error', ['A folder or dashboard with this name exists already.']);
}
}
}
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<div class="section gf-form-group">
<h1 class="section-heading">Folder Settings</h1>
<form name="folderSettingsForm" ng-submit="ctrl.save()">
<div class="gf-form">
<label class="gf-form-label width-7">Name</label>
<input type="text" class="gf-form-input width-30" ng-model='ctrl.dashboard.title'></input>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-disabled="!ctrl.canSave">
<i class="fa fa-trash"></i>
Save
</button>
<button class="btn btn-danger" ng-click="ctrl.delete($event)" ng-disabled="!ctrl.canSave">
<i class="fa fa-trash"></i>
Delete
</button>
</div>
</form>
</div>
</div>
import {coreModule} from 'app/core/core';
var template = `
<div class="gf-form-select-wrapper max-width-13">
<div class="gf-form-select-wrapper max-width-18">
<select class="gf-form-input" ng-model="panel.repeat" ng-options="f.value as f.text for f in variables" ng-change="optionChanged()">
<option value=""></option>
</div>
......
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import coreModule from 'app/core/core_module';
import Remarkable from 'remarkable';
......@@ -44,7 +42,7 @@ export class OrgUsersCtrl {
if (this.externalUserMngLinkName) {
return this.externalUserMngLinkName;
}
return "Add Member";
return "Invite User";
}
get() {
......
///<reference path="../../headers/common.d.ts" />
import coreModule from '../../core/core_module';
import {appEvents} from 'app/core/core';
import _ from 'lodash';
export class DataSourcesCtrl {
datasources: any;
unfiltered: any;
navModel: any;
searchQuery: string;
/** @ngInject */
constructor(
private $scope,
private backendSrv,
private datasourceSrv,
private $location,
private navModelSrv) {
this.navModel = this.navModelSrv.getNav('cfg', 'datasources', 0);
this.navigateToUrl = this.navigateToUrl.bind(this);
backendSrv.get('/api/datasources').then(result => {
this.datasources = result;
});
appEvents.on('location-change', payload => {
this.navigateToUrl(payload.href);
this.unfiltered = result;
});
}
navigateToUrl(url) {
// debugger;
this.$location.path(url);
this.$location.replace();
onQueryUpdated() {
let regex = new RegExp(this.searchQuery, 'ig');
this.datasources = _.filter(this.unfiltered, item => {
return regex.test(item.name) || regex.test(item.type);
});
}
removeDataSourceConfirmed(ds) {
......
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<div ng-if="ctrl.datasources.length">
<div ng-if="ctrl.unfiltered.length">
<div class="page-action-bar">
<div class="gf-form">
<label class="gf-form-label">Search</label>
<input type="text" class="gf-form-input width-20" ng-model="ctrl.searchQuery" ng-change="ctrl.onQueryUpdated()" give-focus="true" placeholder="Filter by name or type" />
</div>
<div class="page-action-bar__spacer"></div>
<a class="page-header__cta btn btn-success" href="datasources/new">
<i class="fa fa-plus"></i>
Add data source
......@@ -42,7 +48,7 @@
</section>
</div>
<div ng-if="ctrl.datasources.length === 0">
<div ng-if="ctrl.unfiltered.length === 0">
<empty-list-cta model="{
title: 'There are no data sources defined yet',
buttonIcon: 'gicon gicon-dashboard-new',
......
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body" ng-init="ctrl.init()">
<div class="page-action-bar">
<button class="btn" ng-repeat="tab in ctrl.tabs" ng-class="{'btn-secondary': ctrl.tabIndex === $index, 'btn-inverse': ctrl.tabIndex !== $index}" ng-click="ctrl.tabIndex = $index">
{{tab}}
</button>
</div>
<div ng-if="ctrl.navModel">
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<div class="sidebar-container">
<div class="tab-content sidebar-content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Readme'">
<div class="tab-content sidebar-content" ng-if="ctrl.tab === 'readme'">
<div ng-bind-html="ctrl.readmeHtml" class="markdown-html">
</div>
</div>
<div class="tab-content sidebar-content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Config'">
<div class="tab-content sidebar-content" ng-if="ctrl.tab === 'config'">
<div ng-if="ctrl.model.id">
<plugin-component type="app-config-ctrl"></plugin-component>
......@@ -26,7 +20,7 @@
</div>
</div>
<div class="tab-content sidebar.content" ng-if="ctrl.tabs[ctrl.tabIndex] === 'Dashboards'">
<div class="tab-content sidebar-content" ng-if="ctrl.tab === 'dashboards'">
<dashboard-import-list plugin="ctrl.model"></dashboard-import-list>
</div>
......@@ -70,4 +64,5 @@
</section>
</aside>
</div>
</div>
</div>
<page-header model="ctrl.navModel"></page-header>
<!-- <div class="page&#45;header&#45;canvas"> -->
<!-- <div class="page&#45;container"> -->
<!-- <navbar model="ctrl.navModel"></navbar> -->
<!-- -->
<!-- <div class="page&#45;header"> -->
<!-- <page&#45;h1 model="ctrl.navModel"></page&#45;h1> -->
<!-- -->
<!-- <div class="page&#45;header&#45;tabs"> -->
<!-- <ul class="gf&#45;tabs"> -->
<!-- <li class="gf&#45;tabs&#45;item"> -->
<!-- <a class="gf&#45;tabs&#45;link" href="plugins?type=panel" ng&#45;class="{active: ctrl.tabIndex === 0}"> -->
<!-- <i class="icon&#45;gf icon&#45;gf&#45;panel"></i> -->
<!-- Panels -->
<!-- </a> -->
<!-- </li> -->
<!-- <li class="gf&#45;tabs&#45;item"> -->
<!-- <a class="gf&#45;tabs&#45;link" href="plugins?type=datasource" ng&#45;class="{active: ctrl.tabIndex === 1}"> -->
<!-- <i class="gicon gicon&#45;datasources"></i> -->
<!-- Data sources -->
<!-- </a> -->
<!-- </li> -->
<!-- <li class="gf&#45;tabs&#45;item"> -->
<!-- <a class="gf&#45;tabs&#45;link" href="plugins?type=app" ng&#45;class="{active: ctrl.tabIndex === 2}"> -->
<!-- <i class="icon&#45;gf icon&#45;gf&#45;apps"></i> -->
<!-- Apps -->
<!-- </a> -->
<!-- </li> -->
<!-- </ul> -->
<!-- -->
<!-- <a class="get&#45;more&#45;plugins&#45;link pull&#45;right" href="https://grafana.com/plugins?utm_source=grafana_plugin_list" target="_blank"> -->
<!-- Find more <img src="public/img/icn&#45;plugins&#45;tiny.svg" />plugins on Grafana.com -->
<!-- </a> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<div class="page-container page-body">
<div class="page-action-bar">
<div class="gf-form">
......
<navbar model="ctrl.navModel" ng-if="ctrl.navModel"></navbar>
<div ng-if="ctrl.navModel">
<page-header model="ctrl.navModel"></page-header>
<div class="page-container" >
<div class="page-container">
<div ng-if="ctrl.page">
<plugin-component type="app-page">
</plugin-component>
</div>
</div>
</div>
......@@ -11,8 +11,7 @@ export class PluginEditCtrl {
includes: any;
readmeHtml: any;
includedDatasources: any;
tabIndex: number;
tabs: any;
tab: string;
navModel: any;
hasDashboards: any;
preUpdateHook: () => any;
......@@ -24,24 +23,76 @@ export class PluginEditCtrl {
private $rootScope,
private backendSrv,
private $sce,
$routeParams,
private $routeParams,
navModelSrv,
) {
this.navModel = navModelSrv.getNav('cfg', 'plugins', 0);
this.model = {};
this.pluginId = $routeParams.pluginId;
this.tabIndex = 0;
this.tabs = ['Readme'];
this.pluginId = $routeParams.pluginId;
this.preUpdateHook = () => Promise.resolve();
this.postUpdateHook = () => Promise.resolve();
this.init();
}
setNavModel(model) {
let defaultTab = 'readme';
this.navModel = {
main: {
img: model.info.logos.large,
subTitle: model.info.description,
url: '',
text: '',
breadcrumbs: [
{ title: 'Plugins', url: '/plugins' },
{ title: model.name },
],
children: [
{
icon: 'fa fa-fw fa-file-text-o',
id: 'readme',
text: 'Readme',
url: `plugins/${this.model.id}/edit?tab=readme`
}
]
}
};
if (model.type === 'app') {
this.navModel.main.children.push({
icon: 'fa fa-fw fa-th-large',
id: 'config',
text: 'Config',
url: `plugins/${this.model.id}/edit?tab=config`
});
let hasDashboards = _.find(model.includes, {type: 'dashboard'});
if (hasDashboards) {
this.navModel.main.children.push({
icon: 'gicon gicon-dashboard',
id: 'dashboards',
text: 'Dashboards',
url: `plugins/${this.model.id}/edit?tab=dashboards`
});
}
defaultTab = 'config';
}
this.tab = this.$routeParams.tab || defaultTab;
for (let tab of this.navModel.main.children) {
if (tab.id === this.tab) {
tab.active = true;
}
}
}
init() {
return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => {
this.model = result;
this.pluginIcon = this.getPluginIcon(this.model.type);
this.navModel.breadcrumbs.push({text: this.model.name});
this.model.dependencies.plugins.forEach(plug => {
plug.icon = this.getPluginIcon(plug.type);
......@@ -52,16 +103,7 @@ export class PluginEditCtrl {
return plug;
});
if (this.model.type === 'app') {
this.hasDashboards = _.find(result.includes, {type: 'dashboard'});
if (this.hasDashboards) {
this.tabs.unshift('Dashboards');
}
this.tabs.unshift('Config');
this.tabIndex = 0;
}
this.setNavModel(this.model);
return this.initReadme();
});
}
......
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import _ from 'lodash';
......@@ -35,8 +33,20 @@ export class AppPageCtrl {
return;
}
this.navModel = this.navModelSrv.getNav('plugin-page-' + app.id);
this.navModel.breadcrumbs.push({text: this.page.name});
let pluginNav = this.navModelSrv.getNav('plugin-page-' + app.id);
this.navModel = {
main: {
img: app.info.logos.large,
subTitle: app.name,
url: '',
text: '',
breadcrumbs: [
{ title: app.name, url: pluginNav.main.url },
{ title: this.page.name },
],
}
};
}
loadPluginInfo() {
......
......@@ -167,14 +167,14 @@
</select>
</div>
</div>
<div class="gf-form max-width-21">
<div class="gf-form max-width-22">
<span class="gf-form-label width-7">
Refresh
<info-popover mode="right-normal">
When to update the values of this variable.
</info-popover>
</span>
<div class="gf-form-select-wrapper max-width-14">
<div class="gf-form-select-wrapper width-15">
<select class="gf-form-input" ng-model="current.refresh" ng-options="f.value as f.text for f in refreshOptions"></select>
</div>
</div>
......
......@@ -11,7 +11,7 @@
</ul>
</info-popover>
</span>
<div class="gf-form-select-wrapper width-8">
<div class="gf-form-select-wrapper width-9">
<select class="gf-form-input" ng-model="ctrl.annotation.type" ng-options="f.value as f.text for f in ctrl.types">
</select>
</div>
......
......@@ -78,7 +78,7 @@
<div class="gf-form">
<label class="gf-form-label width-7">Null value</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input max-width-8" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
<select class="gf-form-input max-width-9" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
......
......@@ -24,4 +24,5 @@
.search-results-filter-row__filters-item {
width: 150px;
margin-right: 0;
}
\ No newline at end of file
......@@ -124,7 +124,7 @@
}
.gf-form-switch--search-result__section {
min-width: 3.3rem;
min-width: 3.05rem;
margin-right: -0.3rem;
input + label {
......@@ -133,7 +133,7 @@
}
.gf-form-switch--search-result__item {
min-width: 2.6rem;
min-width: 2.7rem;
input + label {
height: 2.7rem;
......@@ -141,7 +141,7 @@
}
.gf-form-switch--search-result-filter-row__checkbox {
min-width: 4.7rem;
min-width: 3.75rem;
input + label {
height: 2.5rem;
......
......@@ -17,29 +17,24 @@
.panel-info-corner--info,
.panel-info-corner--links {
opacity: 0;
transition: all 1.5s ease-in-out 1s;
}
.navbar {
box-shadow: none;
background: transparent;
padding-left: $side-menu-width + 20px;
}
.navbar-page-btn {
border-color: transparent;
background: transparent;
transform: translate3d(-40px, 0, 0);
transition: all 1.5s ease-in-out 1s;
i {
opacity: 0;
transition: all 1.5s ease-in-out 1s;
}
}
.gf-timepicker-nav-btn {
transform: translate3d(40px, 0, 0);
transition: transform 1.5s ease-in-out 1s;
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment