ux: navigation work

This commit is contained in:
Torkel Ödegaard 2017-08-15 17:52:52 +02:00
parent db54416deb
commit 1eab771231
21 changed files with 180 additions and 143 deletions

View File

@ -7,7 +7,7 @@ type IndexViewData struct {
AppSubUrl string
GoogleAnalyticsId string
GoogleTagManagerId string
MainNavLinks []*NavLink
NavTree []*NavLink
BuildVersion string
BuildCommit string
NewGrafanaVersionExists bool
@ -20,10 +20,12 @@ type PluginCss struct {
}
type NavLink struct {
Text string `json:"text,omitempty"`
Icon string `json:"icon,omitempty"`
Img string `json:"img,omitempty"`
Url string `json:"url,omitempty"`
Divider bool `json:"divider,omitempty"`
Children []*NavLink `json:"children,omitempty"`
Id string `json:"id,omitempty"`
Text string `json:"text,omitempty"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
Img string `json:"img,omitempty"`
Url string `json:"url,omitempty"`
Divider bool `json:"divider,omitempty"`
Children []*NavLink `json:"children,omitempty"`
}

View File

@ -85,7 +85,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
}
if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "New",
Icon: "fa fa-fw fa-plus",
Url: "",
@ -103,7 +103,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
{Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots", Icon: "icon-gf icon-gf-snapshot"},
}
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Dashboards",
Icon: "icon-gf icon-gf-dashboard",
Url: setting.AppSubUrl + "/",
@ -116,7 +116,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
{Text: "Notification channels", Url: setting.AppSubUrl + "/alerting/notifications", Icon: "fa fa-fw fa-bell-o"},
}
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
data.NavTree = append(data.NavTree, &dtos.NavLink{
Text: "Alerting",
Icon: "icon-gf icon-gf-alert",
Url: setting.AppSubUrl + "/alerting/list",
@ -165,36 +165,61 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
}
if len(appLink.Children) > 0 {
data.MainNavLinks = append(data.MainNavLinks, appLink)
data.NavTree = append(data.NavTree, appLink)
}
}
}
if c.OrgRole == m.ROLE_ADMIN {
cfgNode := &dtos.NavLink{
Id: "cfg",
Text: "Configuration",
Icon: "fa fa-fw fa-cogs",
Url: setting.AppSubUrl + "/configuration",
Children: []*dtos.NavLink{
{
Text: "Data Sources",
Icon: "icon-gf icon-gf-datasources",
Url: setting.AppSubUrl + "/datasources",
Text: "Data Sources",
Icon: "icon-gf icon-gf-datasources",
Description: "Add and configure data sources",
Id: "datasources",
Url: setting.AppSubUrl + "/datasources",
Children: []*dtos.NavLink{
{Text: "List", Url: setting.AppSubUrl + "/datasources", Icon: "icon-gf icon-gf-datasources"},
{Text: "New", Url: setting.AppSubUrl + "/datasources", Icon: "fa fa-fw fa-plus"},
},
},
{
Text: "Plugins",
Icon: "icon-gf icon-gf-apps",
Url: setting.AppSubUrl + "/plugins",
Text: "Preferences",
Id: "org",
Description: "Organization preferences",
Icon: "fa fa-fw fa-sliders",
Url: setting.AppSubUrl + "/org",
},
{
Text: "Plugins",
Id: "plugins",
Description: "View and configure plugins",
Icon: "icon-gf icon-gf-apps",
Url: setting.AppSubUrl + "/plugins",
Children: []*dtos.NavLink{
{Text: "Panels", Url: setting.AppSubUrl + "/plugins?type=panel", Icon: "fa fa-fw fa-stop"},
{Text: "Data sources", Url: setting.AppSubUrl + "/plugins?type=datasource", Icon: "icon-gf icon-gf-datasources"},
{Text: "Apps", Url: setting.AppSubUrl + "/plugins?type=app", Icon: "icon-gf icon-gf-apps"},
},
},
{
Text: "User Management",
Id: "users",
Description: "Manage users & user groups",
Icon: "fa fa-fw fa-users",
Url: setting.AppSubUrl + "/org/users",
},
{
Text: "API Keys",
Description: "Create & manage API keys",
Icon: "fa fa-fw fa-key",
Url: setting.AppSubUrl + "/org/apikeys",
},
},
}
@ -212,7 +237,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
})
}
data.MainNavLinks = append(data.MainNavLinks, cfgNode)
data.NavTree = append(data.NavTree, cfgNode)
}
return &data, nil

View File

@ -1,20 +1,20 @@
<div class="page-nav">
<div class="container">
<div class="page-breadcrumb">
<div class="page-breadcrumb__item dropdown" ng-repeat="item in ctrl.model.items">
<a class="pointer" ng-href="{{::item.url}}" data-toggle="dropdown" ng-if="::item.items">
{{::item.title}}
<i class="page-breadcrumb__caret fa fa-caret-down"></i>
</a>
<div class="page-breadcrumb__item dropdown" ng-repeat="item in ctrl.model.breadcrumbs">
<!-- <a class="pointer" ng&#45;href="{{::item.url}}" data&#45;toggle="dropdown" ng&#45;if="::item.children"> -->
<!-- {{::item.text}} -->
<!-- <i class="page&#45;breadcrumb__caret fa fa&#45;caret&#45;down"></i> -->
<!-- </a> -->
<a class="pointer" ng-href="{{::item.url}}" ng-if="::!item.items">
{{::item.title}}
<a class="pointer" ng-href="{{::item.url}}">
{{::item.text}}
</a>
<ul class="dropdown-menu dropdown-menu--navbar">
<li ng-repeat="subItem in ::item.items">
<li ng-repeat="subItem in ::item.children">
<a class="pointer" ng-href="{{::subItem.url}}" ng-click="ctrl.navItemClicked(subItem, $event)">
{{::subItem.title}}
{{::subItem.text}}
</a>
</li>
</ul>

View File

@ -11,6 +11,7 @@ export class NavbarCtrl {
/** @ngInject */
constructor(private $scope, private $rootScope, private contextSrv) {
console.log(this.model);
}
showSearch() {

View File

@ -1,4 +1,3 @@
<div class="sidemenu__top">
<a class="navbar-brand-btn pointer" ng-click="ctrl.contextSrv.toggleSideMenu()">

View File

@ -26,7 +26,7 @@ export class SideMenuCtrl {
this.showSignout = this.contextSrv.isSignedIn && !config['disableSignoutMenu'];
this.maxShownOrgs = 10;
this.mainLinks = config.bootData.mainNavLinks;
this.mainLinks = config.bootData.navTree;
this.openUserDropdown();
this.loginUrl = 'login?redirect=' + encodeURIComponent(this.$location.path());

View File

@ -1,6 +1,8 @@
///<reference path="../headers/common.d.ts" />
import coreModule from 'app/core/core_module';
import config from 'app/core/config';
import _ from 'lodash';
export interface NavModelItem {
title: string;
@ -15,10 +17,24 @@ export interface NavModel {
}
export class NavModelSrv {
navItems: any;
/** @ngInject */
constructor(private contextSrv) {
this.navItems = config.bootData.navTree;
}
getCfgNode() {
return _.find(this.navItems, {id: 'cfg'});
}
getConfigurationNav() {
let cfg = this.getCfgNode();
return {
breadcrumbs: [cfg],
node: cfg,
};
}
getAlertingNav(subPage) {
@ -36,21 +52,11 @@ export class NavModelSrv {
}
getDatasourceNav(subPage) {
let cfg = this.getCfgNode();
let ds = _.find(cfg.children, {id: 'datasources'});
return {
items: [
{
title: 'Configuration',
items: [
{title: 'Data sources', active: subPage === 0, url: 'datasources', icon: 'fa fa-database'},
{title: 'Users', active: subPage === 0, url: 'users', icon: 'fa fa-fw fa-users'},
{title: 'Plugins', active: subPage === 0, url: 'plugins', icon: 'icon-gf icon-gf-apps'},
]
},
{
title: 'Data sources',
url: 'datasources',
}
]
breadcrumbs: [cfg, ds],
node: ds
};
}
@ -91,18 +97,11 @@ export class NavModelSrv {
}
getOrgNav(subPage) {
let cfg = this.getCfgNode();
let org = _.find(cfg.children, {id: 'org'});
return {
section: {
title: 'Organization',
url: 'org',
icon: 'icon-gf icon-gf-users'
},
menu: [
{title: 'Preferences', active: subPage === 0, url: 'org', icon: 'fa fa-fw fa-cog'},
{title: 'Org Users', active: subPage === 1, url: 'org/users', icon: 'fa fa-fw fa-users'},
{title: 'API Keys', active: subPage === 2, url: 'org/apikeys', icon: 'fa fa-fw fa-key'},
{title: 'Org User Groups', active: subPage === 3, url: 'org/user-groups', icon: 'fa fa-fw fa-users'},
]
breadcrumbs: [this.getCfgNode(), org],
node: org
};
}
@ -124,13 +123,20 @@ export class NavModelSrv {
}
getPluginsNav() {
let cfg = this.getCfgNode();
let plugins = _.find(cfg.children, {id: 'plugins'});
return {
section: {
title: 'Plugins',
url: 'plugins',
icon: 'icon-gf icon-gf-apps'
},
menu: []
breadcrumbs: [this.getCfgNode(), plugins],
node: plugins
};
}
getUserManNav() {
let cfg = this.getCfgNode();
let users = _.find(cfg.children, {id: 'users'});
return {
breadcrumbs: [cfg, users],
node: users,
};
}

View File

@ -47,7 +47,7 @@ export class ConfigurationHomeCtrl {
/** @ngInject */
constructor(private $scope, private backendSrv, private navModelSrv) {
this.navModel = navModelSrv.getAdminNav();
this.navModel = navModelSrv.getConfigurationNav();
}
}

View File

@ -4,35 +4,30 @@
<div class="page-container">
<div class="page-header">
<h1>
<i class="fa fa-cogs"></i>
<i class="fa fa-fw fa-cogs"></i>
<span>Configuration</span>
</h1>
</div>
<section class="card-section" layout-mode>
<layout-selector></layout-selector>
<section class="card-section card-list-layout-grid">
<ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">
<a class="card-item" href="datasources/edit/{{ds.id}}/">
<li class="card-item-wrapper" ng-repeat="navItem in ctrl.navModel.node.children">
<a class="card-item" ng-href="{{::navItem.url}}">
<div class="card-item-header">
<div class="card-item-type">
{{ds.type}}
</div>
</div>
<div class="card-item-body">
<figure class="card-item-figure">
<img ng-src="{{ds.typeLogoUrl}}">
<i class="{{navItem.icon}}"></i>
</figure>
<div class="card-item-details">
<div class="card-item-name">
{{ds.name}}
<span ng-if="ds.isDefault">
<span class="btn btn-secondary btn-mini">default</span>
</span>
{{navItem.text}}
</div>
<div class="card-item-sub-name">
{{ds.url}}
{{navItem.description}}
</div>
</div>
</div>
@ -40,9 +35,5 @@
</li>
</ol>
</section>
<div ng-if="ctrl.datasources.length === 0">
<em>No data sources defined</em>
</div>
</div>
</div>

View File

@ -23,7 +23,8 @@ export class OrgUsersCtrl {
loginOrEmail: '',
role: 'Viewer',
};
this.navModel = navModelSrv.getOrgNav(0);
this.navModel = navModelSrv.getUserManNav(0);
this.get();
this.editor = { index: 0 };

View File

@ -2,14 +2,17 @@
<div class="page-container">
<div class="page-header">
<h1>Org Preferences</h1>
<h1>
<i class="{{navModel.node.icon}}"></i>
{{navModel.node.text}}
</h1>
</div>
<h3 class="page-heading">General</h3>
<form name="orgForm" class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form max-width-28">
<span class="gf-form-label width-6">Name</span>
<span class="gf-form-label">Organization name</span>
<input class="gf-form-input" type="text" required ng-model="org.name">
</div>
<div class="gf-form">
@ -61,16 +64,6 @@
</div>
</form>
<h3 class="page-heading">Admin Pages</h3>
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<a href="org/users" class="btn gf-form-btn btn-inverse">Users &amp; Roles</a>
</div>
<div class="gf-form">
<a href="org/apikeys" class="btn gf-form-btn btn-inverse">API Keys</a>
</div>
</div>
</div>

View File

@ -2,7 +2,10 @@
<div class="page-container">
<div class="page-header">
<h1>Organization users</h1>
<h1>
<i class="{{ctrl.navModel.node.icon}}"></i>
{{ctrl.navModel.node.text}}
</h1>
<div class="page-header-tabs">
@ -22,8 +25,13 @@
Users ({{ctrl.users.length}})
</a>
</li>
<li class="gf-tabs-item">
<a class="gf-tabs-link" ng-click="ctrl.editor.index = 0" ng-class="{active: ctrl.editor.index === 1}">
Groups (0)
</a>
</li>
<li class="gf-tabs-item" ng-show="ctrl.showInviteUI">
<a class="gf-tabs-link" ng-click="ctrl.editor.index = 1" ng-class="{active: ctrl.editor.index === 1}">
<a class="gf-tabs-link" ng-click="ctrl.editor.index = 1" ng-class="{active: ctrl.editor.index === 2}">
Pending Invitations ({{ctrl.pendingInvites.length}})
</a>
</li>
@ -54,8 +62,10 @@
<td><span class="ellipsis">{{user.email}}</span></td>
<td>{{user.lastSeenAtAge}}</td>
<td>
<select type="text" ng-model="user.role" class="input-medium" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
</select>
<div class="gf-form-select-wrapper width-9">
<select type="text" ng-model="user.role" class="gf-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="ctrl.updateOrgUser(user)">
</select>
</div>
</td>
<td>
<a ng-click="ctrl.removeUser(user)" class="btn btn-danger btn-mini">

View File

@ -59,7 +59,7 @@ export class DataSourceEditCtrl {
initNewDatasourceModel() {
this.isNew = true;
this.current = angular.copy(defaults);
this.navModel.items.push({title: 'New data source'});
this.navModel.breadcrumbs.push({text: 'New data source'});
// We are coming from getting started
if (this.$location.search().gettingstarted) {
@ -86,7 +86,7 @@ export class DataSourceEditCtrl {
this.backendSrv.get('/api/datasources/' + id).then(ds => {
this.isNew = false;
this.current = ds;
this.navModel.items.push({title: ds.name});
this.navModel.breadcrumbs.push({text: ds.name});
if (datasourceCreated) {
datasourceCreated = false;

View File

@ -4,7 +4,7 @@
<div class="page-container">
<div class="page-header">
<h1>
<i class="icon-gf icon-gf-datasources"></i>
<i class="icon-gf icon-gf-fw icon-gf-datasources"></i>
<span>Data Sources</span>
</h1>
@ -15,7 +15,6 @@
</div>
<section class="card-section" layout-mode>
<layout-selector></layout-selector>
<ol class="card-list" >
<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">

View File

@ -42,6 +42,7 @@ export class PluginEditCtrl {
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);

View File

@ -18,14 +18,20 @@
font-variant: normal;
text-transform: none;
line-height: 1;
display: inline-block;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-gf-fw {
width: 1.2857142857em;
text-align: center;
}
.inline-icon-gf {
vertical-align: middle;
vertical-align: middle;
}
.icon-gf-raintank_wordmark:before {
@ -145,7 +151,7 @@
}
.icon-gf-grabber:before {
content: "\e90b";
}
}
.icon-gf-users:before {
content: "\e622";
}

View File

@ -97,7 +97,7 @@
color: $text-color-weak;
text-transform: uppercase;
font-size: $font-size-sm;
font-weight: bold;
font-weight: $font-weight-semi-bold;
}
.card-item-notice {
@ -158,6 +158,9 @@
img {
width: 6rem;
}
.fa, .icon-gf {
font-size: 3.5rem;
}
}
.card-item-name {

View File

@ -194,7 +194,7 @@ $gf-form-margin: 0.1rem;
select.gf-form-input {
text-indent: .01px;
text-overflow: '';
padding-right: $input-padding-x*2;
padding-right: $input-padding-x*4;
-webkit-appearance: none;
-moz-appearance: menulist-text; // was set to "window" and caused odd display on windos and linux.
appearance: none;

View File

@ -110,39 +110,39 @@
// Srollbars
//
// ::-webkit-scrollbar {
// width: 8px;
// height: 8px;
// }
//
// ::-webkit-scrollbar:hover {
// height: 8px;
// }
//
// ::-webkit-scrollbar-button:start:decrement,
// ::-webkit-scrollbar-button:end:increment { display: none; }
// ::-webkit-scrollbar-button:horizontal:decrement { display: none; }
// ::-webkit-scrollbar-button:horizontal:increment { display: none; }
// ::-webkit-scrollbar-button:vertical:decrement { display: none; }
// ::-webkit-scrollbar-button:vertical:increment { display: none; }
// ::-webkit-scrollbar-button:horizontal:decrement:active { background-image: none; }
// ::-webkit-scrollbar-button:horizontal:increment:active { background-image: none; }
// ::-webkit-scrollbar-button:vertical:decrement:active { background-image: none; }
// ::-webkit-scrollbar-button:vertical:increment:active {background-image: none; }
// ::-webkit-scrollbar-track-piece { background-color: transparent; }
//
// ::-webkit-scrollbar-thumb:vertical {
// height: 50px;
// background: -webkit-gradient(linear, left top, right top, color-stop(0%, $scrollbarBackground), color-stop(100%, $scrollbarBackground2));
// border: 1px solid $scrollbarBorder;
// border-top: 1px solid $scrollbarBorder;
// border-left: 1px solid $scrollbarBorder;
// }
//
// ::-webkit-scrollbar-thumb:horizontal {
// width: 50px;
// background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, $scrollbarBackground), color-stop(100%, $scrollbarBackground2));
// border: 1px solid $scrollbarBorder;
// border-top: 1px solid $scrollbarBorder;
// border-left: 1px solid $scrollbarBorder;
// }
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar:hover {
height: 8px;
}
::-webkit-scrollbar-button:start:decrement,
::-webkit-scrollbar-button:end:increment { display: none; }
::-webkit-scrollbar-button:horizontal:decrement { display: none; }
::-webkit-scrollbar-button:horizontal:increment { display: none; }
::-webkit-scrollbar-button:vertical:decrement { display: none; }
::-webkit-scrollbar-button:vertical:increment { display: none; }
::-webkit-scrollbar-button:horizontal:decrement:active { background-image: none; }
::-webkit-scrollbar-button:horizontal:increment:active { background-image: none; }
::-webkit-scrollbar-button:vertical:decrement:active { background-image: none; }
::-webkit-scrollbar-button:vertical:increment:active {background-image: none; }
::-webkit-scrollbar-track-piece { background-color: transparent; }
::-webkit-scrollbar-thumb:vertical {
height: 50px;
background: -webkit-gradient(linear, left top, right top, color-stop(0%, $scrollbarBackground), color-stop(100%, $scrollbarBackground2));
border: 1px solid $scrollbarBorder;
border-top: 1px solid $scrollbarBorder;
border-left: 1px solid $scrollbarBorder;
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, $scrollbarBackground), color-stop(100%, $scrollbarBackground2));
border: 1px solid $scrollbarBorder;
border-top: 1px solid $scrollbarBorder;
border-left: 1px solid $scrollbarBorder;
}

View File

@ -61,12 +61,12 @@
margin-bottom: $spacer*1.5;
}
a, button {
float: right;
margin-left: $spacer;
}
}
.page-header__cta {
float: right;
margin-left: $spacer;
}
.page-heading {
font-size: 1.25rem;

View File

@ -82,7 +82,7 @@
window.grafanaBootData = {
user:[[.User]],
settings: [[.Settings]],
mainNavLinks: [[.MainNavLinks]]
navTree: [[.NavTree]]
};
</script>