diff --git a/public/app/core/components/PermissionList/AddPermission.tsx b/public/app/core/components/PermissionList/AddPermission.tsx index 77ac6953b74..fc062ce63e4 100644 --- a/public/app/core/components/PermissionList/AddPermission.tsx +++ b/public/app/core/components/PermissionList/AddPermission.tsx @@ -1,7 +1,8 @@ import React, { Component } from 'react'; -import { UserPicker, User } from 'app/core/components/Picker/UserPicker'; +import { UserPicker } from 'app/core/components/Picker/UserPicker'; import { TeamPicker, Team } from 'app/core/components/Picker/TeamPicker'; import DescriptionPicker, { OptionWithDescription } from 'app/core/components/Picker/DescriptionPicker'; +import { User } from 'app/types'; import { dashboardPermissionLevels, dashboardAclTargets, diff --git a/public/app/core/components/Picker/UserPicker.tsx b/public/app/core/components/Picker/UserPicker.tsx index e50513c44e1..8f48ba8f66a 100644 --- a/public/app/core/components/Picker/UserPicker.tsx +++ b/public/app/core/components/Picker/UserPicker.tsx @@ -3,6 +3,7 @@ import Select from 'react-select'; import PickerOption from './PickerOption'; import { debounce } from 'lodash'; import { getBackendSrv } from 'app/core/services/backend_srv'; +import { User } from 'app/types'; export interface Props { onSelected: (user: User) => void; @@ -14,13 +15,6 @@ export interface State { isLoading: boolean; } -export interface User { - id: number; - label: string; - avatarUrl: string; - login: string; -} - export class UserPicker extends Component { debouncedSearch: any; diff --git a/public/app/features/api-keys/ApiKeysAddedModal.test.tsx b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx new file mode 100644 index 00000000000..160418a7ab8 --- /dev/null +++ b/public/app/features/api-keys/ApiKeysAddedModal.test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { ApiKeysAddedModal, Props } from './ApiKeysAddedModal'; + +const setup = (propOverrides?: object) => { + const props: Props = { + apiKey: 'api key test', + rootPath: 'test/path', + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + + return { + wrapper, + }; +}; + +describe('Render', () => { + it('should render component', () => { + const { wrapper } = setup(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/api-keys/ApiKeysAddedModal.tsx b/public/app/features/api-keys/ApiKeysAddedModal.tsx new file mode 100644 index 00000000000..995aa46c773 --- /dev/null +++ b/public/app/features/api-keys/ApiKeysAddedModal.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +export interface Props { + apiKey: string; + rootPath: string; +} + +export const ApiKeysAddedModal = (props: Props) => { + return ( +
+
+

+ + API Key Created +

+ + + + +
+ +
+
+
+ Key + {props.apiKey} +
+
+ +
+ You will only be able to view this key here once! It is not stored in this form. So be sure to copy it now. +
+
+ You can authenticate request using the Authorization HTTP header, example: +
+
+
+            curl -H "Authorization: Bearer {props.apiKey}" {props.rootPath}/api/dashboards/home
+          
+
+
+
+ ); +}; + +export default ApiKeysAddedModal; diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx new file mode 100644 index 00000000000..518180fc424 --- /dev/null +++ b/public/app/features/api-keys/ApiKeysPage.test.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { Props, ApiKeysPage } from './ApiKeysPage'; +import { NavModel, ApiKey } from 'app/types'; +import { getMultipleMockKeys, getMockKey } from './__mocks__/apiKeysMock'; + +const setup = (propOverrides?: object) => { + const props: Props = { + navModel: {} as NavModel, + apiKeys: [] as ApiKey[], + searchQuery: '', + loadApiKeys: jest.fn(), + deleteApiKey: jest.fn(), + setSearchQuery: jest.fn(), + addApiKey: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as ApiKeysPage; + + return { + wrapper, + instance, + }; +}; + +describe('Render', () => { + it('should render component', () => { + const { wrapper } = setup(); + expect(wrapper).toMatchSnapshot(); + }); + + it('should render API keys table', () => { + const { wrapper } = setup({ + apiKeys: getMultipleMockKeys(5), + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); + +describe('Life cycle', () => { + it('should call loadApiKeys', () => { + const { instance } = setup(); + + instance.componentDidMount(); + + expect(instance.props.loadApiKeys).toHaveBeenCalled(); + }); +}); + +describe('Functions', () => { + describe('Delete team', () => { + it('should call delete team', () => { + const { instance } = setup(); + instance.onDeleteApiKey(getMockKey()); + expect(instance.props.deleteApiKey).toHaveBeenCalledWith(1); + }); + }); + + describe('on search query change', () => { + it('should call setSearchQuery', () => { + const { instance } = setup(); + const mockEvent = { target: { value: 'test' } }; + + instance.onSearchQueryChange(mockEvent); + + expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test'); + }); + }); +}); diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx new file mode 100644 index 00000000000..2f19250e835 --- /dev/null +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -0,0 +1,222 @@ +import React, { PureComponent } from 'react'; +import ReactDOMServer from 'react-dom/server'; +import { connect } from 'react-redux'; +import { hot } from 'react-hot-loader'; +import { NavModel, ApiKey, NewApiKey, OrgRole } from 'app/types'; +import { getNavModel } from 'app/core/selectors/navModel'; +import { getApiKeys } from './state/selectors'; +import { loadApiKeys, deleteApiKey, setSearchQuery, addApiKey } from './state/actions'; +import PageHeader from 'app/core/components/PageHeader/PageHeader'; +import SlideDown from 'app/core/components/Animations/SlideDown'; +import ApiKeysAddedModal from './ApiKeysAddedModal'; +import config from 'app/core/config'; +import appEvents from 'app/core/app_events'; + +export interface Props { + navModel: NavModel; + apiKeys: ApiKey[]; + searchQuery: string; + loadApiKeys: typeof loadApiKeys; + deleteApiKey: typeof deleteApiKey; + setSearchQuery: typeof setSearchQuery; + addApiKey: typeof addApiKey; +} + +export interface State { + isAdding: boolean; + newApiKey: NewApiKey; +} + +enum ApiKeyStateProps { + Name = 'name', + Role = 'role', +} + +const initialApiKeyState = { + name: '', + role: OrgRole.Viewer, +}; + +export class ApiKeysPage extends PureComponent { + constructor(props) { + super(props); + this.state = { isAdding: false, newApiKey: initialApiKeyState }; + } + + componentDidMount() { + this.fetchApiKeys(); + } + + async fetchApiKeys() { + await this.props.loadApiKeys(); + } + + onDeleteApiKey(key: ApiKey) { + this.props.deleteApiKey(key.id); + } + + onSearchQueryChange = evt => { + this.props.setSearchQuery(evt.target.value); + }; + + onToggleAdding = () => { + this.setState({ isAdding: !this.state.isAdding }); + }; + + onAddApiKey = async evt => { + evt.preventDefault(); + + const openModal = (apiKey: string) => { + const rootPath = window.location.origin + config.appSubUrl; + const modalTemplate = ReactDOMServer.renderToString(); + + appEvents.emit('show-modal', { + templateHtml: modalTemplate, + }); + }; + + this.props.addApiKey(this.state.newApiKey, openModal); + this.setState((prevState: State) => { + return { + ...prevState, + newApiKey: initialApiKeyState, + }; + }); + }; + + onApiKeyStateUpdate = (evt, prop: string) => { + const value = evt.currentTarget.value; + this.setState((prevState: State) => { + const newApiKey = { + ...prevState.newApiKey, + }; + newApiKey[prop] = value; + + return { + ...prevState, + newApiKey: newApiKey, + }; + }); + }; + + render() { + const { newApiKey, isAdding } = this.state; + const { navModel, apiKeys, searchQuery } = this.props; + + return ( +
+ +
+
+
+ +
+ +
+ +
+ + +
+ +
Add API Key
+
+
+
+ Key name + this.onApiKeyStateUpdate(evt, ApiKeyStateProps.Name)} + /> +
+
+ Role + + + +
+
+ +
+
+
+
+
+ +

Existing Keys

+ + + + + + + + {apiKeys.length > 0 ? ( + + {apiKeys.map(key => { + return ( + + + + + + ); + })} + + ) : null} +
NameRole +
{key.name}{key.role} + this.onDeleteApiKey(key)} className="btn btn-danger btn-mini"> + + +
+
+
+ ); + } +} + +function mapStateToProps(state) { + return { + navModel: getNavModel(state.navIndex, 'apikeys'), + apiKeys: getApiKeys(state.apiKeys), + searchQuery: state.apiKeys.searchQuery, + }; +} + +const mapDispatchToProps = { + loadApiKeys, + deleteApiKey, + setSearchQuery, + addApiKey, +}; + +export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ApiKeysPage)); diff --git a/public/app/features/api-keys/__mocks__/apiKeysMock.ts b/public/app/features/api-keys/__mocks__/apiKeysMock.ts new file mode 100644 index 00000000000..117f0d6d0c6 --- /dev/null +++ b/public/app/features/api-keys/__mocks__/apiKeysMock.ts @@ -0,0 +1,22 @@ +import { ApiKey, OrgRole } from 'app/types'; + +export const getMultipleMockKeys = (numberOfKeys: number): ApiKey[] => { + const keys: ApiKey[] = []; + for (let i = 1; i <= numberOfKeys; i++) { + keys.push({ + id: i, + name: `test-${i}`, + role: OrgRole.Viewer, + }); + } + + return keys; +}; + +export const getMockKey = (): ApiKey => { + return { + id: 1, + name: 'test', + role: OrgRole.Admin, + }; +}; diff --git a/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap b/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap new file mode 100644 index 00000000000..0fcb13308eb --- /dev/null +++ b/public/app/features/api-keys/__snapshots__/ApiKeysAddedModal.test.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
+
+

+ + + API Key Created + +

+ + + +
+
+
+
+ + Key + + + api key test + +
+
+
+ You will only be able to view this key here once! It is not stored in this form. So be sure to copy it now. +
+
+ You can authenticate request using the Authorization HTTP header, example: +
+
+
+        curl -H "Authorization: Bearer 
+        api key test
+        " 
+        test/path
+        /api/dashboards/home
+      
+
+
+
+`; diff --git a/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap new file mode 100644 index 00000000000..77c7f620173 --- /dev/null +++ b/public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap @@ -0,0 +1,435 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render API keys table 1`] = ` +
+ +
+
+
+ +
+
+ +
+ +
+ +
+ Add API Key +
+
+
+
+ + Key name + + +
+
+ + Role + + + + +
+
+ +
+
+
+
+
+

+ Existing Keys +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Role + +
+ test-1 + + Viewer + + + + +
+ test-2 + + Viewer + + + + +
+ test-3 + + Viewer + + + + +
+ test-4 + + Viewer + + + + +
+ test-5 + + Viewer + + + + +
+
+
+`; + +exports[`Render should render component 1`] = ` +
+ +
+
+
+ +
+
+ +
+ +
+ +
+ Add API Key +
+
+
+
+ + Key name + + +
+
+ + Role + + + + +
+
+ +
+
+
+
+
+

+ Existing Keys +

+ + + + + + + +
+ Name + + Role + +
+
+
+`; diff --git a/public/app/features/api-keys/state/actions.ts b/public/app/features/api-keys/state/actions.ts new file mode 100644 index 00000000000..63e91088476 --- /dev/null +++ b/public/app/features/api-keys/state/actions.ts @@ -0,0 +1,56 @@ +import { ThunkAction } from 'redux-thunk'; +import { getBackendSrv } from 'app/core/services/backend_srv'; +import { StoreState, ApiKey } from 'app/types'; + +export enum ActionTypes { + LoadApiKeys = 'LOAD_API_KEYS', + SetApiKeysSearchQuery = 'SET_API_KEYS_SEARCH_QUERY', +} + +export interface LoadApiKeysAction { + type: ActionTypes.LoadApiKeys; + payload: ApiKey[]; +} + +export interface SetSearchQueryAction { + type: ActionTypes.SetApiKeysSearchQuery; + payload: string; +} + +export type Action = LoadApiKeysAction | SetSearchQueryAction; + +type ThunkResult = ThunkAction; + +const apiKeysLoaded = (apiKeys: ApiKey[]): LoadApiKeysAction => ({ + type: ActionTypes.LoadApiKeys, + payload: apiKeys, +}); + +export function addApiKey(apiKey: ApiKey, openModal: (key: string) => void): ThunkResult { + return async dispatch => { + const result = await getBackendSrv().post('/api/auth/keys', apiKey); + dispatch(setSearchQuery('')); + dispatch(loadApiKeys()); + openModal(result.key); + }; +} + +export function loadApiKeys(): ThunkResult { + return async dispatch => { + const response = await getBackendSrv().get('/api/auth/keys'); + dispatch(apiKeysLoaded(response)); + }; +} + +export function deleteApiKey(id: number): ThunkResult { + return async dispatch => { + getBackendSrv() + .delete('/api/auth/keys/' + id) + .then(dispatch(loadApiKeys())); + }; +} + +export const setSearchQuery = (searchQuery: string): SetSearchQueryAction => ({ + type: ActionTypes.SetApiKeysSearchQuery, + payload: searchQuery, +}); diff --git a/public/app/features/api-keys/state/reducers.test.ts b/public/app/features/api-keys/state/reducers.test.ts new file mode 100644 index 00000000000..3b2c831a5a3 --- /dev/null +++ b/public/app/features/api-keys/state/reducers.test.ts @@ -0,0 +1,31 @@ +import { Action, ActionTypes } from './actions'; +import { initialApiKeysState, apiKeysReducer } from './reducers'; +import { getMultipleMockKeys } from '../__mocks__/apiKeysMock'; + +describe('API Keys reducer', () => { + it('should set keys', () => { + const payload = getMultipleMockKeys(4); + + const action: Action = { + type: ActionTypes.LoadApiKeys, + payload, + }; + + const result = apiKeysReducer(initialApiKeysState, action); + + expect(result.keys).toEqual(payload); + }); + + it('should set search query', () => { + const payload = 'test query'; + + const action: Action = { + type: ActionTypes.SetApiKeysSearchQuery, + payload, + }; + + const result = apiKeysReducer(initialApiKeysState, action); + + expect(result.searchQuery).toEqual('test query'); + }); +}); diff --git a/public/app/features/api-keys/state/reducers.ts b/public/app/features/api-keys/state/reducers.ts new file mode 100644 index 00000000000..a21aa55dbf7 --- /dev/null +++ b/public/app/features/api-keys/state/reducers.ts @@ -0,0 +1,21 @@ +import { ApiKeysState } from 'app/types'; +import { Action, ActionTypes } from './actions'; + +export const initialApiKeysState: ApiKeysState = { + keys: [], + searchQuery: '', +}; + +export const apiKeysReducer = (state = initialApiKeysState, action: Action): ApiKeysState => { + switch (action.type) { + case ActionTypes.LoadApiKeys: + return { ...state, keys: action.payload }; + case ActionTypes.SetApiKeysSearchQuery: + return { ...state, searchQuery: action.payload }; + } + return state; +}; + +export default { + apiKeys: apiKeysReducer, +}; diff --git a/public/app/features/api-keys/state/selectors.test.ts b/public/app/features/api-keys/state/selectors.test.ts new file mode 100644 index 00000000000..7d8f3122ce6 --- /dev/null +++ b/public/app/features/api-keys/state/selectors.test.ts @@ -0,0 +1,25 @@ +import { getApiKeys } from './selectors'; +import { getMultipleMockKeys } from '../__mocks__/apiKeysMock'; +import { ApiKeysState } from 'app/types'; + +describe('API Keys selectors', () => { + describe('Get API Keys', () => { + const mockKeys = getMultipleMockKeys(5); + + it('should return all keys if no search query', () => { + const mockState: ApiKeysState = { keys: mockKeys, searchQuery: '' }; + + const keys = getApiKeys(mockState); + + expect(keys).toEqual(mockKeys); + }); + + it('should filter keys if search query exists', () => { + const mockState: ApiKeysState = { keys: mockKeys, searchQuery: '5' }; + + const keys = getApiKeys(mockState); + + expect(keys.length).toEqual(1); + }); + }); +}); diff --git a/public/app/features/api-keys/state/selectors.ts b/public/app/features/api-keys/state/selectors.ts new file mode 100644 index 00000000000..8065c252e85 --- /dev/null +++ b/public/app/features/api-keys/state/selectors.ts @@ -0,0 +1,9 @@ +import { ApiKeysState } from 'app/types'; + +export const getApiKeys = (state: ApiKeysState) => { + const regex = RegExp(state.searchQuery, 'i'); + + return state.keys.filter(key => { + return regex.test(key.name) || regex.test(key.role); + }); +}; diff --git a/public/app/features/org/all.ts b/public/app/features/org/all.ts index 8872450e3ab..1cbca483138 100644 --- a/public/app/features/org/all.ts +++ b/public/app/features/org/all.ts @@ -6,6 +6,5 @@ import './change_password_ctrl'; import './new_org_ctrl'; import './user_invite_ctrl'; import './create_team_ctrl'; -import './org_api_keys_ctrl'; import './org_details_ctrl'; import './prefs_control'; diff --git a/public/app/features/org/org_api_keys_ctrl.ts b/public/app/features/org/org_api_keys_ctrl.ts deleted file mode 100644 index 1ead0a350b9..00000000000 --- a/public/app/features/org/org_api_keys_ctrl.ts +++ /dev/null @@ -1,44 +0,0 @@ -import angular from 'angular'; - -export class OrgApiKeysCtrl { - /** @ngInject */ - constructor($scope, $http, backendSrv, navModelSrv) { - $scope.navModel = navModelSrv.getNav('cfg', 'apikeys', 0); - - $scope.roleTypes = ['Viewer', 'Editor', 'Admin']; - $scope.token = { role: 'Viewer' }; - - $scope.init = () => { - $scope.getTokens(); - }; - - $scope.getTokens = () => { - backendSrv.get('/api/auth/keys').then(tokens => { - $scope.tokens = tokens; - }); - }; - - $scope.removeToken = id => { - backendSrv.delete('/api/auth/keys/' + id).then($scope.getTokens); - }; - - $scope.addToken = () => { - backendSrv.post('/api/auth/keys', $scope.token).then(result => { - const modalScope = $scope.$new(true); - modalScope.key = result.key; - modalScope.rootPath = window.location.origin + $scope.$root.appSubUrl; - - $scope.appEvent('show-modal', { - src: 'public/app/features/org/partials/apikeyModal.html', - scope: modalScope, - }); - - $scope.getTokens(); - }); - }; - - $scope.init(); - } -} - -angular.module('grafana.controllers').controller('OrgApiKeysCtrl', OrgApiKeysCtrl); diff --git a/public/app/features/org/partials/apikeyModal.html b/public/app/features/org/partials/apikeyModal.html deleted file mode 100644 index eeefcafc634..00000000000 --- a/public/app/features/org/partials/apikeyModal.html +++ /dev/null @@ -1,37 +0,0 @@ - - diff --git a/public/app/features/org/partials/orgApiKeys.html b/public/app/features/org/partials/orgApiKeys.html deleted file mode 100644 index a2b4ceb6670..00000000000 --- a/public/app/features/org/partials/orgApiKeys.html +++ /dev/null @@ -1,49 +0,0 @@ - - -
- -

Add new

- -
-
-
- Key name - -
-
- Role - - - -
-
- -
-
-
- -

Existing Keys

- - - - - - - - - - - - - - - -
NameRole
{{t.name}}{{t.role}} - - - -
-
- - - diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index cda175f4395..588745eea37 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -1,10 +1,10 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import SlideDown from 'app/core/components/Animations/SlideDown'; -import { UserPicker, User } from 'app/core/components/Picker/UserPicker'; +import { UserPicker } from 'app/core/components/Picker/UserPicker'; import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; -import { TeamMember } from '../../types'; +import { TeamMember, User } from 'app/types'; import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions'; import { getSearchMemberQuery, getTeamMembers } from './state/selectors'; diff --git a/public/app/features/teams/__mocks__/teamMocks.ts b/public/app/features/teams/__mocks__/teamMocks.ts index 34fa06b2d09..339f227c081 100644 --- a/public/app/features/teams/__mocks__/teamMocks.ts +++ b/public/app/features/teams/__mocks__/teamMocks.ts @@ -1,4 +1,4 @@ -import { Team, TeamGroup, TeamMember } from '../../../types'; +import { Team, TeamGroup, TeamMember } from 'app/types'; export const getMultipleMockTeams = (numberOfTeams: number): Team[] => { const teams: Team[] = []; diff --git a/public/app/routes/routes.ts b/public/app/routes/routes.ts index e4662c77367..dbaa1c02952 100644 --- a/public/app/routes/routes.ts +++ b/public/app/routes/routes.ts @@ -5,6 +5,7 @@ import ServerStats from 'app/features/admin/ServerStats'; import AlertRuleList from 'app/features/alerting/AlertRuleList'; import TeamPages from 'app/features/teams/TeamPages'; import TeamList from 'app/features/teams/TeamList'; +import ApiKeys from 'app/features/api-keys/ApiKeysPage'; import PluginListPage from 'app/features/plugins/PluginListPage'; import FolderSettingsPage from 'app/features/folders/FolderSettingsPage'; import FolderPermissions from 'app/features/folders/FolderPermissions'; @@ -139,8 +140,11 @@ export function setupAngularRoutes($routeProvider, $locationProvider) { controllerAs: 'ctrl', }) .when('/org/apikeys', { - templateUrl: 'public/app/features/org/partials/orgApiKeys.html', - controller: 'OrgApiKeysCtrl', + template: '', + resolve: { + roles: () => ['Editor', 'Admin'], + component: () => ApiKeys, + }, }) .when('/org/teams', { template: '', diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index 08d3d5bede0..6d1f5f74b4e 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -4,6 +4,7 @@ import { createLogger } from 'redux-logger'; import sharedReducers from 'app/core/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; import teamsReducers from 'app/features/teams/state/reducers'; +import apiKeysReducers from 'app/features/api-keys/state/reducers'; import foldersReducers from 'app/features/folders/state/reducers'; import dashboardReducers from 'app/features/dashboard/state/reducers'; import pluginReducers from 'app/features/plugins/state/reducers'; @@ -12,6 +13,7 @@ const rootReducer = combineReducers({ ...sharedReducers, ...alertingReducers, ...teamsReducers, + ...apiKeysReducers, ...foldersReducers, ...dashboardReducers, ...pluginReducers, diff --git a/public/app/types/apiKeys.ts b/public/app/types/apiKeys.ts new file mode 100644 index 00000000000..6288f5165ad --- /dev/null +++ b/public/app/types/apiKeys.ts @@ -0,0 +1,17 @@ +import { OrgRole } from './acl'; + +export interface ApiKey { + id: number; + name: string; + role: OrgRole; +} + +export interface NewApiKey { + name: string; + role: OrgRole; +} + +export interface ApiKeysState { + keys: ApiKey[]; + searchQuery: string; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 1dd11d73564..b4e4e504a2b 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -6,6 +6,8 @@ import { FolderDTO, FolderState, FolderInfo } from './folders'; import { DashboardState } from './dashboard'; import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { DataSource } from './datasources'; +import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; +import { User } from './user'; import { PluginMeta, Plugin, PluginsState } from './plugins'; export { @@ -33,6 +35,10 @@ export { PermissionLevel, DataSource, PluginMeta, + ApiKey, + ApiKeysState, + NewApiKey, + User, Plugin, PluginsState, }; diff --git a/public/app/types/user.ts b/public/app/types/user.ts new file mode 100644 index 00000000000..9c13e6b027b --- /dev/null +++ b/public/app/types/user.ts @@ -0,0 +1,6 @@ +export interface User { + id: number; + label: string; + avatarUrl: string; + login: string; +}