mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #13878 from grafana/org-page-to-react
Org page to react
This commit is contained in:
@@ -9,3 +9,6 @@ import './admin';
|
||||
import './alerting/NotificationsEditCtrl';
|
||||
import './alerting/NotificationsListCtrl';
|
||||
import './manage-dashboards';
|
||||
import './teams/CreateTeamCtrl';
|
||||
import './profile/ProfileCtrl';
|
||||
import './profile/ChangePasswordCtrl';
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadDashboardPermissions = 'LOAD_DASHBOARD_PERMISSIONS',
|
||||
LoadStarredDashboards = 'LOAD_STARRED_DASHBOARDS',
|
||||
}
|
||||
|
||||
export interface LoadDashboardPermissionsAction {
|
||||
@@ -20,7 +21,12 @@ export interface LoadDashboardPermissionsAction {
|
||||
payload: DashboardAcl[];
|
||||
}
|
||||
|
||||
export type Action = LoadDashboardPermissionsAction;
|
||||
export interface LoadStarredDashboardsAction {
|
||||
type: ActionTypes.LoadStarredDashboards;
|
||||
payload: DashboardAcl[];
|
||||
}
|
||||
|
||||
export type Action = LoadDashboardPermissionsAction | LoadStarredDashboardsAction;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Action, ActionTypes } from './actions';
|
||||
import { OrgRole, PermissionLevel, DashboardState } from 'app/types';
|
||||
import { inititalState, dashboardReducer } from './reducers';
|
||||
import { initialState, dashboardReducer } from './reducers';
|
||||
|
||||
describe('dashboard reducer', () => {
|
||||
describe('loadDashboardPermissions', () => {
|
||||
@@ -14,7 +14,7 @@ describe('dashboard reducer', () => {
|
||||
{ id: 3, dashboardId: 1, role: OrgRole.Editor, permission: PermissionLevel.Edit },
|
||||
],
|
||||
};
|
||||
state = dashboardReducer(inititalState, action);
|
||||
state = dashboardReducer(initialState, action);
|
||||
});
|
||||
|
||||
it('should add permissions to state', async () => {
|
||||
|
||||
@@ -2,11 +2,11 @@ import { DashboardState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
import { processAclItems } from 'app/core/utils/acl';
|
||||
|
||||
export const inititalState: DashboardState = {
|
||||
export const initialState: DashboardState = {
|
||||
permissions: [],
|
||||
};
|
||||
|
||||
export const dashboardReducer = (state = inititalState, action: Action): DashboardState => {
|
||||
export const dashboardReducer = (state = initialState, action: Action): DashboardState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadDashboardPermissions:
|
||||
return {
|
||||
|
||||
45
public/app/features/org/OrgDetailsPage.test.tsx
Normal file
45
public/app/features/org/OrgDetailsPage.test.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OrgDetailsPage, Props } from './OrgDetailsPage';
|
||||
import { NavModel, Organization, OrganizationPreferences } from '../../types';
|
||||
|
||||
const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
preferences: {} as OrganizationPreferences,
|
||||
organization: {} as Organization,
|
||||
navModel: {} as NavModel,
|
||||
loadOrganization: jest.fn(),
|
||||
loadOrganizationPreferences: jest.fn(),
|
||||
loadStarredDashboards: jest.fn(),
|
||||
setOrganizationName: jest.fn(),
|
||||
updateOrganization: jest.fn(),
|
||||
};
|
||||
|
||||
Object.assign(props, propOverrides);
|
||||
|
||||
return shallow(<OrgDetailsPage {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render organization and preferences', () => {
|
||||
const wrapper = setup({
|
||||
organization: {
|
||||
name: 'Cool org',
|
||||
id: 1,
|
||||
},
|
||||
preferences: {
|
||||
homeDashboardId: 1,
|
||||
theme: 'Default',
|
||||
timezone: 'Default',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
85
public/app/features/org/OrgDetailsPage.tsx
Normal file
85
public/app/features/org/OrgDetailsPage.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import PageHeader from '../../core/components/PageHeader/PageHeader';
|
||||
import PageLoader from '../../core/components/PageLoader/PageLoader';
|
||||
import OrgProfile from './OrgProfile';
|
||||
import OrgPreferences from './OrgPreferences';
|
||||
import {
|
||||
loadOrganization,
|
||||
loadOrganizationPreferences,
|
||||
setOrganizationName,
|
||||
updateOrganization,
|
||||
} from './state/actions';
|
||||
import { loadStarredDashboards } from '../../core/actions/user';
|
||||
import { NavModel, Organization, OrganizationPreferences, StoreState } from 'app/types';
|
||||
import { getNavModel } from '../../core/selectors/navModel';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
organization: Organization;
|
||||
preferences: OrganizationPreferences;
|
||||
loadOrganization: typeof loadOrganization;
|
||||
loadOrganizationPreferences: typeof loadOrganizationPreferences;
|
||||
loadStarredDashboards: typeof loadStarredDashboards;
|
||||
setOrganizationName: typeof setOrganizationName;
|
||||
updateOrganization: typeof updateOrganization;
|
||||
}
|
||||
|
||||
export class OrgDetailsPage extends PureComponent<Props> {
|
||||
async componentDidMount() {
|
||||
await this.props.loadStarredDashboards();
|
||||
await this.props.loadOrganization();
|
||||
await this.props.loadOrganizationPreferences();
|
||||
}
|
||||
|
||||
onOrgNameChange = name => {
|
||||
this.props.setOrganizationName(name);
|
||||
};
|
||||
|
||||
onUpdateOrganization = () => {
|
||||
this.props.updateOrganization();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navModel, organization, preferences } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
<div className="page-container page-body">
|
||||
{Object.keys(organization).length === 0 || Object.keys(preferences).length === 0 ? (
|
||||
<PageLoader pageName="Organization" />
|
||||
) : (
|
||||
<div>
|
||||
<OrgProfile
|
||||
onOrgNameChange={name => this.onOrgNameChange(name)}
|
||||
onSubmit={this.onUpdateOrganization}
|
||||
orgName={organization.name}
|
||||
/>
|
||||
<OrgPreferences />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: StoreState) {
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, 'org-settings'),
|
||||
organization: state.organization.organization,
|
||||
preferences: state.organization.preferences,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
loadOrganization,
|
||||
loadOrganizationPreferences,
|
||||
loadStarredDashboards,
|
||||
setOrganizationName,
|
||||
updateOrganization,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(OrgDetailsPage));
|
||||
28
public/app/features/org/OrgPreferences.test.tsx
Normal file
28
public/app/features/org/OrgPreferences.test.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { OrgPreferences, Props } from './OrgPreferences';
|
||||
|
||||
const setup = () => {
|
||||
const props: Props = {
|
||||
preferences: {
|
||||
homeDashboardId: 1,
|
||||
timezone: 'UTC',
|
||||
theme: 'Default',
|
||||
},
|
||||
starredDashboards: [{ id: 1, title: 'Standard dashboard', url: '', uri: '', uid: '', type: '', tags: [] }],
|
||||
setOrganizationTimezone: jest.fn(),
|
||||
setOrganizationTheme: jest.fn(),
|
||||
setOrganizationHomeDashboard: jest.fn(),
|
||||
updateOrganizationPreferences: jest.fn(),
|
||||
};
|
||||
|
||||
return shallow(<OrgPreferences {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
113
public/app/features/org/OrgPreferences.tsx
Normal file
113
public/app/features/org/OrgPreferences.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Label } from '../../core/components/Label/Label';
|
||||
import SimplePicker from '../../core/components/Picker/SimplePicker';
|
||||
import { DashboardSearchHit, OrganizationPreferences } from 'app/types';
|
||||
import {
|
||||
setOrganizationHomeDashboard,
|
||||
setOrganizationTheme,
|
||||
setOrganizationTimezone,
|
||||
updateOrganizationPreferences,
|
||||
} from './state/actions';
|
||||
|
||||
export interface Props {
|
||||
preferences: OrganizationPreferences;
|
||||
starredDashboards: DashboardSearchHit[];
|
||||
setOrganizationHomeDashboard: typeof setOrganizationHomeDashboard;
|
||||
setOrganizationTheme: typeof setOrganizationTheme;
|
||||
setOrganizationTimezone: typeof setOrganizationTimezone;
|
||||
updateOrganizationPreferences: typeof updateOrganizationPreferences;
|
||||
}
|
||||
|
||||
const themes = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
|
||||
|
||||
const timezones = [
|
||||
{ value: '', text: 'Default' },
|
||||
{ value: 'browser', text: 'Local browser time' },
|
||||
{ value: 'utc', text: 'UTC' },
|
||||
];
|
||||
|
||||
export class OrgPreferences extends PureComponent<Props> {
|
||||
onSubmitForm = event => {
|
||||
event.preventDefault();
|
||||
this.props.updateOrganizationPreferences();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
preferences,
|
||||
starredDashboards,
|
||||
setOrganizationHomeDashboard,
|
||||
setOrganizationTimezone,
|
||||
setOrganizationTheme,
|
||||
} = this.props;
|
||||
|
||||
starredDashboards.unshift({ id: 0, title: 'Default', tags: [], type: '', uid: '', uri: '', url: '' });
|
||||
|
||||
return (
|
||||
<form className="section gf-form-group" onSubmit={this.onSubmitForm}>
|
||||
<h3 className="page-heading">Preferences</h3>
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-11">UI Theme</span>
|
||||
<SimplePicker
|
||||
defaultValue={themes.find(theme => theme.value === preferences.theme)}
|
||||
options={themes}
|
||||
getOptionValue={i => i.value}
|
||||
getOptionLabel={i => i.text}
|
||||
onSelected={theme => setOrganizationTheme(theme.value)}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<Label
|
||||
width={11}
|
||||
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
|
||||
>
|
||||
Home Dashboard
|
||||
</Label>
|
||||
<SimplePicker
|
||||
defaultValue={starredDashboards.find(dashboard => dashboard.id === preferences.homeDashboardId)}
|
||||
getOptionValue={i => i.id}
|
||||
getOptionLabel={i => i.title}
|
||||
onSelected={(dashboard: DashboardSearchHit) => setOrganizationHomeDashboard(dashboard.id)}
|
||||
options={starredDashboards}
|
||||
placeholder="Chose default dashboard"
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label width-11">Timezone</label>
|
||||
<SimplePicker
|
||||
defaultValue={timezones.find(timezone => timezone.value === preferences.timezone)}
|
||||
getOptionValue={i => i.value}
|
||||
getOptionLabel={i => i.text}
|
||||
onSelected={timezone => setOrganizationTimezone(timezone.value)}
|
||||
options={timezones}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
<button type="submit" className="btn btn-success">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
preferences: state.organization.preferences,
|
||||
starredDashboards: state.user.starredDashboards,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setOrganizationHomeDashboard,
|
||||
setOrganizationTimezone,
|
||||
setOrganizationTheme,
|
||||
updateOrganizationPreferences,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OrgPreferences);
|
||||
21
public/app/features/org/OrgProfile.test.tsx
Normal file
21
public/app/features/org/OrgProfile.test.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import OrgProfile, { Props } from './OrgProfile';
|
||||
|
||||
const setup = () => {
|
||||
const props: Props = {
|
||||
orgName: 'Main org',
|
||||
onSubmit: jest.fn(),
|
||||
onOrgNameChange: jest.fn(),
|
||||
};
|
||||
|
||||
return shallow(<OrgProfile {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
44
public/app/features/org/OrgProfile.tsx
Normal file
44
public/app/features/org/OrgProfile.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { SFC } from 'react';
|
||||
|
||||
export interface Props {
|
||||
orgName: string;
|
||||
onSubmit: () => void;
|
||||
onOrgNameChange: (orgName: string) => void;
|
||||
}
|
||||
|
||||
const OrgProfile: SFC<Props> = ({ onSubmit, onOrgNameChange, orgName }) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="page-sub-heading">Organization profile</h3>
|
||||
<form
|
||||
name="orgForm"
|
||||
className="gf-form-group"
|
||||
onSubmit={event => {
|
||||
event.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form max-width-28">
|
||||
<span className="gf-form-label">Organization name</span>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
type="text"
|
||||
onChange={event => {
|
||||
onOrgNameChange(event.target.value);
|
||||
}}
|
||||
value={orgName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
<button type="submit" className="btn btn-success">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgProfile;
|
||||
@@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div>
|
||||
<PageHeader
|
||||
model={Object {}}
|
||||
/>
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<PageLoader
|
||||
pageName="Organization"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render organization and preferences 1`] = `
|
||||
<div>
|
||||
<PageHeader
|
||||
model={Object {}}
|
||||
/>
|
||||
<div
|
||||
className="page-container page-body"
|
||||
>
|
||||
<div>
|
||||
<OrgProfile
|
||||
onOrgNameChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
orgName="Cool org"
|
||||
/>
|
||||
<Connect(OrgPreferences) />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,136 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<form
|
||||
className="section gf-form-group"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<h3
|
||||
className="page-heading"
|
||||
>
|
||||
Preferences
|
||||
</h3>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<span
|
||||
className="gf-form-label width-11"
|
||||
>
|
||||
UI Theme
|
||||
</span>
|
||||
<SimplePicker
|
||||
getOptionLabel={[Function]}
|
||||
getOptionValue={[Function]}
|
||||
onSelected={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"text": "Default",
|
||||
"value": "",
|
||||
},
|
||||
Object {
|
||||
"text": "Dark",
|
||||
"value": "dark",
|
||||
},
|
||||
Object {
|
||||
"text": "Light",
|
||||
"value": "light",
|
||||
},
|
||||
]
|
||||
}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<Component
|
||||
tooltip="Not finding dashboard you want? Star it first, then it should appear in this select box."
|
||||
width={11}
|
||||
>
|
||||
Home Dashboard
|
||||
</Component>
|
||||
<SimplePicker
|
||||
defaultValue={
|
||||
Object {
|
||||
"id": 1,
|
||||
"tags": Array [],
|
||||
"title": "Standard dashboard",
|
||||
"type": "",
|
||||
"uid": "",
|
||||
"uri": "",
|
||||
"url": "",
|
||||
}
|
||||
}
|
||||
getOptionLabel={[Function]}
|
||||
getOptionValue={[Function]}
|
||||
onSelected={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"id": 0,
|
||||
"tags": Array [],
|
||||
"title": "Default",
|
||||
"type": "",
|
||||
"uid": "",
|
||||
"uri": "",
|
||||
"url": "",
|
||||
},
|
||||
Object {
|
||||
"id": 1,
|
||||
"tags": Array [],
|
||||
"title": "Standard dashboard",
|
||||
"type": "",
|
||||
"uid": "",
|
||||
"uri": "",
|
||||
"url": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
placeholder="Chose default dashboard"
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<label
|
||||
className="gf-form-label width-11"
|
||||
>
|
||||
Timezone
|
||||
</label>
|
||||
<SimplePicker
|
||||
getOptionLabel={[Function]}
|
||||
getOptionValue={[Function]}
|
||||
onSelected={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"text": "Default",
|
||||
"value": "",
|
||||
},
|
||||
Object {
|
||||
"text": "Local browser time",
|
||||
"value": "browser",
|
||||
},
|
||||
Object {
|
||||
"text": "UTC",
|
||||
"value": "utc",
|
||||
},
|
||||
]
|
||||
}
|
||||
width={20}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-button-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
@@ -0,0 +1,46 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div>
|
||||
<h3
|
||||
className="page-sub-heading"
|
||||
>
|
||||
Organization profile
|
||||
</h3>
|
||||
<form
|
||||
className="gf-form-group"
|
||||
name="orgForm"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form max-width-28"
|
||||
>
|
||||
<span
|
||||
className="gf-form-label"
|
||||
>
|
||||
Organization name
|
||||
</span>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="Main org"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-button-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,8 +1,3 @@
|
||||
import './profile_ctrl';
|
||||
import './select_org_ctrl';
|
||||
import './change_password_ctrl';
|
||||
import './new_org_ctrl';
|
||||
import './user_invite_ctrl';
|
||||
import './create_team_ctrl';
|
||||
import './org_details_ctrl';
|
||||
import './prefs_control';
|
||||
import './SelectOrgCtrl';
|
||||
import './NewOrgCtrl';
|
||||
import './UserInviteCtrl';
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import angular from 'angular';
|
||||
|
||||
export class OrgDetailsCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope, $http, backendSrv, contextSrv, navModelSrv) {
|
||||
$scope.init = () => {
|
||||
$scope.getOrgInfo();
|
||||
$scope.navModel = navModelSrv.getNav('cfg', 'org-settings', 0);
|
||||
};
|
||||
|
||||
$scope.getOrgInfo = () => {
|
||||
backendSrv.get('/api/org').then(org => {
|
||||
$scope.org = org;
|
||||
$scope.address = org.address;
|
||||
contextSrv.user.orgName = org.name;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.update = () => {
|
||||
if (!$scope.orgForm.$valid) {
|
||||
return;
|
||||
}
|
||||
const data = { name: $scope.org.name };
|
||||
backendSrv.put('/api/org', data).then($scope.getOrgInfo);
|
||||
};
|
||||
|
||||
$scope.updateAddress = () => {
|
||||
if (!$scope.addressForm.$valid) {
|
||||
return;
|
||||
}
|
||||
backendSrv.put('/api/org/address', $scope.address).then($scope.getOrgInfo);
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('OrgDetailsCtrl', OrgDetailsCtrl);
|
||||
@@ -1,21 +0,0 @@
|
||||
<page-header model="navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<h3 class="page-sub-heading">Organization profile</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">Organization name</span>
|
||||
<input class="gf-form-input" type="text" required ng-model="org.name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-success" ng-click="update()">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
<prefs-control mode="org"></prefs-control>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import config from 'app/core/config';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
export class PrefsControlCtrl {
|
||||
prefs: any;
|
||||
oldTheme: any;
|
||||
prefsForm: any;
|
||||
mode: string;
|
||||
|
||||
timezones: any = [
|
||||
{ value: '', text: 'Default' },
|
||||
{ value: 'browser', text: 'Local browser time' },
|
||||
{ value: 'utc', text: 'UTC' },
|
||||
];
|
||||
themes: any = [{ value: '', text: 'Default' }, { value: 'dark', text: 'Dark' }, { value: 'light', text: 'Light' }];
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $location) {}
|
||||
|
||||
$onInit() {
|
||||
return this.backendSrv.get(`/api/${this.mode}/preferences`).then(prefs => {
|
||||
this.prefs = prefs;
|
||||
this.oldTheme = prefs.theme;
|
||||
});
|
||||
}
|
||||
|
||||
updatePrefs() {
|
||||
if (!this.prefsForm.$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cmd = {
|
||||
theme: this.prefs.theme,
|
||||
timezone: this.prefs.timezone,
|
||||
homeDashboardId: this.prefs.homeDashboardId,
|
||||
};
|
||||
|
||||
this.backendSrv.put(`/api/${this.mode}/preferences`, cmd).then(() => {
|
||||
window.location.href = config.appSubUrl + this.$location.path();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const template = `
|
||||
<form name="ctrl.prefsForm" class="section gf-form-group">
|
||||
<h3 class="page-heading">Preferences</h3>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-11">UI Theme</span>
|
||||
<div class="gf-form-select-wrapper max-width-20">
|
||||
<select class="gf-form-input" ng-model="ctrl.prefs.theme" ng-options="f.value as f.text for f in ctrl.themes"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-11">
|
||||
Home Dashboard
|
||||
<info-popover mode="right-normal">
|
||||
Not finding dashboard you want? Star it first, then it should appear in this select box.
|
||||
</info-popover>
|
||||
</span>
|
||||
<dashboard-selector class="gf-form-select-wrapper max-width-20" model="ctrl.prefs.homeDashboardId">
|
||||
</dashboard-selector>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-11">Timezone</label>
|
||||
<div class="gf-form-select-wrapper max-width-20">
|
||||
<select class="gf-form-input" ng-model="ctrl.prefs.timezone" ng-options="f.value as f.text for f in ctrl.timezones"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-success" ng-click="ctrl.updatePrefs()">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
export function prefsControlDirective() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controller: PrefsControlCtrl,
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
template: template,
|
||||
scope: {
|
||||
mode: '@',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
coreModule.directive('prefsControl', prefsControlDirective);
|
||||
118
public/app/features/org/state/actions.ts
Normal file
118
public/app/features/org/state/actions.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { Organization, OrganizationPreferences, StoreState } from 'app/types';
|
||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, any>;
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadOrganization = 'LOAD_ORGANISATION',
|
||||
LoadPreferences = 'LOAD_PREFERENCES',
|
||||
SetOrganizationName = 'SET_ORGANIZATION_NAME',
|
||||
SetOrganizationTheme = 'SET_ORGANIZATION_THEME',
|
||||
SetOrganizationHomeDashboard = 'SET_ORGANIZATION_HOME_DASHBOARD',
|
||||
SetOrganizationTimezone = 'SET_ORGANIZATION_TIMEZONE',
|
||||
}
|
||||
|
||||
interface LoadOrganizationAction {
|
||||
type: ActionTypes.LoadOrganization;
|
||||
payload: Organization;
|
||||
}
|
||||
|
||||
interface LoadPreferencesAction {
|
||||
type: ActionTypes.LoadPreferences;
|
||||
payload: OrganizationPreferences;
|
||||
}
|
||||
|
||||
interface SetOrganizationNameAction {
|
||||
type: ActionTypes.SetOrganizationName;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
interface SetOrganizationThemeAction {
|
||||
type: ActionTypes.SetOrganizationTheme;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
interface SetOrganizationHomeDashboardAction {
|
||||
type: ActionTypes.SetOrganizationHomeDashboard;
|
||||
payload: number;
|
||||
}
|
||||
|
||||
interface SetOrganizationTimezoneAction {
|
||||
type: ActionTypes.SetOrganizationTimezone;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
const organisationLoaded = (organisation: Organization) => ({
|
||||
type: ActionTypes.LoadOrganization,
|
||||
payload: organisation,
|
||||
});
|
||||
|
||||
const preferencesLoaded = (preferences: OrganizationPreferences) => ({
|
||||
type: ActionTypes.LoadPreferences,
|
||||
payload: preferences,
|
||||
});
|
||||
|
||||
export const setOrganizationName = (orgName: string) => ({
|
||||
type: ActionTypes.SetOrganizationName,
|
||||
payload: orgName,
|
||||
});
|
||||
|
||||
export const setOrganizationTheme = (theme: string) => ({
|
||||
type: ActionTypes.SetOrganizationTheme,
|
||||
payload: theme,
|
||||
});
|
||||
|
||||
export const setOrganizationHomeDashboard = (id: number) => ({
|
||||
type: ActionTypes.SetOrganizationHomeDashboard,
|
||||
payload: id,
|
||||
});
|
||||
|
||||
export const setOrganizationTimezone = (timezone: string) => ({
|
||||
type: ActionTypes.SetOrganizationTimezone,
|
||||
payload: timezone,
|
||||
});
|
||||
|
||||
export type Action =
|
||||
| LoadOrganizationAction
|
||||
| LoadPreferencesAction
|
||||
| SetOrganizationNameAction
|
||||
| SetOrganizationThemeAction
|
||||
| SetOrganizationHomeDashboardAction
|
||||
| SetOrganizationTimezoneAction;
|
||||
|
||||
export function loadOrganization(): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const organisationResponse = await getBackendSrv().get('/api/org');
|
||||
dispatch(organisationLoaded(organisationResponse));
|
||||
|
||||
return organisationResponse;
|
||||
};
|
||||
}
|
||||
|
||||
export function loadOrganizationPreferences(): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const preferencesResponse = await getBackendSrv().get('/api/org/preferences');
|
||||
dispatch(preferencesLoaded(preferencesResponse));
|
||||
};
|
||||
}
|
||||
|
||||
export function updateOrganization() {
|
||||
return async (dispatch, getStore) => {
|
||||
const organization = getStore().organization.organization;
|
||||
|
||||
await getBackendSrv().put('/api/org', { name: organization.name });
|
||||
|
||||
dispatch(loadOrganization());
|
||||
};
|
||||
}
|
||||
|
||||
export function updateOrganizationPreferences() {
|
||||
return async (dispatch, getStore) => {
|
||||
const preferences = getStore().organization.preferences;
|
||||
|
||||
await getBackendSrv().put('/api/org/preferences', preferences);
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
}
|
||||
35
public/app/features/org/state/reducers.ts
Normal file
35
public/app/features/org/state/reducers.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Organization, OrganizationPreferences, OrganizationState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
|
||||
const initialState: OrganizationState = {
|
||||
organization: {} as Organization,
|
||||
preferences: {} as OrganizationPreferences,
|
||||
};
|
||||
|
||||
const organizationReducer = (state = initialState, action: Action): OrganizationState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadOrganization:
|
||||
return { ...state, organization: action.payload };
|
||||
|
||||
case ActionTypes.LoadPreferences:
|
||||
return { ...state, preferences: action.payload };
|
||||
|
||||
case ActionTypes.SetOrganizationName:
|
||||
return { ...state, organization: { ...state.organization, name: action.payload } };
|
||||
|
||||
case ActionTypes.SetOrganizationTheme:
|
||||
return { ...state, preferences: { ...state.preferences, theme: action.payload } };
|
||||
|
||||
case ActionTypes.SetOrganizationHomeDashboard:
|
||||
return { ...state, preferences: { ...state.preferences, homeDashboardId: action.payload } };
|
||||
|
||||
case ActionTypes.SetOrganizationTimezone:
|
||||
return { ...state, preferences: { ...state.preferences, timezone: action.payload } };
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default {
|
||||
organization: organizationReducer,
|
||||
};
|
||||
Reference in New Issue
Block a user