Merge pull request #12975 from influxdata/fix/org-dashboards-disappearing
fix(ui): Ensure dashboard cards generates sort order from correct dashboardspull/11814/head
commit
b8272155e3
|
@ -9,6 +9,7 @@ import {getBuckets} from 'src/buckets/actions'
|
|||
import {getTelegrafs} from 'src/telegrafs/actions'
|
||||
import {getVariables} from 'src/variables/actions'
|
||||
import {getScrapers} from 'src/scrapers/actions'
|
||||
import {getDashboardsAsync} from 'src/dashboards/actions'
|
||||
|
||||
// Types
|
||||
import {AppState} from 'src/types'
|
||||
|
@ -24,6 +25,7 @@ import {TechnoSpinner, SpinnerContainer} from '@influxdata/clockface'
|
|||
import {getAuthorizations} from 'src/authorizations/actions'
|
||||
import {AuthorizationsState} from 'src/authorizations/reducers'
|
||||
import {VariablesState} from 'src/variables/reducers'
|
||||
import {DashboardsState} from 'src/dashboards/reducers/dashboards'
|
||||
|
||||
interface StateProps {
|
||||
org: Organization
|
||||
|
@ -33,6 +35,7 @@ interface StateProps {
|
|||
variables: VariablesState
|
||||
scrapers: ScrapersState
|
||||
tokens: AuthorizationsState
|
||||
dashboards: DashboardsState
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -42,6 +45,7 @@ interface DispatchProps {
|
|||
getVariables: typeof getVariables
|
||||
getScrapers: typeof getScrapers
|
||||
getAuthorizations: typeof getAuthorizations
|
||||
getDashboards: typeof getDashboardsAsync
|
||||
}
|
||||
|
||||
interface PassedProps {
|
||||
|
@ -57,12 +61,17 @@ export enum ResourceTypes {
|
|||
Variables = 'variables',
|
||||
Authorizations = 'tokens',
|
||||
Scrapers = 'scrapers',
|
||||
Dashboards = 'dashboards',
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class GetResources extends PureComponent<Props, StateProps> {
|
||||
public async componentDidMount() {
|
||||
switch (this.props.resource) {
|
||||
case ResourceTypes.Dashboards: {
|
||||
return await this.props.getDashboards()
|
||||
}
|
||||
|
||||
case ResourceTypes.Labels: {
|
||||
return await this.props.getLabels()
|
||||
}
|
||||
|
@ -115,6 +124,7 @@ const mstp = ({
|
|||
variables,
|
||||
scrapers,
|
||||
tokens,
|
||||
dashboards,
|
||||
}: AppState): StateProps => {
|
||||
const org = orgs[0]
|
||||
|
||||
|
@ -122,6 +132,7 @@ const mstp = ({
|
|||
labels,
|
||||
buckets,
|
||||
telegrafs,
|
||||
dashboards,
|
||||
variables,
|
||||
scrapers,
|
||||
tokens,
|
||||
|
@ -136,6 +147,7 @@ const mdtp = {
|
|||
getVariables: getVariables,
|
||||
getScrapers: getScrapers,
|
||||
getAuthorizations: getAuthorizations,
|
||||
getDashboards: getDashboardsAsync,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, {}>(
|
||||
|
|
|
@ -66,22 +66,22 @@ import {
|
|||
} from 'src/types'
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadDashboards = 'LOAD_DASHBOARDS',
|
||||
LoadDashboard = 'LOAD_DASHBOARD',
|
||||
DeleteDashboard = 'DELETE_DASHBOARD',
|
||||
SetDashboards = 'SET_DASHBOARDS',
|
||||
SetDashboard = 'SET_DASHBOARD',
|
||||
RemoveDashboard = 'REMOVE_DASHBOARD',
|
||||
DeleteDashboardFailed = 'DELETE_DASHBOARD_FAILED',
|
||||
UpdateDashboard = 'UPDATE_DASHBOARD',
|
||||
DeleteCell = 'DELETE_CELL',
|
||||
EditDashboard = 'EDIT_DASHBOARD',
|
||||
RemoveCell = 'REMOVE_CELL',
|
||||
AddDashboardLabels = 'ADD_DASHBOARD_LABELS',
|
||||
RemoveDashboardLabels = 'REMOVE_DASHBOARD_LABELS',
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| LoadDashboardsAction
|
||||
| DeleteDashboardAction
|
||||
| LoadDashboardAction
|
||||
| UpdateDashboardAction
|
||||
| DeleteCellAction
|
||||
| SetDashboardsAction
|
||||
| RemoveDashboardAction
|
||||
| SetDashboardAction
|
||||
| EditDashboardAction
|
||||
| RemoveCellAction
|
||||
| PublishNotificationAction
|
||||
| SetViewAction
|
||||
| DeleteTimeRangeAction
|
||||
|
@ -89,32 +89,33 @@ export type Action =
|
|||
| AddDashboardLabelsAction
|
||||
| RemoveDashboardLabelsAction
|
||||
|
||||
interface DeleteCellAction {
|
||||
type: ActionTypes.DeleteCell
|
||||
interface RemoveCellAction {
|
||||
type: ActionTypes.RemoveCell
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
cell: Cell
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateDashboardAction {
|
||||
type: ActionTypes.UpdateDashboard
|
||||
interface EditDashboardAction {
|
||||
type: ActionTypes.EditDashboard
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
}
|
||||
}
|
||||
|
||||
interface LoadDashboardsAction {
|
||||
type: ActionTypes.LoadDashboards
|
||||
interface SetDashboardsAction {
|
||||
type: ActionTypes.SetDashboards
|
||||
payload: {
|
||||
dashboards: Dashboard[]
|
||||
status: RemoteDataState
|
||||
list: Dashboard[]
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteDashboardAction {
|
||||
type: ActionTypes.DeleteDashboard
|
||||
interface RemoveDashboardAction {
|
||||
type: ActionTypes.RemoveDashboard
|
||||
payload: {
|
||||
dashboardID: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,8 +126,8 @@ interface DeleteDashboardFailedAction {
|
|||
}
|
||||
}
|
||||
|
||||
interface LoadDashboardAction {
|
||||
type: ActionTypes.LoadDashboard
|
||||
interface SetDashboardAction {
|
||||
type: ActionTypes.SetDashboard
|
||||
payload: {
|
||||
dashboard: Dashboard
|
||||
}
|
||||
|
@ -150,32 +151,30 @@ interface RemoveDashboardLabelsAction {
|
|||
|
||||
// Action Creators
|
||||
|
||||
export const updateDashboard = (
|
||||
dashboard: Dashboard
|
||||
): UpdateDashboardAction => ({
|
||||
type: ActionTypes.UpdateDashboard,
|
||||
export const editDashboard = (dashboard: Dashboard): EditDashboardAction => ({
|
||||
type: ActionTypes.EditDashboard,
|
||||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const loadDashboards = (
|
||||
dashboards: Dashboard[]
|
||||
): LoadDashboardsAction => ({
|
||||
type: ActionTypes.LoadDashboards,
|
||||
export const setDashboards = (
|
||||
status: RemoteDataState,
|
||||
list?: Dashboard[]
|
||||
): SetDashboardsAction => ({
|
||||
type: ActionTypes.SetDashboards,
|
||||
payload: {
|
||||
dashboards,
|
||||
status,
|
||||
list,
|
||||
},
|
||||
})
|
||||
|
||||
export const loadDashboard = (dashboard: Dashboard): LoadDashboardAction => ({
|
||||
type: ActionTypes.LoadDashboard,
|
||||
export const setDashboard = (dashboard: Dashboard): SetDashboardAction => ({
|
||||
type: ActionTypes.SetDashboard,
|
||||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const deleteDashboard = (
|
||||
dashboardID: string
|
||||
): DeleteDashboardAction => ({
|
||||
type: ActionTypes.DeleteDashboard,
|
||||
payload: {dashboardID},
|
||||
export const removeDashboard = (id: string): RemoveDashboardAction => ({
|
||||
type: ActionTypes.RemoveDashboard,
|
||||
payload: {id},
|
||||
})
|
||||
|
||||
export const deleteDashboardFailed = (
|
||||
|
@ -185,11 +184,11 @@ export const deleteDashboardFailed = (
|
|||
payload: {dashboard},
|
||||
})
|
||||
|
||||
export const deleteCell = (
|
||||
export const removeCell = (
|
||||
dashboard: Dashboard,
|
||||
cell: Cell
|
||||
): DeleteCellAction => ({
|
||||
type: ActionTypes.DeleteCell,
|
||||
): RemoveCellAction => ({
|
||||
type: ActionTypes.RemoveCell,
|
||||
payload: {dashboard, cell},
|
||||
})
|
||||
|
||||
|
@ -215,10 +214,12 @@ export const getDashboardsAsync = () => async (
|
|||
dispatch: Dispatch<Action>
|
||||
): Promise<Dashboard[]> => {
|
||||
try {
|
||||
dispatch(setDashboards(RemoteDataState.Loading))
|
||||
const dashboards = await getDashboardsAJAX()
|
||||
dispatch(loadDashboards(dashboards))
|
||||
dispatch(setDashboards(RemoteDataState.Done, dashboards))
|
||||
return dashboards
|
||||
} catch (error) {
|
||||
dispatch(setDashboards(RemoteDataState.Error))
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
|
@ -244,9 +245,10 @@ export const importDashboardAsync = (dashboard: Dashboard) => async (
|
|||
await createDashboardAJAX(dashboard)
|
||||
const dashboards = await getDashboardsAJAX()
|
||||
|
||||
dispatch(loadDashboards(dashboards))
|
||||
dispatch(setDashboards(RemoteDataState.Done, dashboards))
|
||||
dispatch(notify(copy.dashboardImported()))
|
||||
} catch (error) {
|
||||
dispatch(setDashboards(RemoteDataState.Error))
|
||||
dispatch(notify(copy.dashboardImportFailed('Could not upload dashboard')))
|
||||
console.error(error)
|
||||
}
|
||||
|
@ -255,7 +257,7 @@ export const importDashboardAsync = (dashboard: Dashboard) => async (
|
|||
export const deleteDashboardAsync = (dashboard: Dashboard) => async (
|
||||
dispatch: Dispatch<Action>
|
||||
): Promise<void> => {
|
||||
dispatch(deleteDashboard(dashboard.id))
|
||||
dispatch(removeDashboard(dashboard.id))
|
||||
dispatch(deleteTimeRange(dashboard.id))
|
||||
|
||||
try {
|
||||
|
@ -303,7 +305,7 @@ export const getDashboardAsync = (dashboardID: string) => async (
|
|||
await dispatch(refreshDashboardVariableValues(dashboard, views))
|
||||
|
||||
// Now that all the necessary state has been loaded, set the dashboard
|
||||
dispatch(loadDashboard(dashboard))
|
||||
dispatch(setDashboard(dashboard))
|
||||
} catch {
|
||||
dispatch(replace(`/dashboards`))
|
||||
dispatch(notify(copy.dashboardGetFailed(dashboardID)))
|
||||
|
@ -319,7 +321,7 @@ export const updateDashboardAsync = (dashboard: Dashboard) => async (
|
|||
): Promise<void> => {
|
||||
try {
|
||||
const updatedDashboard = await updateDashboardAJAX(dashboard)
|
||||
dispatch(updateDashboard(updatedDashboard))
|
||||
dispatch(editDashboard(updatedDashboard))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
dispatch(notify(copy.dashboardUpdateFailed()))
|
||||
|
@ -354,7 +356,7 @@ export const createCellWithView = (
|
|||
await dispatch(refreshDashboardVariableValues(dashboard, views))
|
||||
|
||||
dispatch(setView(createdCell.id, newView, RemoteDataState.Done))
|
||||
dispatch(updateDashboard(updatedDashboard))
|
||||
dispatch(editDashboard(updatedDashboard))
|
||||
} catch {
|
||||
notify(copy.cellAddFailed())
|
||||
}
|
||||
|
@ -393,7 +395,7 @@ export const updateCellsAsync = (dashboard: Dashboard, cells: Cell[]) => async (
|
|||
cells: updatedCells,
|
||||
}
|
||||
|
||||
dispatch(loadDashboard(updatedDashboard))
|
||||
dispatch(setDashboard(updatedDashboard))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
@ -413,7 +415,7 @@ export const deleteCellAsync = (dashboard: Dashboard, cell: Cell) => async (
|
|||
dispatch(refreshDashboardVariableValues(dashboard, views)),
|
||||
])
|
||||
|
||||
dispatch(deleteCell(dashboard, cell))
|
||||
dispatch(removeCell(dashboard, cell))
|
||||
dispatch(notify(copy.cellDeleted()))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -431,7 +433,7 @@ export const copyDashboardCellAsync = (
|
|||
cells: [...dashboard.cells, clonedCell],
|
||||
}
|
||||
|
||||
dispatch(loadDashboard(updatedDashboard))
|
||||
dispatch(setDashboard(updatedDashboard))
|
||||
dispatch(notify(copy.cellAdded()))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -471,7 +473,7 @@ export const selectVariableValue = (
|
|||
value: string
|
||||
) => async (dispatch, getState: GetState): Promise<void> => {
|
||||
const variables = getHydratedVariables(getState(), dashboardID)
|
||||
const dashboard = getState().dashboards.find(d => d.id === dashboardID)
|
||||
const dashboard = getState().dashboards.list.find(d => d.id === dashboardID)
|
||||
|
||||
dispatch(selectValue(dashboardID, variableID, value))
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ export const createNoteCell = (dashboardID: string) => async (
|
|||
dispatch: Dispatch<Action>,
|
||||
getState: GetState
|
||||
) => {
|
||||
const dashboard = getState().dashboards.find(d => d.id === dashboardID)
|
||||
const dashboard = getState().dashboards.list.find(d => d.id === dashboardID)
|
||||
|
||||
if (!dashboard) {
|
||||
throw new Error(`could not find dashboard with id "${dashboardID}"`)
|
||||
|
|
|
@ -312,7 +312,7 @@ const mstp = (state: AppState, {params: {dashboardID}}): StateProps => {
|
|||
const timeRange =
|
||||
ranges.find(r => r.dashboardID === dashboardID) || DEFAULT_TIME_RANGE
|
||||
|
||||
const dashboard = dashboards.find(d => d.id === dashboardID)
|
||||
const dashboard = dashboards.list.find(d => d.id === dashboardID)
|
||||
|
||||
return {
|
||||
links,
|
||||
|
|
|
@ -105,7 +105,7 @@ class VEOContents extends PureComponent<Props, {}> {
|
|||
|
||||
const mstp = (state: AppState, {dashboardID}): StateProps => {
|
||||
const {dashboards} = state
|
||||
const dashboard = dashboards.find(d => d.id === dashboardID)
|
||||
const dashboard = dashboards.list.find(d => d.id === dashboardID)
|
||||
|
||||
const {view, draftQueries} = getActiveTimeMachine(state)
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class DashboardCards extends PureComponent<Props> {
|
|||
|
||||
const mstp = (state: AppState, props: OwnProps) => {
|
||||
return {
|
||||
sortedIDs: getSortedResource(state.dashboards, props),
|
||||
sortedIDs: getSortedResource(state.dashboards.list, props),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ import {
|
|||
} from 'src/dashboards/actions'
|
||||
import {retainRangesDashTimeV1 as retainRangesDashTimeV1Action} from 'src/dashboards/actions/ranges'
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import GetResources, {
|
||||
ResourceTypes,
|
||||
} from 'src/configuration/components/GetResources'
|
||||
|
||||
// Constants
|
||||
import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants/index'
|
||||
|
@ -71,8 +74,8 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {handleGetDashboards, dashboards} = this.props
|
||||
await handleGetDashboards()
|
||||
const {dashboards} = this.props
|
||||
|
||||
const dashboardIDs = dashboards.map(d => d.id)
|
||||
this.props.retainRangesDashTimeV1(dashboardIDs)
|
||||
}
|
||||
|
@ -98,6 +101,7 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
</Page.Header>
|
||||
<Page.Contents fullWidth={false} scrollable={true}>
|
||||
<div className="col-md-12">
|
||||
<GetResources resource={ResourceTypes.Dashboards}>
|
||||
<DashboardsIndexContents
|
||||
filterComponent={() => (
|
||||
<SearchWidget
|
||||
|
@ -117,6 +121,7 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
onFilterChange={this.handleFilterDashboards}
|
||||
onImportDashboard={this.summonImportOverlay}
|
||||
/>
|
||||
</GetResources>
|
||||
</div>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
|
@ -174,7 +179,11 @@ class DashboardIndex extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
const mstp = (state: AppState): StateProps => {
|
||||
const {dashboards, links, orgs} = state
|
||||
const {
|
||||
dashboards: {list: dashboards},
|
||||
links,
|
||||
orgs,
|
||||
} = state
|
||||
|
||||
return {
|
||||
orgs,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Reducer
|
||||
import reducer from 'src/dashboards/reducers/dashboards'
|
||||
import {dashboardsReducer as reducer} from 'src/dashboards/reducers/dashboards'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
loadDashboard,
|
||||
loadDashboards,
|
||||
deleteDashboard,
|
||||
updateDashboard,
|
||||
deleteCell,
|
||||
setDashboard,
|
||||
setDashboards,
|
||||
removeDashboard,
|
||||
editDashboard,
|
||||
removeCell,
|
||||
addDashboardLabels,
|
||||
removeDashboardLabels,
|
||||
} from 'src/dashboards/actions/'
|
||||
|
@ -15,48 +15,53 @@ import {
|
|||
// Resources
|
||||
import {dashboard} from 'src/dashboards/resources'
|
||||
import {labels} from 'mocks/dummyData'
|
||||
import {RemoteDataState} from '@influxdata/clockface'
|
||||
|
||||
const status = RemoteDataState.Done
|
||||
|
||||
describe('dashboards reducer', () => {
|
||||
it('can load the dashboards', () => {
|
||||
const expected = [dashboard]
|
||||
const actual = reducer([], loadDashboards(expected))
|
||||
it('can set the dashboards', () => {
|
||||
const list = [dashboard]
|
||||
|
||||
const expected = {status, list}
|
||||
const actual = reducer(undefined, setDashboards(status, list))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can delete a dashboard', () => {
|
||||
it('can remove a dashboard', () => {
|
||||
const d2 = {...dashboard, id: '2'}
|
||||
const state = [dashboard, d2]
|
||||
const expected = [dashboard]
|
||||
const actual = reducer(state, deleteDashboard(d2.id))
|
||||
const list = [dashboard, d2]
|
||||
const expected = {list: [dashboard], status}
|
||||
const actual = reducer({list, status}, removeDashboard(d2.id))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can load a dashboard', () => {
|
||||
it('can set a dashboard', () => {
|
||||
const loadedDashboard = {...dashboard, name: 'updated'}
|
||||
const d2 = {...dashboard, id: '2'}
|
||||
const state = [dashboard, d2]
|
||||
const state = {status, list: [dashboard, d2]}
|
||||
|
||||
const expected = [loadedDashboard, d2]
|
||||
const actual = reducer(state, loadDashboard(loadedDashboard))
|
||||
const expected = {status, list: [loadedDashboard, d2]}
|
||||
const actual = reducer(state, setDashboard(loadedDashboard))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can update a dashboard', () => {
|
||||
it('can edit a dashboard', () => {
|
||||
const updates = {...dashboard, name: 'updated dash'}
|
||||
const expected = [updates]
|
||||
const actual = reducer([dashboard], updateDashboard(updates))
|
||||
const expected = {status, list: [updates]}
|
||||
const actual = reducer({status, list: [dashboard]}, editDashboard(updates))
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can delete a cell from a dashboard', () => {
|
||||
const expected = [{...dashboard, cells: []}]
|
||||
it('can remove a cell from a dashboard', () => {
|
||||
const expected = {status, list: [{...dashboard, cells: []}]}
|
||||
const actual = reducer(
|
||||
[dashboard],
|
||||
deleteCell(dashboard, dashboard.cells[0])
|
||||
{status, list: [dashboard]},
|
||||
removeCell(dashboard, dashboard.cells[0])
|
||||
)
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
|
@ -64,20 +69,24 @@ describe('dashboards reducer', () => {
|
|||
|
||||
it('can add labels to a dashboard', () => {
|
||||
const dashboardWithoutLabels = {...dashboard, labels: []}
|
||||
const expected = [{...dashboard, labels}]
|
||||
const expected = {status, list: [{...dashboard, labels}]}
|
||||
const actual = reducer(
|
||||
[dashboardWithoutLabels],
|
||||
{status, list: [dashboardWithoutLabels]},
|
||||
addDashboardLabels(dashboardWithoutLabels.id, labels)
|
||||
)
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can delete labels from a dashboard', () => {
|
||||
const dashboardWithLabels = {...dashboard, labels}
|
||||
const expected = [{...dashboard, labels: []}]
|
||||
it('can remove labels from a dashboard', () => {
|
||||
const leftOverLabel = {...labels[0], name: 'wowowowo', id: '3'}
|
||||
const dashboardWithLabels = {
|
||||
...dashboard,
|
||||
labels: [...labels, leftOverLabel],
|
||||
}
|
||||
const expected = {status, list: [{...dashboard, labels: [leftOverLabel]}]}
|
||||
const actual = reducer(
|
||||
[dashboardWithLabels],
|
||||
{status, list: [dashboardWithLabels]},
|
||||
removeDashboardLabels(dashboardWithLabels.id, labels)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,83 +1,111 @@
|
|||
import {Action, ActionTypes} from 'src/dashboards/actions'
|
||||
import {Dashboard} from 'src/types'
|
||||
// Libraries
|
||||
import {produce} from 'immer'
|
||||
import _ from 'lodash'
|
||||
|
||||
type State = Dashboard[]
|
||||
// Types
|
||||
import {Action, ActionTypes} from 'src/dashboards/actions'
|
||||
import {Dashboard, RemoteDataState} from 'src/types'
|
||||
|
||||
export default (state: State = [], action: Action): State => {
|
||||
export interface DashboardsState {
|
||||
list: Dashboard[]
|
||||
status: RemoteDataState
|
||||
}
|
||||
|
||||
const initialState = () => ({
|
||||
list: [],
|
||||
status: RemoteDataState.NotStarted,
|
||||
})
|
||||
|
||||
export const dashboardsReducer = (
|
||||
state: DashboardsState = initialState(),
|
||||
action: Action
|
||||
): DashboardsState => {
|
||||
return produce(state, draftState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadDashboards: {
|
||||
const {dashboards} = action.payload
|
||||
case ActionTypes.SetDashboards: {
|
||||
const {list, status} = action.payload
|
||||
|
||||
return [...dashboards]
|
||||
draftState.status = status
|
||||
if (list) {
|
||||
draftState.list = list
|
||||
}
|
||||
|
||||
case ActionTypes.DeleteDashboard: {
|
||||
const {dashboardID} = action.payload
|
||||
|
||||
return [...state.filter(d => d.id !== dashboardID)]
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.LoadDashboard: {
|
||||
case ActionTypes.RemoveDashboard: {
|
||||
const {id} = action.payload
|
||||
draftState.list = draftState.list.filter(l => l.id !== id)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.SetDashboard: {
|
||||
const {dashboard} = action.payload
|
||||
draftState.list = _.unionBy([dashboard], state.list, 'id')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.EditDashboard: {
|
||||
const {dashboard} = action.payload
|
||||
|
||||
const newDashboards = _.unionBy([dashboard], state, 'id')
|
||||
|
||||
return newDashboards
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboard.id) {
|
||||
return dashboard
|
||||
}
|
||||
|
||||
case ActionTypes.UpdateDashboard: {
|
||||
const {dashboard} = action.payload
|
||||
const newState = state.map(d =>
|
||||
d.id === dashboard.id ? {...dashboard} : d
|
||||
)
|
||||
|
||||
return [...newState]
|
||||
}
|
||||
|
||||
case ActionTypes.DeleteCell: {
|
||||
const {dashboard, cell} = action.payload
|
||||
const newState = state.map(d => {
|
||||
if (d.id !== dashboard.id) {
|
||||
return {...d}
|
||||
}
|
||||
|
||||
const cells = d.cells.filter(c => c.id !== cell.id)
|
||||
return {...d, cells}
|
||||
return d
|
||||
})
|
||||
|
||||
return [...newState]
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveCell: {
|
||||
const {dashboard, cell} = action.payload
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboard.id) {
|
||||
const cells = d.cells.filter(c => c.id !== cell.id)
|
||||
d.cells = cells
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.AddDashboardLabels: {
|
||||
const {dashboardID, labels} = action.payload
|
||||
|
||||
const newState = state.map(d => {
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboardID) {
|
||||
return {...d, labels: [...d.labels, ...labels]}
|
||||
d.labels = [...d.labels, ...labels]
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
|
||||
return [...newState]
|
||||
return
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveDashboardLabels: {
|
||||
const {dashboardID, labels} = action.payload
|
||||
|
||||
const newState = state.map(d => {
|
||||
draftState.list = draftState.list.map(d => {
|
||||
if (d.id === dashboardID) {
|
||||
const updatedLabels = d.labels.filter(l => {
|
||||
return !labels.includes(l)
|
||||
const updatedLabels = d.labels.filter(el => {
|
||||
const labelToRemove = labels.find(l => l.id === el.id)
|
||||
|
||||
return !labelToRemove
|
||||
})
|
||||
return {...d, labels: updatedLabels}
|
||||
|
||||
d.labels = updatedLabels
|
||||
}
|
||||
|
||||
return d
|
||||
})
|
||||
|
||||
return [...newState]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ export const getViewsForDashboard = (
|
|||
state: AppState,
|
||||
dashboardID: string
|
||||
): View[] => {
|
||||
const dashboard = state.dashboards.find(
|
||||
const dashboard = state.dashboards.list.find(
|
||||
dashboard => dashboard.id === dashboardID
|
||||
)
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@ class SaveAsCellForm extends PureComponent<Props, State> {
|
|||
const mstp = (state: AppState): StateProps => {
|
||||
const {
|
||||
orgs,
|
||||
dashboards,
|
||||
dashboards: {list: dashboards},
|
||||
timeMachines: {
|
||||
timeMachines: {de},
|
||||
},
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
incrementCurrentStepIndex,
|
||||
decrementCurrentStepIndex,
|
||||
} from 'src/dataLoaders/actions/steps'
|
||||
import {createDashboardsForPlugins as createDashboardsForPluginsAction} from 'src/protos/actions'
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
|
||||
// APIs
|
||||
|
@ -52,7 +51,6 @@ interface DispatchProps {
|
|||
onDecrementStep: typeof decrementCurrentStepIndex
|
||||
notify: typeof notifyAction
|
||||
onSaveTelegrafConfig: typeof createOrUpdateTelegrafConfigAsync
|
||||
createDashboardsForPlugins: typeof createDashboardsForPluginsAction
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
|
@ -244,7 +242,6 @@ const mdtp: DispatchProps = {
|
|||
onSetActiveTelegrafPlugin: setActiveTelegrafPlugin,
|
||||
onSetPluginConfiguration: setPluginConfiguration,
|
||||
onSaveTelegrafConfig: createOrUpdateTelegrafConfigAsync,
|
||||
createDashboardsForPlugins: createDashboardsForPluginsAction,
|
||||
notify: notifyAction,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,21 +5,17 @@ import {
|
|||
ITask as Task,
|
||||
ITaskTemplate,
|
||||
} from '@influxdata/influx'
|
||||
import {Dashboard} from 'src/types'
|
||||
|
||||
// API
|
||||
import {client} from 'src/utils/api'
|
||||
import {getDashboardsByOrgID} from 'src/dashboards/apis/v2/index'
|
||||
import {createTaskFromTemplate as createTaskFromTemplateAJAX} from 'src/templates/api'
|
||||
|
||||
export enum ActionTypes {
|
||||
GetTasks = 'GET_TASKS',
|
||||
PopulateTasks = 'POPULATE_TASKS',
|
||||
getDashboards = 'GET_DASHBOARDS',
|
||||
PopulateDashboards = 'POPULATE_DASHBOARDS',
|
||||
}
|
||||
|
||||
export type Actions = PopulateTasks | PopulateDashboards
|
||||
export type Actions = PopulateTasks
|
||||
|
||||
export interface PopulateTasks {
|
||||
type: ActionTypes.PopulateTasks
|
||||
|
@ -36,23 +32,6 @@ export const getTasks = (orgID: string) => async dispatch => {
|
|||
dispatch(populateTasks(tasks))
|
||||
}
|
||||
|
||||
export interface PopulateDashboards {
|
||||
type: ActionTypes.PopulateDashboards
|
||||
payload: {dashboards: Dashboard[]}
|
||||
}
|
||||
|
||||
export const populateDashboards = (
|
||||
dashboards: Dashboard[]
|
||||
): PopulateDashboards => ({
|
||||
type: ActionTypes.PopulateDashboards,
|
||||
payload: {dashboards},
|
||||
})
|
||||
|
||||
export const getDashboards = (orgID: string) => async dispatch => {
|
||||
const dashboards = await getDashboardsByOrgID(orgID)
|
||||
dispatch(populateDashboards(dashboards))
|
||||
}
|
||||
|
||||
export const createScraper = (scraper: ScraperTargetRequest) => async () => {
|
||||
await client.scrapers.create(scraper)
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ import {DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants/index'
|
|||
|
||||
// Types
|
||||
import {IconFont} from '@influxdata/clockface'
|
||||
import {Notification} from 'src/types/notifications'
|
||||
import {Dashboard} from 'src/types'
|
||||
import {Dashboard, AppState, Notification} from 'src/types'
|
||||
|
||||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -39,12 +38,14 @@ interface DispatchProps {
|
|||
}
|
||||
|
||||
interface OwnProps {
|
||||
dashboards: Dashboard[]
|
||||
onChange: () => void
|
||||
orgID: string
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps & WithRouterProps
|
||||
interface StateProps {
|
||||
dashboards: Dashboard[]
|
||||
}
|
||||
|
||||
type Props = DispatchProps & StateProps & OwnProps & WithRouterProps
|
||||
|
||||
interface State {
|
||||
searchTerm: string
|
||||
|
@ -149,13 +150,19 @@ class Dashboards extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const mstp = (state: AppState, props: OwnProps): StateProps => {
|
||||
const dashboards = state.dashboards.list.filter(d => d.orgID === props.orgID)
|
||||
|
||||
return {dashboards}
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
notify: notifyAction,
|
||||
handleDeleteDashboard: deleteDashboardAsync,
|
||||
handleUpdateDashboard: updateDashboardAsync,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, OwnProps>(
|
||||
null,
|
||||
export default connect<StateProps, DispatchProps, OwnProps>(
|
||||
mstp,
|
||||
mdtp
|
||||
)(withRouter(Dashboards))
|
||||
|
|
|
@ -9,21 +9,19 @@ import OrganizationNavigation from 'src/organizations/components/OrganizationNav
|
|||
import OrgHeader from 'src/organizations/containers/OrgHeader'
|
||||
import {Tabs} from 'src/clockface'
|
||||
import {Page} from 'src/pageLayout'
|
||||
import {SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
|
||||
import TabbedPageSection from 'src/shared/components/tabbed_page/TabbedPageSection'
|
||||
import Dashboards from 'src/organizations/components/Dashboards'
|
||||
import GetResources, {
|
||||
ResourceTypes,
|
||||
} from 'src/configuration/components/GetResources'
|
||||
|
||||
//Actions
|
||||
import * as NotificationsActions from 'src/types/actions/notifications'
|
||||
import * as notifyActions from 'src/shared/actions/notifications'
|
||||
import {
|
||||
getDashboards as getDashboardsAction,
|
||||
populateDashboards as populateDashboardsAction,
|
||||
} from 'src/organizations/actions/orgView'
|
||||
|
||||
// Types
|
||||
import {Organization} from '@influxdata/influx'
|
||||
import {AppState, Dashboard} from 'src/types'
|
||||
import {AppState} from 'src/types'
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
interface RouterProps {
|
||||
|
@ -34,13 +32,10 @@ interface RouterProps {
|
|||
|
||||
interface DispatchProps {
|
||||
notify: NotificationsActions.PublishNotificationActionCreator
|
||||
getDashboards: typeof getDashboardsAction
|
||||
populateDashboards: typeof populateDashboardsAction
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
org: Organization
|
||||
dashboards: Dashboard[]
|
||||
}
|
||||
|
||||
type Props = WithRouterProps & RouterProps & DispatchProps & StateProps
|
||||
|
@ -51,25 +46,6 @@ interface State {
|
|||
|
||||
@ErrorHandling
|
||||
class OrgDashboardsIndex extends Component<Props, State> {
|
||||
public state = {
|
||||
loadingState: RemoteDataState.NotStarted,
|
||||
}
|
||||
|
||||
public componentDidMount = async () => {
|
||||
this.setState({loadingState: RemoteDataState.Loading})
|
||||
|
||||
const {getDashboards, org} = this.props
|
||||
|
||||
await getDashboards(org.id)
|
||||
|
||||
this.setState({loadingState: RemoteDataState.Done})
|
||||
}
|
||||
|
||||
public componentWillUnmount = async () => {
|
||||
const {populateDashboards} = this.props
|
||||
populateDashboards([])
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {org} = this.props
|
||||
|
||||
|
@ -87,7 +63,9 @@ class OrgDashboardsIndex extends Component<Props, State> {
|
|||
url="dashboards"
|
||||
title="Dashboards"
|
||||
>
|
||||
{this.orgsDashboardsPage}
|
||||
<GetResources resource={ResourceTypes.Dashboards}>
|
||||
<Dashboards orgID={org.id} />
|
||||
</GetResources>
|
||||
</TabbedPageSection>
|
||||
</Tabs.TabContents>
|
||||
</Tabs>
|
||||
|
@ -98,49 +76,18 @@ class OrgDashboardsIndex extends Component<Props, State> {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private get orgsDashboardsPage() {
|
||||
const {org, dashboards} = this.props
|
||||
const {loadingState} = this.state
|
||||
return (
|
||||
<SpinnerContainer
|
||||
loading={loadingState}
|
||||
spinnerComponent={<TechnoSpinner />}
|
||||
>
|
||||
<Dashboards
|
||||
dashboards={dashboards}
|
||||
onChange={this.getDashboards}
|
||||
orgID={org.id}
|
||||
/>
|
||||
</SpinnerContainer>
|
||||
)
|
||||
}
|
||||
|
||||
private getDashboards = async () => {
|
||||
const {getDashboards, org} = this.props
|
||||
|
||||
await getDashboards(org.id)
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = (state: AppState, props: Props): StateProps => {
|
||||
const {
|
||||
orgs,
|
||||
orgView: {dashboards},
|
||||
} = state
|
||||
|
||||
const org = orgs.find(o => o.id === props.params.orgID)
|
||||
const org = state.orgs.find(o => o.id === props.params.orgID)
|
||||
|
||||
return {
|
||||
org,
|
||||
dashboards,
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
notify: notifyActions.notify,
|
||||
getDashboards: getDashboardsAction,
|
||||
populateDashboards: populateDashboardsAction,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, {}>(
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import {ITask as Task, Telegraf} from '@influxdata/influx'
|
||||
import {Dashboard} from 'src/types'
|
||||
import {Actions, ActionTypes} from 'src/organizations/actions/orgView'
|
||||
|
||||
export interface OrgViewState {
|
||||
tasks: Task[]
|
||||
dashboards: Dashboard[]
|
||||
telegrafs: Telegraf[]
|
||||
}
|
||||
|
||||
const defaultState: OrgViewState = {
|
||||
tasks: [],
|
||||
dashboards: [],
|
||||
telegrafs: [],
|
||||
}
|
||||
|
||||
|
@ -18,8 +15,6 @@ export default (state = defaultState, action: Actions): OrgViewState => {
|
|||
switch (action.type) {
|
||||
case ActionTypes.PopulateTasks:
|
||||
return {...state, tasks: action.payload.tasks}
|
||||
case ActionTypes.PopulateDashboards:
|
||||
return {...state, dashboards: action.payload.dashboards}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
// Libraries
|
||||
import {Dispatch} from 'redux'
|
||||
|
||||
import {client} from 'src/utils/api'
|
||||
|
||||
// Utils
|
||||
import {addDashboardIDToCells} from 'src/dashboards/apis/'
|
||||
|
||||
// Actions
|
||||
import {loadDashboard} from 'src/dashboards/actions/'
|
||||
import {notify} from 'src/shared/actions/notifications'
|
||||
|
||||
// Types
|
||||
import {Proto} from '@influxdata/influx'
|
||||
import {GetState, Dashboard} from 'src/types'
|
||||
import {ConfigurationState} from 'src/types/dataLoaders'
|
||||
|
||||
// Const
|
||||
import {
|
||||
TelegrafDashboardFailed,
|
||||
TelegrafDashboardCreated,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadProto = 'LOAD_PROTO',
|
||||
}
|
||||
|
||||
export type Action = LoadProtoAction
|
||||
|
||||
interface LoadProtoAction {
|
||||
type: ActionTypes.LoadProto
|
||||
payload: {
|
||||
proto: Proto
|
||||
}
|
||||
}
|
||||
|
||||
export const loadProto = (proto: Proto): LoadProtoAction => ({
|
||||
type: ActionTypes.LoadProto,
|
||||
payload: {proto},
|
||||
})
|
||||
|
||||
export const getProtos = () => async (dispatch: Dispatch<Action>) => {
|
||||
try {
|
||||
const protos = await client.protos.getAll()
|
||||
|
||||
protos.forEach(p => {
|
||||
dispatch(loadProto(p))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const createDashFromProto = (
|
||||
protoID: string,
|
||||
orgID: string
|
||||
) => async dispatch => {
|
||||
try {
|
||||
const dashboards = await client.dashboards.createFromProto(protoID, orgID)
|
||||
|
||||
dashboards.forEach((d: Dashboard) => {
|
||||
const updatedDashboard = {
|
||||
...d,
|
||||
cells: addDashboardIDToCells(d.cells, d.id),
|
||||
}
|
||||
dispatch(loadDashboard(updatedDashboard))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const createDashboardsForPlugins = () => async (
|
||||
dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
await dispatch(getProtos())
|
||||
const {
|
||||
dataLoading: {
|
||||
dataLoaders: {telegrafPlugins},
|
||||
steps: {orgID},
|
||||
},
|
||||
protos,
|
||||
} = getState()
|
||||
|
||||
const plugins = []
|
||||
|
||||
try {
|
||||
telegrafPlugins.forEach(tp => {
|
||||
if (tp.configured === ConfigurationState.Configured) {
|
||||
if (protos[tp.name]) {
|
||||
dispatch(createDashFromProto(protos[tp.name].id, orgID))
|
||||
plugins.push(tp.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (plugins.length) {
|
||||
dispatch(notify(TelegrafDashboardCreated(plugins)))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
dispatch(notify(TelegrafDashboardFailed()))
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Types
|
||||
import {Action} from 'src/protos/actions/'
|
||||
import {Proto} from '@influxdata/influx'
|
||||
|
||||
export interface ProtosState {
|
||||
[protoName: string]: Proto
|
||||
}
|
||||
|
||||
const protosReducer = (state: ProtosState = {}, action: Action) => {
|
||||
switch (action.type) {
|
||||
case 'LOAD_PROTO': {
|
||||
const {
|
||||
proto,
|
||||
proto: {name},
|
||||
} = action.payload
|
||||
|
||||
return {
|
||||
...state,
|
||||
[name]: {...proto},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default protosReducer
|
|
@ -13,7 +13,7 @@ import persistStateEnhancer from './persistStateEnhancer'
|
|||
import meReducer from 'src/shared/reducers/v2/me'
|
||||
import tasksReducer from 'src/tasks/reducers'
|
||||
import rangesReducer from 'src/dashboards/reducers/ranges'
|
||||
import dashboardsReducer from 'src/dashboards/reducers/dashboards'
|
||||
import {dashboardsReducer} from 'src/dashboards/reducers/dashboards'
|
||||
import viewsReducer from 'src/dashboards/reducers/views'
|
||||
import {timeMachinesReducer} from 'src/timeMachine/reducers'
|
||||
import orgsReducer from 'src/organizations/reducers/orgs'
|
||||
|
@ -21,7 +21,6 @@ import orgViewReducer from 'src/organizations/reducers/orgView'
|
|||
import onboardingReducer from 'src/onboarding/reducers'
|
||||
import noteEditorReducer from 'src/dashboards/reducers/notes'
|
||||
import dataLoadingReducer from 'src/dataLoaders/reducers'
|
||||
import protosReducer from 'src/protos/reducers'
|
||||
import {variablesReducer} from 'src/variables/reducers'
|
||||
import {labelsReducer} from 'src/labels/reducers'
|
||||
import {bucketsReducer} from 'src/buckets/reducers'
|
||||
|
@ -51,7 +50,6 @@ export const rootReducer = combineReducers<ReducerState>({
|
|||
onboarding: onboardingReducer,
|
||||
noteEditor: noteEditorReducer,
|
||||
dataLoading: dataLoadingReducer,
|
||||
protos: protosReducer,
|
||||
variables: variablesReducer,
|
||||
labels: labelsReducer,
|
||||
buckets: bucketsReducer,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import {Dashboard} from 'src/types/dashboards'
|
||||
import {Organization} from 'src/types/orgs'
|
||||
import {Links} from 'src/types/links'
|
||||
import {Notification} from 'src/types'
|
||||
|
@ -11,7 +10,6 @@ import {MeState} from 'src/shared/reducers/v2/me'
|
|||
import {NoteEditorState} from 'src/dashboards/reducers/notes'
|
||||
import {DataLoadingState} from 'src/dataLoaders/reducers'
|
||||
import {OnboardingState} from 'src/onboarding/reducers'
|
||||
import {ProtosState} from 'src/protos/reducers'
|
||||
import {VariablesState} from 'src/variables/reducers'
|
||||
import {OrgViewState} from 'src/organizations/reducers/orgView'
|
||||
import {LabelsState} from 'src/labels/reducers'
|
||||
|
@ -23,6 +21,7 @@ import {RangeState} from 'src/dashboards/reducers/ranges'
|
|||
import {ViewsState} from 'src/dashboards/reducers/views'
|
||||
import {ScrapersState} from 'src/scrapers/reducers'
|
||||
import {UserSettingsState} from 'src/userSettings/reducers'
|
||||
import {DashboardsState} from 'src/dashboards/reducers/dashboards'
|
||||
|
||||
export interface AppState {
|
||||
VERSION: string
|
||||
|
@ -33,7 +32,7 @@ export interface AppState {
|
|||
app: AppPresentationState
|
||||
ranges: RangeState
|
||||
views: ViewsState
|
||||
dashboards: Dashboard[]
|
||||
dashboards: DashboardsState
|
||||
notifications: Notification[]
|
||||
timeMachines: TimeMachinesState
|
||||
routing: RouterState
|
||||
|
@ -45,7 +44,6 @@ export interface AppState {
|
|||
onboarding: OnboardingState
|
||||
noteEditor: NoteEditorState
|
||||
dataLoading: DataLoadingState
|
||||
protos: ProtosState
|
||||
variables: VariablesState
|
||||
tokens: AuthorizationsState
|
||||
templates: TemplatesState
|
||||
|
|
Loading…
Reference in New Issue