Merge branch 'master' into logs-viewer/filter-bar

pull/3582/head
Alex P 2018-06-06 11:34:27 -07:00
commit 1791e4ec53
147 changed files with 2559 additions and 1503 deletions

View File

@ -3,6 +3,7 @@
### Features
1. [#3522](https://github.com/influxdata/chronograf/pull/3522): Add support for Template Variables in Cell Titles
1. [#3559](https://github.com/influxdata/chronograf/pull/3559): Add ability to export and import dashboards
### UI Improvements
@ -11,6 +12,7 @@
### Bug Fixes
1. [#3527](https://github.com/influxdata/chronograf/pull/3527): Ensure cell queries use constraints from TimeSelector
1. [#3573](https://github.com/influxdata/chronograf/pull/3573): Fix Gauge color selection bug
## v1.5.0.0 [2018-05-15-RC]

View File

@ -121,7 +121,7 @@ message Server {
bool Active = 7; // is this the currently active server for the source
string Organization = 8; // Organization is the organization ID that resource belongs to
bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client
string Type = 10; // Type is the kind of the server (e.g. ifql)
string Type = 10; // Type is the kind of the server (e.g. flux)
string MetadataJSON = 11; // JSON byte representation of the metadata
}

View File

@ -368,7 +368,7 @@ type Server struct {
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the server is accepted.
Active bool `json:"active"` // Is this the active server for the source?
Organization string `json:"organization"` // Organization is the organization ID that resource belongs to
Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or ifql)
Type string `json:"type"` // Type is the kind of service (e.g. kapacitor or flux)
Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
}

View File

@ -2730,10 +2730,10 @@ func TestServer(t *testing.T) {
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
"flux": {
"ast": "/chronograf/v1/flux/ast",
"self": "/chronograf/v1/flux",
"suggestions": "/chronograf/v1/flux/suggestions"
}
}
`,
@ -2818,10 +2818,10 @@ func TestServer(t *testing.T) {
"external": {
"statusFeed": ""
},
"ifql": {
"ast": "/chronograf/v1/ifql/ast",
"self": "/chronograf/v1/ifql",
"suggestions": "/chronograf/v1/ifql/suggestions"
"flux": {
"ast": "/chronograf/v1/flux/ast",
"self": "/chronograf/v1/flux",
"suggestions": "/chronograf/v1/flux/suggestions"
}
}
`,

View File

@ -12,41 +12,41 @@ import (
type Params map[string]string
// SuggestionsResponse provides a list of available IFQL functions
// SuggestionsResponse provides a list of available Flux functions
type SuggestionsResponse struct {
Functions []SuggestionResponse `json:"funcs"`
}
// SuggestionResponse provides the parameters available for a given IFQL function
// SuggestionResponse provides the parameters available for a given Flux function
type SuggestionResponse struct {
Name string `json:"name"`
Params Params `json:"params"`
}
type ifqlLinks struct {
type fluxLinks struct {
Self string `json:"self"` // Self link mapping to this resource
Suggestions string `json:"suggestions"` // URL for ifql builder function suggestions
Suggestions string `json:"suggestions"` // URL for flux builder function suggestions
}
type ifqlResponse struct {
Links ifqlLinks `json:"links"`
type fluxResponse struct {
Links fluxLinks `json:"links"`
}
// IFQL returns a list of links for the IFQL API
func (s *Service) IFQL(w http.ResponseWriter, r *http.Request) {
httpAPIIFQL := "/chronograf/v1/ifql"
res := ifqlResponse{
Links: ifqlLinks{
Self: fmt.Sprintf("%s", httpAPIIFQL),
Suggestions: fmt.Sprintf("%s/suggestions", httpAPIIFQL),
// Flux returns a list of links for the Flux API
func (s *Service) Flux(w http.ResponseWriter, r *http.Request) {
httpAPIFlux := "/chronograf/v1/flux"
res := fluxResponse{
Links: fluxLinks{
Self: fmt.Sprintf("%s", httpAPIFlux),
Suggestions: fmt.Sprintf("%s/suggestions", httpAPIFlux),
},
}
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// IFQLSuggestions returns a list of available IFQL functions for the IFQL Builder
func (s *Service) IFQLSuggestions(w http.ResponseWriter, r *http.Request) {
// FluxSuggestions returns a list of available Flux functions for the Flux Builder
func (s *Service) FluxSuggestions(w http.ResponseWriter, r *http.Request) {
completer := query.DefaultCompleter()
names := completer.FunctionNames()
var functions []SuggestionResponse
@ -76,8 +76,8 @@ func (s *Service) IFQLSuggestions(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, s.Logger)
}
// IFQLSuggestion returns the function parameters for the requested function
func (s *Service) IFQLSuggestion(w http.ResponseWriter, r *http.Request) {
// FluxSuggestion returns the function parameters for the requested function
func (s *Service) FluxSuggestion(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
name := httprouter.GetParamFromContext(ctx, "name")
completer := query.DefaultCompleter()
@ -94,7 +94,7 @@ type ASTRequest struct {
Body string `json:"body"`
}
func (s *Service) IFQLAST(w http.ResponseWriter, r *http.Request) {
func (s *Service) FluxAST(w http.ResponseWriter, r *http.Request) {
var request ASTRequest
err := json.NewDecoder(r.Body).Decode(&request)
if err != nil {

View File

@ -5,7 +5,7 @@ import (
"net/url"
)
type getIFQLLinksResponse struct {
type getFluxLinksResponse struct {
AST string `json:"ast"`
Self string `json:"self"`
Suggestions string `json:"suggestions"`

View File

@ -156,11 +156,11 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
router.DELETE("/chronograf/v1/sources/:id", EnsureEditor(service.RemoveSource))
router.GET("/chronograf/v1/sources/:id/health", EnsureViewer(service.SourceHealth))
// IFQL
router.GET("/chronograf/v1/ifql", EnsureViewer(service.IFQL))
router.POST("/chronograf/v1/ifql/ast", EnsureViewer(service.IFQLAST))
router.GET("/chronograf/v1/ifql/suggestions", EnsureViewer(service.IFQLSuggestions))
router.GET("/chronograf/v1/ifql/suggestions/:name", EnsureViewer(service.IFQLSuggestion))
// Flux
router.GET("/chronograf/v1/flux", EnsureViewer(service.Flux))
router.POST("/chronograf/v1/flux/ast", EnsureViewer(service.FluxAST))
router.GET("/chronograf/v1/flux/suggestions", EnsureViewer(service.FluxSuggestions))
router.GET("/chronograf/v1/flux/suggestions/:name", EnsureViewer(service.FluxSuggestion))
// Source Proxy to Influx; Has gzip compression around the handler
influx := gziphandler.GzipHandler(http.HandlerFunc(EnsureViewer(service.Influx)))

View File

@ -44,7 +44,7 @@ type getRoutesResponse struct {
Auth []AuthRoute `json:"auth"` // Location of all auth routes.
Logout *string `json:"logout,omitempty"` // Location of the logout route for all auth routes
ExternalLinks getExternalLinksResponse `json:"external"` // All external links for the client to use
IFQL getIFQLLinksResponse `json:"ifql"`
Flux getFluxLinksResponse `json:"flux"`
}
// AllRoutes is a handler that returns all links to resources in Chronograf server, as well as
@ -95,10 +95,10 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
StatusFeed: &a.StatusFeed,
CustomLinks: customLinks,
},
IFQL: getIFQLLinksResponse{
Self: "/chronograf/v1/ifql",
AST: "/chronograf/v1/ifql/ast",
Suggestions: "/chronograf/v1/ifql/suggestions",
Flux: getFluxLinksResponse{
Self: "/chronograf/v1/flux",
AST: "/chronograf/v1/flux/ast",
Suggestions: "/chronograf/v1/flux/suggestions",
},
}

View File

@ -29,7 +29,7 @@ func TestAllRoutes(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutes not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"ifql":{"ast":"/chronograf/v1/ifql/ast","self":"/chronograf/v1/ifql","suggestions":"/chronograf/v1/ifql/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutes\nwanted\n*%s*\ngot\n*%s*", want, string(body))
@ -67,7 +67,7 @@ func TestAllRoutesWithAuth(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"ifql":{"ast":"/chronograf/v1/ifql/ast","self":"/chronograf/v1/ifql","suggestions":"/chronograf/v1/ifql/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutesWithAuth\nwanted\n*%s*\ngot\n*%s*", want, string(body))
@ -100,7 +100,7 @@ func TestAllRoutesWithExternalLinks(t *testing.T) {
if err := json.Unmarshal(body, &routes); err != nil {
t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response")
}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"ifql":{"ast":"/chronograf/v1/ifql/ast","self":"/chronograf/v1/ifql","suggestions":"/chronograf/v1/ifql/suggestions"}}
want := `{"layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}}
`
if want != string(body) {
t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body))

View File

@ -12,7 +12,7 @@ import (
type postServiceRequest struct {
Name *string `json:"name"` // User facing name of service instance.; Required: true
URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true
Type *string `json:"type"` // Type is the kind of service (e.g. ifql); Required
Type *string `json:"type"` // Type is the kind of service (e.g. flux); Required
Username string `json:"username,omitempty"` // Username for authentication to service
Password string `json:"password,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted.
@ -58,7 +58,7 @@ type service struct {
Username string `json:"username,omitempty"` // Username for authentication to service
Password string `json:"password,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted.
Type string `json:"type"` // Type is the kind of service (e.g. ifql)
Type string `json:"type"` // Type is the kind of service (e.g. flux)
Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service
Links serviceLinks `json:"links"` // Links are URI locations related to service
}
@ -229,7 +229,7 @@ func (s *Service) RemoveService(w http.ResponseWriter, r *http.Request) {
type patchServiceRequest struct {
Name *string `json:"name,omitempty"` // User facing name of service instance.
Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. ifql)
Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. flux)
URL *string `json:"url,omitempty"` // URL for the service
Username *string `json:"username,omitempty"` // Username for service auth
Password *string `json:"password,omitempty"`

View File

@ -1,364 +0,0 @@
import {
getDashboards as getDashboardsAJAX,
updateDashboard as updateDashboardAJAX,
deleteDashboard as deleteDashboardAJAX,
updateDashboardCell as updateDashboardCellAJAX,
addDashboardCell as addDashboardCellAJAX,
deleteDashboardCell as deleteDashboardCellAJAX,
runTemplateVariableQuery,
} from 'src/dashboards/apis'
import {notify} from 'shared/actions/notifications'
import {errorThrown} from 'shared/actions/errors'
import {
getNewDashboardCell,
getClonedDashboardCell,
} from 'src/dashboards/utils/cellGetters'
import {
notifyDashboardDeleted,
notifyDashboardDeleteFailed,
notifyCellAdded,
notifyCellDeleted,
} from 'shared/copy/notifications'
import {
TEMPLATE_VARIABLE_SELECTED,
TEMPLATE_VARIABLES_SELECTED_BY_NAME,
} from 'shared/constants/actionTypes'
import {makeQueryForTemplate} from 'src/dashboards/utils/templateVariableQueryGenerator'
import parsers from 'shared/parsing'
export const loadDashboards = (dashboards, dashboardID) => ({
type: 'LOAD_DASHBOARDS',
payload: {
dashboards,
dashboardID,
},
})
export const loadDeafaultDashTimeV1 = dashboardID => ({
type: 'ADD_DASHBOARD_TIME_V1',
payload: {
dashboardID,
},
})
export const addDashTimeV1 = (dashboardID, timeRange) => ({
type: 'ADD_DASHBOARD_TIME_V1',
payload: {
dashboardID,
timeRange,
},
})
export const setDashTimeV1 = (dashboardID, timeRange) => ({
type: 'SET_DASHBOARD_TIME_V1',
payload: {
dashboardID,
timeRange,
},
})
export const setTimeRange = timeRange => ({
type: 'SET_DASHBOARD_TIME_RANGE',
payload: {
timeRange,
},
})
export const updateDashboard = dashboard => ({
type: 'UPDATE_DASHBOARD',
payload: {
dashboard,
},
})
export const deleteDashboard = dashboard => ({
type: 'DELETE_DASHBOARD',
payload: {
dashboard,
dashboardID: dashboard.id,
},
})
export const deleteDashboardFailed = dashboard => ({
type: 'DELETE_DASHBOARD_FAILED',
payload: {
dashboard,
},
})
export const updateDashboardCells = (dashboard, cells) => ({
type: 'UPDATE_DASHBOARD_CELLS',
payload: {
dashboard,
cells,
},
})
export const syncDashboardCell = (dashboard, cell) => ({
type: 'SYNC_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
export const addDashboardCell = (dashboard, cell) => ({
type: 'ADD_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
export const editDashboardCell = (dashboard, x, y, isEditing) => ({
type: 'EDIT_DASHBOARD_CELL',
// x and y coords are used as a alternative to cell ids, which are not
// universally unique, and cannot be because React depends on a
// quasi-predictable ID for keys. Since cells cannot overlap, coordinates act
// as a suitable id
payload: {
dashboard,
x, // x-coord of the cell to be edited
y, // y-coord of the cell to be edited
isEditing,
},
})
export const cancelEditCell = (dashboardID, cellID) => ({
type: 'CANCEL_EDIT_CELL',
payload: {
dashboardID,
cellID,
},
})
export const renameDashboardCell = (dashboard, x, y, name) => ({
type: 'RENAME_DASHBOARD_CELL',
payload: {
dashboard,
x, // x-coord of the cell to be renamed
y, // y-coord of the cell to be renamed
name,
},
})
export const deleteDashboardCell = (dashboard, cell) => ({
type: 'DELETE_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
export const editCellQueryStatus = (queryID, status) => ({
type: 'EDIT_CELL_QUERY_STATUS',
payload: {
queryID,
status,
},
})
export const templateVariableSelected = (dashboardID, templateID, values) => ({
type: TEMPLATE_VARIABLE_SELECTED,
payload: {
dashboardID,
templateID,
values,
},
})
export const templateVariablesSelectedByName = (dashboardID, query) => ({
type: TEMPLATE_VARIABLES_SELECTED_BY_NAME,
payload: {
dashboardID,
query,
},
})
export const editTemplateVariableValues = (
dashboardID,
templateID,
values
) => ({
type: 'EDIT_TEMPLATE_VARIABLE_VALUES',
payload: {
dashboardID,
templateID,
values,
},
})
export const setHoverTime = hoverTime => ({
type: 'SET_HOVER_TIME',
payload: {
hoverTime,
},
})
export const setActiveCell = activeCellID => ({
type: 'SET_ACTIVE_CELL',
payload: {
activeCellID,
},
})
// Async Action Creators
export const getDashboardsAsync = () => async dispatch => {
try {
const {
data: {dashboards},
} = await getDashboardsAJAX()
dispatch(loadDashboards(dashboards))
return dashboards
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
const removeUnselectedTemplateValues = dashboard => {
const templates = dashboard.templates.map(template => {
if (template.type === 'csv') {
return template
}
const value = template.values.find(val => val.selected)
const values = value ? [value] : []
return {...template, values}
})
return templates
}
export const putDashboard = dashboard => async dispatch => {
try {
// save only selected template values to server
const templatesWithOnlySelectedValues = removeUnselectedTemplateValues(
dashboard
)
const {
data: dashboardWithOnlySelectedTemplateValues,
} = await updateDashboardAJAX({
...dashboard,
templates: templatesWithOnlySelectedValues,
})
// save all template values to redux
dispatch(
updateDashboard({
...dashboardWithOnlySelectedTemplateValues,
templates: dashboard.templates,
})
)
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const putDashboardByID = dashboardID => async (dispatch, getState) => {
try {
const {
dashboardUI: {dashboards},
} = getState()
const dashboard = dashboards.find(d => d.id === +dashboardID)
const templates = removeUnselectedTemplateValues(dashboard)
await updateDashboardAJAX({...dashboard, templates})
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const updateDashboardCell = (dashboard, cell) => async dispatch => {
try {
const {data} = await updateDashboardCellAJAX(cell)
dispatch(syncDashboardCell(dashboard, data))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const deleteDashboardAsync = dashboard => async dispatch => {
dispatch(deleteDashboard(dashboard))
try {
await deleteDashboardAJAX(dashboard)
dispatch(notify(notifyDashboardDeleted(dashboard.name)))
} catch (error) {
dispatch(
errorThrown(
error,
notifyDashboardDeleteFailed(dashboard.name, error.data.message)
)
)
dispatch(deleteDashboardFailed(dashboard))
}
}
export const addDashboardCellAsync = (
dashboard,
cellType
) => async dispatch => {
try {
const {data} = await addDashboardCellAJAX(
dashboard,
getNewDashboardCell(dashboard, cellType)
)
dispatch(addDashboardCell(dashboard, data))
dispatch(notify(notifyCellAdded(data.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const cloneDashboardCellAsync = (dashboard, cell) => async dispatch => {
try {
const clonedCell = getClonedDashboardCell(dashboard, cell)
const {data} = await addDashboardCellAJAX(dashboard, clonedCell)
dispatch(addDashboardCell(dashboard, data))
dispatch(notify(notifyCellAdded(clonedCell.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const deleteDashboardCellAsync = (dashboard, cell) => async dispatch => {
try {
await deleteDashboardCellAJAX(cell)
dispatch(deleteDashboardCell(dashboard, cell))
dispatch(notify(notifyCellDeleted(cell.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const updateTempVarValues = (source, dashboard) => async dispatch => {
try {
const tempsWithQueries = dashboard.templates.filter(
({query}) => !!query.influxql
)
const asyncQueries = tempsWithQueries.map(({query}) =>
runTemplateVariableQuery(source, {query: makeQueryForTemplate(query)})
)
const results = await Promise.all(asyncQueries)
results.forEach(({data}, i) => {
const {type, query, id} = tempsWithQueries[i]
const parsed = parsers[type](data, query.tagKey || query.measurement)
const vals = parsed[type]
dispatch(editTemplateVariableValues(dashboard.id, id, vals))
})
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}

View File

@ -0,0 +1,688 @@
import _ from 'lodash'
import {
getDashboards as getDashboardsAJAX,
updateDashboard as updateDashboardAJAX,
deleteDashboard as deleteDashboardAJAX,
updateDashboardCell as updateDashboardCellAJAX,
addDashboardCell as addDashboardCellAJAX,
deleteDashboardCell as deleteDashboardCellAJAX,
runTemplateVariableQuery,
createDashboard as createDashboardAJAX,
} from 'src/dashboards/apis'
import {getMe} from 'src/shared/apis/auth'
import {notify} from 'src/shared/actions/notifications'
import {errorThrown} from 'src/shared/actions/errors'
import {
getNewDashboardCell,
getClonedDashboardCell,
} from 'src/dashboards/utils/cellGetters'
import {
notifyDashboardDeleted,
notifyDashboardDeleteFailed,
notifyCellAdded,
notifyCellDeleted,
notifyDashboardImportFailed,
notifyDashboardImported,
} from 'src/shared/copy/notifications'
import {
TEMPLATE_VARIABLE_SELECTED,
TEMPLATE_VARIABLES_SELECTED_BY_NAME,
} from 'src/shared/constants/actionTypes'
import {CellType} from 'src/types/dashboard'
import {makeQueryForTemplate} from 'src/dashboards/utils/templateVariableQueryGenerator'
import parsers from 'src/shared/parsing'
import {getDeep} from 'src/utils/wrappers'
import {Dashboard, TimeRange, Cell, Query, Source, Template} from 'src/types'
interface LoadDashboardsAction {
type: 'LOAD_DASHBOARDS'
payload: {
dashboards: Dashboard[]
dashboardID: string
}
}
export const loadDashboards = (
dashboards: Dashboard[],
dashboardID?: string
): LoadDashboardsAction => ({
type: 'LOAD_DASHBOARDS',
payload: {
dashboards,
dashboardID,
},
})
interface LoadDeafaultDashTimeV1Action {
type: 'ADD_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
}
}
export const loadDeafaultDashTimeV1 = (
dashboardID: string
): LoadDeafaultDashTimeV1Action => ({
type: 'ADD_DASHBOARD_TIME_V1',
payload: {
dashboardID,
},
})
interface AddDashTimeV1Action {
type: 'ADD_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
timeRange: TimeRange
}
}
export const addDashTimeV1 = (
dashboardID: string,
timeRange: TimeRange
): AddDashTimeV1Action => ({
type: 'ADD_DASHBOARD_TIME_V1',
payload: {
dashboardID,
timeRange,
},
})
interface SetDashTimeV1Action {
type: 'SET_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
timeRange: TimeRange
}
}
export const setDashTimeV1 = (
dashboardID: string,
timeRange: TimeRange
): SetDashTimeV1Action => ({
type: 'SET_DASHBOARD_TIME_V1',
payload: {
dashboardID,
timeRange,
},
})
interface SetTimeRangeAction {
type: 'SET_DASHBOARD_TIME_RANGE'
payload: {
timeRange: TimeRange
}
}
export const setTimeRange = (timeRange: TimeRange): SetTimeRangeAction => ({
type: 'SET_DASHBOARD_TIME_RANGE',
payload: {
timeRange,
},
})
interface UpdateDashboardAction {
type: 'UPDATE_DASHBOARD'
payload: {
dashboard: Dashboard
}
}
export const updateDashboard = (
dashboard: Dashboard
): UpdateDashboardAction => ({
type: 'UPDATE_DASHBOARD',
payload: {
dashboard,
},
})
interface CreateDashboardAction {
type: 'CREATE_DASHBOARD'
payload: {
dashboard: Dashboard
}
}
export const createDashboard = (
dashboard: Dashboard
): CreateDashboardAction => ({
type: 'CREATE_DASHBOARD',
payload: {
dashboard,
},
})
interface DeleteDashboardAction {
type: 'DELETE_DASHBOARD'
payload: {
dashboard: Dashboard
dashboardID: number
}
}
export const deleteDashboard = (
dashboard: Dashboard
): DeleteDashboardAction => ({
type: 'DELETE_DASHBOARD',
payload: {
dashboard,
dashboardID: dashboard.id,
},
})
interface DeleteDashboardFailedAction {
type: 'DELETE_DASHBOARD_FAILED'
payload: {
dashboard: Dashboard
}
}
export const deleteDashboardFailed = (
dashboard: Dashboard
): DeleteDashboardFailedAction => ({
type: 'DELETE_DASHBOARD_FAILED',
payload: {
dashboard,
},
})
interface UpdateDashboardCellsAction {
type: 'UPDATE_DASHBOARD_CELLS'
payload: {
dashboard: Dashboard
cells: Cell[]
}
}
export const updateDashboardCells = (
dashboard: Dashboard,
cells: Cell[]
): UpdateDashboardCellsAction => ({
type: 'UPDATE_DASHBOARD_CELLS',
payload: {
dashboard,
cells,
},
})
interface SyncDashboardCellAction {
type: 'SYNC_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
cell: Cell
}
}
export const syncDashboardCell = (
dashboard: Dashboard,
cell: Cell
): SyncDashboardCellAction => ({
type: 'SYNC_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
interface AddDashboardCellAction {
type: 'ADD_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
cell: Cell
}
}
export const addDashboardCell = (
dashboard: Dashboard,
cell: Cell
): AddDashboardCellAction => ({
type: 'ADD_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
interface EditDashboardCellAction {
type: 'EDIT_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
x: number
y: number
isEditing: boolean
}
}
export const editDashboardCell = (
dashboard: Dashboard,
x: number,
y: number,
isEditing: boolean
): EditDashboardCellAction => ({
type: 'EDIT_DASHBOARD_CELL',
// x and y coords are used as a alternative to cell ids, which are not
// universally unique, and cannot be because React depends on a
// quasi-predictable ID for keys. Since cells cannot overlap, coordinates act
// as a suitable id
payload: {
dashboard,
x, // x-coord of the cell to be edited
y, // y-coord of the cell to be edited
isEditing,
},
})
interface CancelEditCellAction {
type: 'CANCEL_EDIT_CELL'
payload: {
dashboardID: string
cellID: string
}
}
export const cancelEditCell = (
dashboardID: string,
cellID: string
): CancelEditCellAction => ({
type: 'CANCEL_EDIT_CELL',
payload: {
dashboardID,
cellID,
},
})
interface RenameDashboardCellAction {
type: 'RENAME_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
x: number
y: number
name: string
}
}
export const renameDashboardCell = (
dashboard: Dashboard,
x: number,
y: number,
name: string
): RenameDashboardCellAction => ({
type: 'RENAME_DASHBOARD_CELL',
payload: {
dashboard,
x, // x-coord of the cell to be renamed
y, // y-coord of the cell to be renamed
name,
},
})
interface DeleteDashboardCellAction {
type: 'DELETE_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
cell: Cell
}
}
export const deleteDashboardCell = (
dashboard: Dashboard,
cell: Cell
): DeleteDashboardCellAction => ({
type: 'DELETE_DASHBOARD_CELL',
payload: {
dashboard,
cell,
},
})
interface EditCellQueryStatusAction {
type: 'EDIT_CELL_QUERY_STATUS'
payload: {
queryID: string
status: string
}
}
export const editCellQueryStatus = (
queryID: string,
status: string
): EditCellQueryStatusAction => ({
type: 'EDIT_CELL_QUERY_STATUS',
payload: {
queryID,
status,
},
})
interface TemplateVariableSelectedAction {
type: 'TEMPLATE_VARIABLE_SELECTED'
payload: {
dashboardID: string
templateID: string
values: any[]
}
}
export const templateVariableSelected = (
dashboardID: string,
templateID: string,
values
): TemplateVariableSelectedAction => ({
type: TEMPLATE_VARIABLE_SELECTED,
payload: {
dashboardID,
templateID,
values,
},
})
interface TemplateVariablesSelectedByNameAction {
type: 'TEMPLATE_VARIABLES_SELECTED_BY_NAME'
payload: {
dashboardID: string
query: Query
}
}
export const templateVariablesSelectedByName = (
dashboardID: string,
query: Query
): TemplateVariablesSelectedByNameAction => ({
type: TEMPLATE_VARIABLES_SELECTED_BY_NAME,
payload: {
dashboardID,
query,
},
})
interface EditTemplateVariableValuesAction {
type: 'EDIT_TEMPLATE_VARIABLE_VALUES'
payload: {
dashboardID: number
templateID: string
values: any[]
}
}
export const editTemplateVariableValues = (
dashboardID: number,
templateID: string,
values
): EditTemplateVariableValuesAction => ({
type: 'EDIT_TEMPLATE_VARIABLE_VALUES',
payload: {
dashboardID,
templateID,
values,
},
})
interface SetHoverTimeAction {
type: 'SET_HOVER_TIME'
payload: {
hoverTime: string
}
}
export const setHoverTime = (hoverTime: string): SetHoverTimeAction => ({
type: 'SET_HOVER_TIME',
payload: {
hoverTime,
},
})
interface SetActiveCellAction {
type: 'SET_ACTIVE_CELL'
payload: {
activeCellID: string
}
}
export const setActiveCell = (activeCellID: string): SetActiveCellAction => ({
type: 'SET_ACTIVE_CELL',
payload: {
activeCellID,
},
})
// Async Action Creators
export const getDashboardsAsync = () => async (
dispatch
): Promise<Dashboard[] | void> => {
try {
const {
data: {dashboards},
} = await getDashboardsAJAX()
dispatch(loadDashboards(dashboards))
return dashboards
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const getChronografVersion = () => async (): Promise<string | void> => {
try {
const results = await getMe()
const version = _.get(results, 'headers.x-chronograf-version')
return version
} catch (error) {
console.error(error)
}
}
const removeUnselectedTemplateValues = (dashboard: Dashboard): Template[] => {
const templates = getDeep<Template[]>(dashboard, 'templates', []).map(
template => {
if (template.type === 'csv') {
return template
}
const value = template.values.find(val => val.selected)
const values = value ? [value] : []
return {...template, values}
}
)
return templates
}
export const putDashboard = (dashboard: Dashboard) => async (
dispatch
): Promise<void> => {
try {
// save only selected template values to server
const templatesWithOnlySelectedValues = removeUnselectedTemplateValues(
dashboard
)
const {
data: dashboardWithOnlySelectedTemplateValues,
} = await updateDashboardAJAX({
...dashboard,
templates: templatesWithOnlySelectedValues,
})
// save all template values to redux
dispatch(
updateDashboard({
...dashboardWithOnlySelectedTemplateValues,
templates: dashboard.templates,
})
)
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const putDashboardByID = (dashboardID: string) => async (
dispatch,
getState
): Promise<void> => {
try {
const {
dashboardUI: {dashboards},
} = getState()
const dashboard: Dashboard = dashboards.find(d => d.id === +dashboardID)
const templates = removeUnselectedTemplateValues(dashboard)
await updateDashboardAJAX({...dashboard, templates})
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const updateDashboardCell = (dashboard: Dashboard, cell: Cell) => async (
dispatch
): Promise<void> => {
try {
const {data} = await updateDashboardCellAJAX(cell)
dispatch(syncDashboardCell(dashboard, data))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const deleteDashboardAsync = (dashboard: Dashboard) => async (
dispatch
): Promise<void> => {
dispatch(deleteDashboard(dashboard))
try {
await deleteDashboardAJAX(dashboard)
dispatch(notify(notifyDashboardDeleted(dashboard.name)))
} catch (error) {
dispatch(
errorThrown(
error,
notifyDashboardDeleteFailed(dashboard.name, error.data.message)
)
)
dispatch(deleteDashboardFailed(dashboard))
}
}
export const addDashboardCellAsync = (
dashboard: Dashboard,
cellType: CellType
) => async (dispatch): Promise<void> => {
try {
const {data} = await addDashboardCellAJAX(
dashboard,
getNewDashboardCell(dashboard, cellType)
)
dispatch(addDashboardCell(dashboard, data))
dispatch(notify(notifyCellAdded(data.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const cloneDashboardCellAsync = (
dashboard: Dashboard,
cell: Cell
) => async (dispatch): Promise<void> => {
try {
const clonedCell = getClonedDashboardCell(dashboard, cell)
const {data} = await addDashboardCellAJAX(dashboard, clonedCell)
dispatch(addDashboardCell(dashboard, data))
dispatch(notify(notifyCellAdded(clonedCell.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const deleteDashboardCellAsync = (
dashboard: Dashboard,
cell: Cell
) => async (dispatch): Promise<void> => {
try {
await deleteDashboardCellAJAX(cell)
dispatch(deleteDashboardCell(dashboard, cell))
dispatch(notify(notifyCellDeleted(cell.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const updateTempVarValues = (
source: Source,
dashboard: Dashboard
) => async (dispatch): Promise<void> => {
try {
const tempsWithQueries = dashboard.templates.filter(
({query}) => !!_.get(query, 'influxql')
)
const asyncQueries = tempsWithQueries.map(({query}) =>
runTemplateVariableQuery(source, {
query: makeQueryForTemplate(query),
db: null,
tempVars: null,
})
)
const results = await Promise.all(asyncQueries)
results.forEach(({data}, i) => {
const {type, query, id} = tempsWithQueries[i]
const parsed = parsers[type](data, query.tagKey || query.measurement)
const vals = parsed[type]
dispatch(editTemplateVariableValues(dashboard.id, id, vals))
})
} catch (error) {
console.error(error)
dispatch(errorThrown(error))
}
}
export const importDashboardAsync = (dashboard: Dashboard) => async (
dispatch
): Promise<void> => {
try {
// save only selected template values to server
const templatesWithOnlySelectedValues = removeUnselectedTemplateValues(
dashboard
)
const results = await createDashboardAJAX({
...dashboard,
templates: templatesWithOnlySelectedValues,
})
const dashboardWithOnlySelectedTemplateValues = _.get(results, 'data')
// save all template values to redux
dispatch(
createDashboard({
...dashboardWithOnlySelectedTemplateValues,
templates: dashboard.templates,
})
)
const {
data: {dashboards},
} = await getDashboardsAJAX()
dispatch(loadDashboards(dashboards))
dispatch(notify(notifyDashboardImported(name)))
} catch (error) {
const errorMessage = _.get(
error,
'data.message',
'Could not upload dashboard'
)
dispatch(notify(notifyDashboardImportFailed('', errorMessage)))
console.error(error)
dispatch(errorThrown(error))
}
}

View File

@ -1,8 +1,8 @@
import React from 'react'
import SourceIndicator from 'shared/components/SourceIndicator'
import SourceIndicator from 'src/shared/components/SourceIndicator'
const DashboardsHeader = () => (
const DashboardsHeader = (): JSX.Element => (
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">

View File

@ -1,98 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import DashboardsTable from 'src/dashboards/components/DashboardsTable'
import SearchBar from 'src/hosts/components/SearchBar'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class DashboardsPageContents extends Component {
constructor(props) {
super(props)
this.state = {
searchTerm: '',
}
}
filterDashboards = searchTerm => {
this.setState({searchTerm})
}
render() {
const {
dashboards,
onDeleteDashboard,
onCreateDashboard,
onCloneDashboard,
dashboardLink,
} = this.props
const {searchTerm} = this.state
let tableHeader
if (dashboards === null) {
tableHeader = 'Loading Dashboards...'
} else if (dashboards.length === 1) {
tableHeader = '1 Dashboard'
} else {
tableHeader = `${dashboards.length} Dashboards`
}
const filteredDashboards = dashboards.filter(d =>
d.name.toLowerCase().includes(searchTerm.toLowerCase())
)
return (
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">{tableHeader}</h2>
<div className="dashboards-page--actions">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
/>
<Authorized requiredRole={EDITOR_ROLE}>
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</Authorized>
</div>
</div>
<div className="panel-body">
<DashboardsTable
dashboards={filteredDashboards}
onDeleteDashboard={onDeleteDashboard}
onCreateDashboard={onCreateDashboard}
onCloneDashboard={onCloneDashboard}
dashboardLink={dashboardLink}
/>
</div>
</div>
</div>
</div>
</div>
</FancyScrollbar>
)
}
}
const {arrayOf, func, shape, string} = PropTypes
DashboardsPageContents.propTypes = {
dashboards: arrayOf(shape()),
onDeleteDashboard: func.isRequired,
onCreateDashboard: func.isRequired,
onCloneDashboard: func.isRequired,
dashboardLink: string.isRequired,
}
export default DashboardsPageContents

View File

@ -0,0 +1,165 @@
import React, {Component, MouseEvent} from 'react'
import {connect} from 'react-redux'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import DashboardsTable from 'src/dashboards/components/DashboardsTable'
import ImportDashboardOverlay from 'src/dashboards/components/ImportDashboardOverlay'
import SearchBar from 'src/hosts/components/SearchBar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
showOverlay as showOverlayAction,
ShowOverlay,
} from 'src/shared/actions/overlayTechnology'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import {Dashboard} from 'src/types'
import {Notification} from 'src/types/notifications'
interface Props {
dashboards: Dashboard[]
onDeleteDashboard: (dashboard: Dashboard) => () => void
onCreateDashboard: () => void
onCloneDashboard: (
dashboard: Dashboard
) => (event: MouseEvent<HTMLButtonElement>) => void
onExportDashboard: (dashboard: Dashboard) => () => void
onImportDashboard: (dashboard: Dashboard) => void
notify: (message: Notification) => void
showOverlay: ShowOverlay
dashboardLink: string
}
interface State {
searchTerm: string
}
@ErrorHandling
class DashboardsPageContents extends Component<Props, State> {
constructor(props) {
super(props)
this.state = {
searchTerm: '',
}
}
public render() {
const {
onDeleteDashboard,
onCreateDashboard,
onCloneDashboard,
onExportDashboard,
dashboardLink,
} = this.props
return (
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<div className="panel">
{this.renderPanelHeading}
<div className="panel-body">
<DashboardsTable
dashboards={this.filteredDashboards}
onDeleteDashboard={onDeleteDashboard}
onCreateDashboard={onCreateDashboard}
onCloneDashboard={onCloneDashboard}
onExportDashboard={onExportDashboard}
dashboardLink={dashboardLink}
/>
</div>
</div>
</div>
</div>
</div>
</FancyScrollbar>
)
}
private get renderPanelHeading(): JSX.Element {
const {onCreateDashboard} = this.props
return (
<div className="panel-heading">
<h2 className="panel-title">{this.panelTitle}</h2>
<div className="panel-controls">
<SearchBar
placeholder="Filter by Name..."
onSearch={this.filterDashboards}
/>
<Authorized requiredRole={EDITOR_ROLE}>
<>
<button
className="btn btn-sm btn-default"
onClick={this.showImportOverlay}
>
<span className="icon import" /> Import Dashboard
</button>
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
>
<span className="icon plus" /> Create Dashboard
</button>
</>
</Authorized>
</div>
</div>
)
}
private get filteredDashboards(): Dashboard[] {
const {dashboards} = this.props
const {searchTerm} = this.state
return dashboards.filter(d =>
d.name.toLowerCase().includes(searchTerm.toLowerCase())
)
}
private get panelTitle(): string {
const {dashboards} = this.props
if (dashboards === null) {
return 'Loading Dashboards...'
} else if (dashboards.length === 1) {
return '1 Dashboard'
}
return `${dashboards.length} Dashboards`
}
private filterDashboards = (searchTerm: string): void => {
this.setState({searchTerm})
}
private showImportOverlay = (): void => {
const {showOverlay, onImportDashboard, notify} = this.props
const options = {
dismissOnClickOutside: false,
dismissOnEscape: false,
}
showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<ImportDashboardOverlay
onDismissOverlay={onDismissOverlay}
onImportDashboard={onImportDashboard}
notify={notify}
/>
)}
</OverlayContext.Consumer>,
options
)
}
}
const mapDispatchToProps = {
showOverlay: showOverlayAction,
}
export default connect(null, mapDispatchToProps)(DashboardsPageContents)

View File

@ -1,116 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import _ from 'lodash'
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
import ConfirmButton from 'shared/components/ConfirmButton'
const AuthorizedEmptyState = ({onCreateDashboard}) => (
<div className="generic-empty-state">
<h4 style={{marginTop: '90px'}}>
Looks like you dont have any dashboards
</h4>
<br />
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
style={{marginBottom: '90px'}}
>
<span className="icon plus" /> Create Dashboard
</button>
</div>
)
const unauthorizedEmptyState = (
<div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>Looks like you dont have any dashboards</h4>
</div>
)
const DashboardsTable = ({
dashboards,
onDeleteDashboard,
onCreateDashboard,
onCloneDashboard,
dashboardLink,
}) => {
return dashboards && dashboards.length ? (
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
<th>Template Variables</th>
<th />
</tr>
</thead>
<tbody>
{_.sortBy(dashboards, d => d.name.toLowerCase()).map(dashboard => (
<tr key={dashboard.id}>
<td>
<Link to={`${dashboardLink}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
<td>
{dashboard.templates.length ? (
dashboard.templates.map(tv => (
<code className="table--temp-var" key={tv.id}>
{tv.tempVar}
</code>
))
) : (
<span className="empty-string">None</span>
)}
</td>
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={<td />}
>
<td className="text-right">
<button
className="btn btn-xs btn-default table--show-on-row-hover"
onClick={onCloneDashboard(dashboard)}
>
<span className="icon duplicate" />
Clone
</button>
<ConfirmButton
confirmAction={onDeleteDashboard(dashboard)}
size="btn-xs"
type="btn-danger"
text="Delete"
customClass="table--show-on-row-hover"
/>
</td>
</Authorized>
</tr>
))}
</tbody>
</table>
) : (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={unauthorizedEmptyState}
>
<AuthorizedEmptyState onCreateDashboard={onCreateDashboard} />
</Authorized>
)
}
const {arrayOf, func, shape, string} = PropTypes
DashboardsTable.propTypes = {
dashboards: arrayOf(shape()),
onDeleteDashboard: func.isRequired,
onCreateDashboard: func.isRequired,
onCloneDashboard: func.isRequired,
dashboardLink: string.isRequired,
}
AuthorizedEmptyState.propTypes = {
onCreateDashboard: func.isRequired,
}
export default DashboardsTable

View File

@ -0,0 +1,147 @@
import React, {PureComponent, MouseEvent} from 'react'
import {Link} from 'react-router'
import _ from 'lodash'
import Authorized, {EDITOR_ROLE, VIEWER_ROLE} from 'src/auth/Authorized'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import {getDeep} from 'src/utils/wrappers'
import {Dashboard, Template} from 'src/types'
interface Props {
dashboards: Dashboard[]
onDeleteDashboard: (dashboard: Dashboard) => () => void
onCreateDashboard: () => void
onCloneDashboard: (
dashboard: Dashboard
) => (event: MouseEvent<HTMLButtonElement>) => void
onExportDashboard: (dashboard: Dashboard) => () => void
dashboardLink: string
}
class DashboardsTable extends PureComponent<Props> {
public render() {
const {
dashboards,
dashboardLink,
onCloneDashboard,
onDeleteDashboard,
onExportDashboard,
} = this.props
if (!dashboards.length) {
return this.emptyStateDashboard
}
return (
<table className="table v-center admin-table table-highlight">
<thead>
<tr>
<th>Name</th>
<th>Template Variables</th>
<th />
</tr>
</thead>
<tbody>
{_.sortBy(dashboards, d => d.name.toLowerCase()).map(dashboard => (
<tr key={dashboard.id}>
<td>
<Link to={`${dashboardLink}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
<td>{this.getDashboardTemplates(dashboard)}</td>
<td className="text-right">
<Authorized
requiredRole={VIEWER_ROLE}
replaceWithIfNotAuthorized={<div />}
>
<button
className="btn btn-xs btn-default table--show-on-row-hover"
onClick={onExportDashboard(dashboard)}
>
<span className="icon export" />Export
</button>
</Authorized>
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={<div />}
>
<>
<button
className="btn btn-xs btn-default table--show-on-row-hover"
onClick={onCloneDashboard(dashboard)}
>
<span className="icon duplicate" />
Clone
</button>
<ConfirmButton
confirmAction={onDeleteDashboard(dashboard)}
size="btn-xs"
type="btn-danger"
text="Delete"
customClass="table--show-on-row-hover"
/>
</>
</Authorized>
</td>
</tr>
))}
</tbody>
</table>
)
}
private getDashboardTemplates = (
dashboard: Dashboard
): JSX.Element | JSX.Element[] => {
const templates = getDeep<Template[]>(dashboard, 'templates', [])
if (templates.length) {
return templates.map(tv => (
<code className="table--temp-var" key={tv.id}>
{tv.tempVar}
</code>
))
}
return <span className="empty-string">None</span>
}
private get emptyStateDashboard(): JSX.Element {
const {onCreateDashboard} = this.props
return (
<Authorized
requiredRole={EDITOR_ROLE}
replaceWithIfNotAuthorized={this.unauthorizedEmptyState}
>
<div className="generic-empty-state">
<h4 style={{marginTop: '90px'}}>
Looks like you dont have any dashboards
</h4>
<br />
<button
className="btn btn-sm btn-primary"
onClick={onCreateDashboard}
style={{marginBottom: '90px'}}
>
<span className="icon plus" /> Create Dashboard
</button>
</div>
</Authorized>
)
}
private get unauthorizedEmptyState(): JSX.Element {
return (
<div className="generic-empty-state">
<h4 style={{margin: '90px 0'}}>
Looks like you dont have any dashboards
</h4>
</div>
)
}
}
export default DashboardsTable

View File

@ -72,12 +72,12 @@ class GaugeOptions extends Component {
onResetFocus()
}
handleChooseColor = threshold => chosenColor => {
handleChooseColor = threshold => {
const {handleUpdateGaugeColors} = this.props
const gaugeColors = this.props.gaugeColors.map(
color =>
color.id === threshold.id
? {...color, hex: chosenColor.hex, name: chosenColor.name}
? {...color, hex: threshold.hex, name: threshold.name}
: color
)

View File

@ -0,0 +1,84 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import Container from 'src/shared/components/overlay/OverlayContainer'
import Heading from 'src/shared/components/overlay/OverlayHeading'
import Body from 'src/shared/components/overlay/OverlayBody'
import DragAndDrop from 'src/shared/components/DragAndDrop'
import {notifyDashboardImportFailed} from 'src/shared/copy/notifications'
import {Dashboard} from 'src/types'
import {Notification} from 'src/types/notifications'
interface Props {
onDismissOverlay: () => void
onImportDashboard: (dashboard: Dashboard) => void
notify: (message: Notification) => void
}
interface State {
isImportable: boolean
}
class ImportDashboardOverlay extends PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
isImportable: false,
}
}
public render() {
const {onDismissOverlay} = this.props
return (
<Container maxWidth={800}>
<Heading title="Import Dashboard" onDismiss={onDismissOverlay} />
<Body>
<DragAndDrop
submitText="Upload Dashboard"
fileTypesToAccept={this.validFileExtension}
handleSubmit={this.handleUploadDashboard}
/>
</Body>
</Container>
)
}
private get validFileExtension(): string {
return '.json'
}
private handleUploadDashboard = (
uploadContent: string,
fileName: string
): void => {
const {onImportDashboard, onDismissOverlay} = this.props
const fileExtensionRegex = new RegExp(`${this.validFileExtension}$`)
if (!fileName.match(fileExtensionRegex)) {
this.props.notify(
notifyDashboardImportFailed(fileName, 'Please import a JSON file')
)
return
}
try {
const {dashboard} = JSON.parse(uploadContent)
if (!_.isEmpty(dashboard)) {
onImportDashboard(dashboard)
} else {
this.props.notify(
notifyDashboardImportFailed(fileName, 'No dashboard found in file')
)
}
} catch (error) {
this.props.notify(notifyDashboardImportFailed(fileName, error))
}
onDismissOverlay()
}
}
export default ImportDashboardOverlay

View File

@ -100,8 +100,9 @@ type NewDefaultDashboard = Pick<
cells: NewDefaultCell[]
}
>
export const DEFAULT_DASHBOARD_NAME = 'Name This Dashboard'
export const NEW_DASHBOARD: NewDefaultDashboard = {
name: 'Name This Dashboard',
name: DEFAULT_DASHBOARD_NAME,
cells: [NEW_DEFAULT_DASHBOARD_CELL],
}
@ -141,7 +142,15 @@ export const TEMPLATE_VARIABLE_TYPES = {
tagValues: 'tagValue',
}
export const TEMPLATE_VARIABLE_QUERIES = {
interface TemplateVariableQueries {
databases: string
measurements: string
fieldKeys: string
tagKeys: string
tagValues: string
}
export const TEMPLATE_VARIABLE_QUERIES: TemplateVariableQueries = {
databases: 'SHOW DATABASES',
measurements: 'SHOW MEASUREMENTS ON :database:',
fieldKeys: 'SHOW FIELD KEYS ON :database: FROM :measurement:',
@ -172,7 +181,7 @@ export const removeUnselectedTemplateValues = templates => {
export const TYPE_QUERY_CONFIG: string = 'queryConfig'
export const TYPE_SHIFTED: string = 'shifted queryConfig'
export const TYPE_IFQL: string = 'ifql'
export const TYPE_FLUX: string = 'flux'
export const DASHBOARD_NAME_MAX_LENGTH: number = 50
export const TEMPLATE_RANGE: TimeRange = {
upper: null,

View File

@ -1,98 +0,0 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {withRouter} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import DashboardsHeader from 'src/dashboards/components/DashboardsHeader'
import DashboardsContents from 'src/dashboards/components/DashboardsPageContents'
import {createDashboard} from 'src/dashboards/apis'
import {getDashboardsAsync, deleteDashboardAsync} from 'src/dashboards/actions'
import {NEW_DASHBOARD} from 'src/dashboards/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class DashboardsPage extends Component {
componentDidMount() {
this.props.handleGetDashboards()
}
handleCreateDashboard = async () => {
const {
source: {id},
router: {push},
} = this.props
const {data} = await createDashboard(NEW_DASHBOARD)
push(`/sources/${id}/dashboards/${data.id}`)
}
handleCloneDashboard = dashboard => async () => {
const {
source: {id},
router: {push},
} = this.props
const {data} = await createDashboard({
...dashboard,
name: `${dashboard.name} (clone)`,
})
push(`/sources/${id}/dashboards/${data.id}`)
}
handleDeleteDashboard = dashboard => () => {
this.props.handleDeleteDashboard(dashboard)
}
render() {
const {dashboards} = this.props
const dashboardLink = `/sources/${this.props.source.id}`
return (
<div className="page">
<DashboardsHeader sourceName={this.props.source.name} />
<DashboardsContents
dashboardLink={dashboardLink}
dashboards={dashboards}
onDeleteDashboard={this.handleDeleteDashboard}
onCreateDashboard={this.handleCreateDashboard}
onCloneDashboard={this.handleCloneDashboard}
/>
</div>
)
}
}
const {arrayOf, func, string, shape} = PropTypes
DashboardsPage.propTypes = {
source: shape({
id: string.isRequired,
name: string.isRequired,
type: string,
links: shape({
proxy: string.isRequired,
}).isRequired,
telegraf: string.isRequired,
}),
router: shape({
push: func.isRequired,
}).isRequired,
handleGetDashboards: func.isRequired,
handleDeleteDashboard: func.isRequired,
dashboards: arrayOf(shape()),
}
const mapStateToProps = ({dashboardUI: {dashboards, dashboard}}) => ({
dashboards,
dashboard,
})
const mapDispatchToProps = dispatch => ({
handleGetDashboards: bindActionCreators(getDashboardsAsync, dispatch),
handleDeleteDashboard: bindActionCreators(deleteDashboardAsync, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(
withRouter(DashboardsPage)
)

View File

@ -0,0 +1,146 @@
import React, {PureComponent} from 'react'
import {withRouter, InjectedRouter} from 'react-router'
import {connect} from 'react-redux'
import download from 'src/external/download'
import _ from 'lodash'
import DashboardsHeader from 'src/dashboards/components/DashboardsHeader'
import DashboardsContents from 'src/dashboards/components/DashboardsPageContents'
import {createDashboard} from 'src/dashboards/apis'
import {
getDashboardsAsync,
deleteDashboardAsync,
getChronografVersion,
importDashboardAsync,
} from 'src/dashboards/actions'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {NEW_DASHBOARD, DEFAULT_DASHBOARD_NAME} from 'src/dashboards/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
notifyDashboardExported,
notifyDashboardExportFailed,
} from 'src/shared/copy/notifications'
import {Source, Dashboard} from 'src/types'
import {Notification} from 'src/types/notifications'
import {DashboardFile} from 'src/types/dashboard'
interface Props {
source: Source
router: InjectedRouter
handleGetDashboards: () => void
handleGetChronografVersion: () => string
handleDeleteDashboard: (dashboard: Dashboard) => void
handleImportDashboard: (dashboard: Dashboard) => void
notify: (message: Notification) => void
dashboards: Dashboard[]
}
@ErrorHandling
class DashboardsPage extends PureComponent<Props> {
public componentDidMount() {
this.props.handleGetDashboards()
}
public render() {
const {dashboards, notify} = this.props
const dashboardLink = `/sources/${this.props.source.id}`
return (
<div className="page">
<DashboardsHeader />
<DashboardsContents
dashboardLink={dashboardLink}
dashboards={dashboards}
onDeleteDashboard={this.handleDeleteDashboard}
onCreateDashboard={this.handleCreateDashboard}
onCloneDashboard={this.handleCloneDashboard}
onExportDashboard={this.handleExportDashboard}
onImportDashboard={this.handleImportDashboard}
notify={notify}
/>
</div>
)
}
private handleCreateDashboard = async (): Promise<void> => {
const {
source: {id},
router: {push},
} = this.props
const {data} = await createDashboard(NEW_DASHBOARD)
push(`/sources/${id}/dashboards/${data.id}`)
}
private handleCloneDashboard = (dashboard: Dashboard) => async (): Promise<
void
> => {
const {
source: {id},
router: {push},
} = this.props
const {data} = await createDashboard({
...dashboard,
name: `${dashboard.name} (clone)`,
})
push(`/sources/${id}/dashboards/${data.id}`)
}
private handleDeleteDashboard = (dashboard: Dashboard) => (): void => {
this.props.handleDeleteDashboard(dashboard)
}
private handleExportDashboard = (dashboard: Dashboard) => async (): Promise<
void
> => {
const dashboardForDownload = await this.modifyDashboardForDownload(
dashboard
)
try {
download(
JSON.stringify(dashboardForDownload, null, '\t'),
`${dashboard.name}.json`,
'text/plain'
)
this.props.notify(notifyDashboardExported(dashboard.name))
} catch (error) {
this.props.notify(notifyDashboardExportFailed(dashboard.name, error))
}
}
private modifyDashboardForDownload = async (
dashboard: Dashboard
): Promise<DashboardFile> => {
const version = await this.props.handleGetChronografVersion()
return {meta: {chronografVersion: version}, dashboard}
}
private handleImportDashboard = async (
dashboard: Dashboard
): Promise<void> => {
const name = _.get(dashboard, 'name', DEFAULT_DASHBOARD_NAME)
await this.props.handleImportDashboard({
...dashboard,
name,
})
}
}
const mapStateToProps = ({dashboardUI: {dashboards, dashboard}}) => ({
dashboards,
dashboard,
})
const mapDispatchToProps = {
handleGetDashboards: getDashboardsAsync,
handleDeleteDashboard: deleteDashboardAsync,
handleGetChronografVersion: getChronografVersion,
handleImportDashboard: importDashboardAsync,
notify: notifyAction,
}
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(DashboardsPage)
)

View File

@ -46,6 +46,14 @@ export default function ui(state = initialState, action) {
return {...state, ...newState}
}
case 'CREATE_DASHBOARD': {
const {dashboard} = action.payload
const newState = {
dashboards: [...state.dashboards, dashboard],
}
return {...state, ...newState}
}
case 'DELETE_DASHBOARD': {
const {dashboard} = action.payload
const newState = {

View File

@ -1,4 +1,10 @@
import {TEMPLATE_VARIABLE_QUERIES} from 'src/dashboards/constants'
import {Template, TemplateQuery} from 'src/types/dashboard'
interface PartialTemplateWithQuery {
query: string
tempVars: Array<Partial<Template>>
}
const generateTemplateVariableQuery = ({
type,
@ -8,7 +14,7 @@ const generateTemplateVariableQuery = ({
measurement,
tagKey,
},
}) => {
}: Partial<Template>): PartialTemplateWithQuery => {
const tempVars = []
if (database) {
@ -45,7 +51,7 @@ const generateTemplateVariableQuery = ({
})
}
const query = TEMPLATE_VARIABLE_QUERIES[type]
const query: string = TEMPLATE_VARIABLE_QUERIES[type]
return {
query,
@ -53,7 +59,12 @@ const generateTemplateVariableQuery = ({
}
}
export const makeQueryForTemplate = ({influxql, db, measurement, tagKey}) =>
export const makeQueryForTemplate = ({
influxql,
db,
measurement,
tagKey,
}: TemplateQuery): string =>
influxql
.replace(':database:', `"${db}"`)
.replace(':measurement:', `"${measurement}"`)

View File

@ -1,4 +1,4 @@
import {modeIFQL, modeTickscript} from 'src/shared/constants/codeMirrorModes'
import {modeFlux, modeTickscript} from 'src/shared/constants/codeMirrorModes'
/* eslint-disable */
const CodeMirror = require('codemirror')
@ -312,7 +312,7 @@ function indentFunction(states, meta) {
}
// Modes
CodeMirror.defineSimpleMode('ifql', modeIFQL)
CodeMirror.defineSimpleMode('flux', modeFlux)
CodeMirror.defineSimpleMode('tickscript', modeTickscript)
// CodeMirror Hints

View File

@ -3,7 +3,7 @@ import _ from 'lodash'
import AJAX from 'src/utils/ajax'
import {Service, FluxTable} from 'src/types'
import {updateService} from 'src/shared/apis'
import {parseResponse} from 'src/shared/parsing/v2/results'
import {parseResponse} from 'src/shared/parsing/flux/response'
export const getSuggestions = async (url: string) => {
try {
@ -64,7 +64,7 @@ export const getTimeSeries = async (
}
}
// TODO: replace with actual requests to IFQL daemon
// TODO: replace with actual requests to Flux daemon
export const getDatabases = async () => {
try {
const response = {data: {dbs: ['telegraf', 'chronograf', '_internal']}}

View File

@ -5,7 +5,7 @@ import {
FlatBody,
BinaryExpressionNode,
MemberExpressionNode,
} from 'src/types/ifql'
} from 'src/types/flux'
interface Expression {
argument: object

View File

@ -1,13 +1,13 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import ExpressionNode from 'src/ifql/components/ExpressionNode'
import VariableName from 'src/ifql/components/VariableName'
import FuncSelector from 'src/ifql/components/FuncSelector'
import {funcNames} from 'src/ifql/constants'
import ExpressionNode from 'src/flux/components/ExpressionNode'
import VariableName from 'src/flux/components/VariableName'
import FuncSelector from 'src/flux/components/FuncSelector'
import {funcNames} from 'src/flux/constants'
import {Service} from 'src/types'
import {FlatBody, Suggestion} from 'src/types/ifql'
import {FlatBody, Suggestion} from 'src/types/flux'
interface Props {
service: Service

View File

@ -1,6 +1,6 @@
import React, {PureComponent} from 'react'
import DatabaseListItem from 'src/ifql/components/DatabaseListItem'
import DatabaseListItem from 'src/flux/components/DatabaseListItem'
import {showDatabases} from 'src/shared/apis/metaQuery'
import showDatabasesParser from 'src/shared/parsing/showDatabases'

View File

@ -1,9 +1,9 @@
import React, {PureComponent, ChangeEvent, MouseEvent} from 'react'
import classnames from 'classnames'
import {tagKeys as fetchTagKeys} from 'src/shared/apis/v2/metaQueries'
import parseValuesColumn from 'src/shared/parsing/v2/tags'
import TagList from 'src/ifql/components/TagList'
import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
import parseValuesColumn from 'src/shared/parsing/flux/values'
import TagList from 'src/flux/components/TagList'
import {Service} from 'src/types'
interface Props {
@ -45,14 +45,14 @@ class DatabaseListItem extends PureComponent<Props, State> {
return (
<div className={this.className} onClick={this.handleClick}>
<div className="ifql-schema-item">
<div className="ifql-schema-item-toggle" />
<div className="flux-schema-item">
<div className="flux-schema-item-toggle" />
{db}
<span className="ifql-schema-type">Bucket</span>
<span className="flux-schema-type">Bucket</span>
</div>
{this.state.isOpen && (
<>
<div className="ifql-schema--filter">
<div className="flux-schema--filter">
<input
className="form-control input-sm"
placeholder={`Filter within ${db}`}
@ -78,7 +78,7 @@ class DatabaseListItem extends PureComponent<Props, State> {
}
private get className(): string {
return classnames('ifql-schema-tree', {
return classnames('flux-schema-tree', {
expanded: this.state.isOpen,
})
}

View File

@ -1,10 +1,10 @@
import React, {PureComponent} from 'react'
import {IFQLContext} from 'src/ifql/containers/IFQLPage'
import FuncSelector from 'src/ifql/components/FuncSelector'
import FuncNode from 'src/ifql/components/FuncNode'
import {FluxContext} from 'src/flux/containers/FluxPage'
import FuncSelector from 'src/flux/components/FuncSelector'
import FuncNode from 'src/flux/components/FuncNode'
import {Func} from 'src/types/ifql'
import {Func} from 'src/types/flux'
interface Props {
funcNames: any[]
@ -25,7 +25,7 @@ class ExpressionNode extends PureComponent<Props> {
declarationsFromBody,
} = this.props
return (
<IFQLContext.Consumer>
<FluxContext.Consumer>
{({
onDeleteFuncNode,
onAddNode,
@ -57,7 +57,7 @@ class ExpressionNode extends PureComponent<Props> {
</>
)
}}
</IFQLContext.Consumer>
</FluxContext.Consumer>
)
}
}

View File

@ -1,8 +1,8 @@
import {PureComponent, ReactNode} from 'react'
import {connect} from 'react-redux'
import {getAST} from 'src/ifql/apis'
import {Links, BinaryExpressionNode, MemberExpressionNode} from 'src/types/ifql'
import Walker from 'src/ifql/ast/walker'
import {getAST} from 'src/flux/apis'
import {Links, BinaryExpressionNode, MemberExpressionNode} from 'src/types/flux'
import Walker from 'src/flux/ast/walker'
interface Props {
value: string
@ -41,7 +41,7 @@ export class Filter extends PureComponent<Props, State> {
}
const mapStateToProps = ({links}) => {
return {links: links.ifql}
return {links: links.flux}
}
export default connect(mapStateToProps, null)(Filter)

View File

@ -1,5 +1,5 @@
import React, {PureComponent} from 'react'
import {MemberExpressionNode} from 'src/types/ifql'
import {MemberExpressionNode} from 'src/types/flux'
type FilterNode = MemberExpressionNode

View File

@ -1,5 +1,5 @@
import React, {PureComponent} from 'react'
import {BinaryExpressionNode, MemberExpressionNode} from 'src/types/ifql'
import {BinaryExpressionNode, MemberExpressionNode} from 'src/types/flux'
type FilterNode = BinaryExpressionNode & MemberExpressionNode
@ -32,29 +32,29 @@ class FilterPreviewNode extends PureComponent<FilterPreviewNodeProps> {
switch (node.type) {
case 'ObjectExpression': {
return <div className="ifql-filter--key">{node.source}</div>
return <div className="flux-filter--key">{node.source}</div>
}
case 'MemberExpression': {
return <div className="ifql-filter--key">{node.property.name}</div>
return <div className="flux-filter--key">{node.property.name}</div>
}
case 'OpenParen': {
return <div className="ifql-filter--paren-open" />
return <div className="flux-filter--paren-open" />
}
case 'CloseParen': {
return <div className="ifql-filter--paren-close" />
return <div className="flux-filter--paren-close" />
}
case 'NumberLiteral':
case 'IntegerLiteral': {
return <div className="ifql-filter--value number">{node.source}</div>
return <div className="flux-filter--value number">{node.source}</div>
}
case 'BooleanLiteral': {
return <div className="ifql-filter--value boolean">{node.source}</div>
return <div className="flux-filter--value boolean">{node.source}</div>
}
case 'StringLiteral': {
return <div className="ifql-filter--value string">{node.source}</div>
return <div className="flux-filter--value string">{node.source}</div>
}
case 'Operator': {
return <div className="ifql-filter--operator">{node.source}</div>
return <div className="flux-filter--operator">{node.source}</div>
}
default: {
return <div />

View File

@ -1,9 +1,9 @@
import React, {PureComponent, ChangeEvent, FormEvent} from 'react'
import IFQLForm from 'src/ifql/components/IFQLForm'
import FluxForm from 'src/flux/components/FluxForm'
import {Service, Notification} from 'src/types'
import {ifqlUpdated, ifqlNotUpdated} from 'src/shared/copy/notifications'
import {fluxUpdated, fluxNotUpdated} from 'src/shared/copy/notifications'
import {UpdateServiceAsync} from 'src/shared/actions/services'
interface Props {
@ -17,7 +17,7 @@ interface State {
service: Service
}
class IFQLEdit extends PureComponent<Props, State> {
class FluxEdit extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
@ -27,7 +27,7 @@ class IFQLEdit extends PureComponent<Props, State> {
public render() {
return (
<IFQLForm
<FluxForm
service={this.state.service}
onSubmit={this.handleSubmit}
onInputChange={this.handleInputChange}
@ -53,13 +53,13 @@ class IFQLEdit extends PureComponent<Props, State> {
try {
await updateService(service)
} catch (error) {
notify(ifqlNotUpdated(error.message))
notify(fluxNotUpdated(error.message))
return
}
notify(ifqlUpdated)
notify(fluxUpdated)
onDismiss()
}
}
export default IFQLEdit
export default FluxEdit

View File

@ -11,7 +11,7 @@ interface Props {
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
}
class IFQLForm extends PureComponent<Props> {
class FluxForm extends PureComponent<Props> {
public render() {
const {service, onSubmit, onInputChange} = this.props
@ -20,7 +20,7 @@ class IFQLForm extends PureComponent<Props> {
<form onSubmit={onSubmit} style={{display: 'inline-block'}}>
<Input
name="url"
label="IFQL URL"
label="Flux URL"
value={this.url}
placeholder={this.url}
onChange={onInputChange}
@ -69,4 +69,4 @@ class IFQLForm extends PureComponent<Props> {
}
}
export default IFQLForm
export default FluxForm

View File

@ -1,7 +1,7 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {fluxTablesToDygraph} from 'src/shared/parsing/v2/dygraph'
import {fluxTablesToDygraph} from 'src/shared/parsing/flux/dygraph'
import Dygraph from 'src/shared/components/Dygraph'
import {FluxTable} from 'src/types'

View File

@ -1,7 +1,7 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
import FluxOverlay from 'src/flux/components/FluxOverlay'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import {
showOverlay as showOverlayAction,
@ -16,7 +16,7 @@ interface Props {
onGetTimeSeries: () => void
}
class IFQLHeader extends PureComponent<Props> {
class FluxHeader extends PureComponent<Props> {
public render() {
const {onGetTimeSeries} = this.props
@ -48,7 +48,7 @@ class IFQLHeader extends PureComponent<Props> {
showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<IFQLOverlay
<FluxOverlay
mode="edit"
service={service}
onDismiss={onDismissOverlay}
@ -64,4 +64,4 @@ const mdtp = {
showOverlay: showOverlayAction,
}
export default connect(null, mdtp)(IFQLHeader)
export default connect(null, mdtp)(FluxHeader)

View File

@ -1,9 +1,9 @@
import React, {PureComponent, ChangeEvent, FormEvent} from 'react'
import IFQLForm from 'src/ifql/components/IFQLForm'
import FluxForm from 'src/flux/components/FluxForm'
import {NewService, Source, Notification} from 'src/types'
import {ifqlCreated, ifqlNotCreated} from 'src/shared/copy/notifications'
import {fluxCreated, fluxNotCreated} from 'src/shared/copy/notifications'
import {CreateServiceAsync} from 'src/shared/actions/services'
interface Props {
@ -19,7 +19,7 @@ interface State {
const port = 8093
class IFQLNew extends PureComponent<Props, State> {
class FluxNew extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
@ -29,7 +29,7 @@ class IFQLNew extends PureComponent<Props, State> {
public render() {
return (
<IFQLForm
<FluxForm
service={this.state.service}
onSubmit={this.handleSubmit}
onInputChange={this.handleInputChange}
@ -56,21 +56,21 @@ class IFQLNew extends PureComponent<Props, State> {
try {
await createService(source, service)
} catch (error) {
notify(ifqlNotCreated(error.message))
notify(fluxNotCreated(error.message))
return
}
notify(ifqlCreated)
notify(fluxCreated)
onDismiss()
}
private get defaultService(): NewService {
return {
name: 'IFQL',
name: 'Flux',
url: this.url,
username: '',
insecureSkipVerify: false,
type: 'ifql',
type: 'flux',
active: true,
}
}
@ -83,4 +83,4 @@ class IFQLNew extends PureComponent<Props, State> {
}
}
export default IFQLNew
export default FluxNew

View File

@ -1,8 +1,8 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import IFQLNew from 'src/ifql/components/IFQLNew'
import IFQLEdit from 'src/ifql/components/IFQLEdit'
import FluxNew from 'src/flux/components/FluxNew'
import FluxEdit from 'src/flux/components/FluxEdit'
import {Service, Source, Notification} from 'src/types'
@ -24,13 +24,13 @@ interface Props {
updateService: UpdateServiceAsync
}
class IFQLOverlay extends PureComponent<Props> {
class FluxOverlay extends PureComponent<Props> {
public render() {
return (
<div className="ifql-overlay">
<div className="flux-overlay">
<div className="template-variable-manager--header">
<div className="page-header__left">
<h1 className="page-header__title">Connect to IFQL</h1>
<h1 className="page-header__title">Connect to Flux</h1>
</div>
<div className="page-header__right">
<span
@ -57,7 +57,7 @@ class IFQLOverlay extends PureComponent<Props> {
if (mode === 'new') {
return (
<IFQLNew
<FluxNew
source={source}
notify={notify}
onDismiss={onDismiss}
@ -67,7 +67,7 @@ class IFQLOverlay extends PureComponent<Props> {
}
return (
<IFQLEdit
<FluxEdit
notify={notify}
service={service}
onDismiss={onDismiss}
@ -83,4 +83,4 @@ const mdtp = {
updateService: updateServiceAsync,
}
export default connect(null, mdtp)(IFQLOverlay)
export default connect(null, mdtp)(FluxOverlay)

View File

@ -4,7 +4,7 @@ import {showDatabases} from 'src/shared/apis/metaQuery'
import showDatabasesParser from 'src/shared/parsing/showDatabases'
import Dropdown from 'src/shared/components/Dropdown'
import {OnChangeArg} from 'src/types/ifql'
import {OnChangeArg} from 'src/types/flux'
import {Service} from 'src/types'
interface Props {

View File

@ -1,13 +1,13 @@
import React, {PureComponent} from 'react'
import FuncArgInput from 'src/ifql/components/FuncArgInput'
import FuncArgTextArea from 'src/ifql/components/FuncArgTextArea'
import FuncArgBool from 'src/ifql/components/FuncArgBool'
import FuncArgInput from 'src/flux/components/FuncArgInput'
import FuncArgTextArea from 'src/flux/components/FuncArgTextArea'
import FuncArgBool from 'src/flux/components/FuncArgBool'
import {ErrorHandling} from 'src/shared/decorators/errors'
import From from 'src/ifql/components/From'
import From from 'src/flux/components/From'
import {funcNames, argTypes} from 'src/ifql/constants'
import {OnChangeArg} from 'src/types/ifql'
import {funcNames, argTypes} from 'src/flux/constants'
import {OnChangeArg} from 'src/types/flux'
import {Service} from 'src/types'
interface Props {

View File

@ -1,7 +1,7 @@
import React, {PureComponent} from 'react'
import SlideToggle from 'src/shared/components/SlideToggle'
import {OnChangeArg} from 'src/types/ifql'
import {OnChangeArg} from 'src/types/flux'
interface Props {
argKey: string

View File

@ -1,6 +1,6 @@
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeArg} from 'src/types/ifql'
import {OnChangeArg} from 'src/types/flux'
interface Props {
funcID: string

View File

@ -1,6 +1,6 @@
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeArg} from 'src/types/ifql'
import {OnChangeArg} from 'src/types/flux'
interface Props {
funcID: string

View File

@ -1,10 +1,10 @@
import React, {PureComponent, ReactElement} from 'react'
import FuncArg from 'src/ifql/components/FuncArg'
import {OnChangeArg} from 'src/types/ifql'
import FuncArg from 'src/flux/components/FuncArg'
import {OnChangeArg} from 'src/types/flux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Func} from 'src/types/ifql'
import {funcNames} from 'src/ifql/constants'
import Join from 'src/ifql/components/Join'
import {Func} from 'src/types/flux'
import {funcNames} from 'src/flux/constants'
import Join from 'src/flux/components/Join'
import {Service} from 'src/types'
interface Props {

View File

@ -2,10 +2,10 @@ import React, {PureComponent} from 'react'
import uuid from 'uuid'
import _ from 'lodash'
import {Func} from 'src/types/ifql'
import {funcNames} from 'src/ifql/constants'
import Filter from 'src/ifql/components/Filter'
import FilterPreview from 'src/ifql/components/FilterPreview'
import {Func} from 'src/types/flux'
import {funcNames} from 'src/flux/constants'
import Filter from 'src/flux/components/Filter'
import FilterPreview from 'src/flux/components/FilterPreview'
import {getDeep} from 'src/utils/wrappers'

View File

@ -2,7 +2,7 @@ import React, {SFC, ChangeEvent, KeyboardEvent} from 'react'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import FuncSelectorInput from 'src/shared/components/FuncSelectorInput'
import FuncListItem from 'src/ifql/components/FuncListItem'
import FuncListItem from 'src/flux/components/FuncListItem'
interface Props {
inputText: string
@ -24,13 +24,13 @@ const FuncList: SFC<Props> = ({
onSetSelectedFunc,
}) => {
return (
<div className="ifql-func--autocomplete">
<div className="flux-func--autocomplete">
<FuncSelectorInput
onFilterChange={onInputChange}
onFilterKeyPress={onKeyDown}
searchTerm={inputText}
/>
<ul className="ifql-func--list">
<ul className="flux-func--list">
<FancyScrollbar
autoHide={false}
autoHeight={true}
@ -48,7 +48,7 @@ const FuncList: SFC<Props> = ({
/>
))
) : (
<div className="ifql-func--item empty">No matches</div>
<div className="flux-func--item empty">No matches</div>
)}
</FancyScrollbar>
</ul>

View File

@ -15,7 +15,7 @@ export default class FuncListItem extends PureComponent<Props> {
<li
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
className={`ifql-func--item ${this.activeClass}`}
className={`flux-func--item ${this.activeClass}`}
>
{this.props.name}
</li>

View File

@ -1,8 +1,8 @@
import React, {PureComponent, MouseEvent} from 'react'
import FuncArgs from 'src/ifql/components/FuncArgs'
import FuncArgsPreview from 'src/ifql/components/FuncArgsPreview'
import {OnDeleteFuncNode, OnChangeArg, Func} from 'src/types/ifql'
import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
import {OnDeleteFuncNode, OnChangeArg, Func} from 'src/types/flux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Service} from 'src/types'

View File

@ -3,8 +3,8 @@ import _ from 'lodash'
import classnames from 'classnames'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import FuncList from 'src/ifql/components/FuncList'
import {OnAddNode} from 'src/types/ifql'
import FuncList from 'src/flux/components/FuncList'
import {OnAddNode} from 'src/types/flux'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface State {
@ -57,7 +57,7 @@ export class FuncSelector extends PureComponent<Props, State> {
/>
) : (
<button
className="btn btn-square btn-primary btn-sm ifql-func--button"
className="btn btn-square btn-primary btn-sm flux-func--button"
onClick={this.handleOpenList}
tabIndex={0}
>
@ -72,7 +72,7 @@ export class FuncSelector extends PureComponent<Props, State> {
private get className(): string {
const {isOpen} = this.state
return classnames('ifql-func--selector', {open: isOpen})
return classnames('flux-func--selector', {open: isOpen})
}
private handleCloseList = () => {

View File

@ -2,12 +2,12 @@ import React, {PureComponent} from 'react'
import _ from 'lodash'
import Dropdown from 'src/shared/components/Dropdown'
import FuncArgInput from 'src/ifql/components/FuncArgInput'
import FuncArgTextArea from 'src/ifql/components/FuncArgTextArea'
import FuncArgInput from 'src/flux/components/FuncArgInput'
import FuncArgTextArea from 'src/flux/components/FuncArgTextArea'
import {getDeep} from 'src/utils/wrappers'
import {OnChangeArg, Func, Arg} from 'src/types/ifql'
import {argTypes} from 'src/ifql/constants'
import {OnChangeArg, Func, Arg} from 'src/types/flux'
import {argTypes} from 'src/flux/constants'
interface Props {
func: Func

View File

@ -0,0 +1,32 @@
import React, {SFC, MouseEvent} from 'react'
const handleClick = (e: MouseEvent<HTMLDivElement>): void => {
e.stopPropagation()
}
const LoaderSkeleton: SFC = () => {
return (
<>
<div className="flux-schema-tree flux-tree-node" onClick={handleClick}>
<div className="flux-schema-item no-hover">
<div className="flux-schema-item-toggle" />
<div className="flux-schema-item-skeleton" style={{width: '160px'}} />
</div>
</div>
<div className="flux-schema-tree flux-tree-node">
<div className="flux-schema-item no-hover">
<div className="flux-schema-item-toggle" />
<div className="flux-schema-item-skeleton" style={{width: '200px'}} />
</div>
</div>
<div className="flux-schema-tree flux-tree-node">
<div className="flux-schema-item no-hover">
<div className="flux-schema-item-toggle" />
<div className="flux-schema-item-skeleton" style={{width: '120px'}} />
</div>
</div>
</>
)
}
export default LoaderSkeleton

View File

@ -1,6 +1,6 @@
import React, {PureComponent} from 'react'
import DatabaseList from 'src/ifql/components/DatabaseList'
import DatabaseList from 'src/flux/components/DatabaseList'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {Service} from 'src/types'
@ -12,7 +12,7 @@ class SchemaExplorer extends PureComponent<Props> {
public render() {
const {service} = this.props
return (
<div className="ifql-schema-explorer">
<div className="flux-schema-explorer">
<FancyScrollbar>
<DatabaseList service={service} />
</FancyScrollbar>

View File

@ -14,10 +14,10 @@ export default class SchemaItem extends PureComponent<Props, State> {
const {schemaType} = this.props
return (
<div className={this.className}>
<div className="ifql-schema-item" onClick={this.handleClick}>
<div className="ifql-schema-item-toggle" />
<div className="flux-schema-item" onClick={this.handleClick}>
<div className="flux-schema-item-toggle" />
{name}
<span className="ifql-schema-type">{schemaType}</span>
<span className="flux-schema-type">{schemaType}</span>
</div>
</div>
)
@ -32,6 +32,6 @@ export default class SchemaItem extends PureComponent<Props, State> {
const {isOpen} = this.state
const openClass = isOpen ? 'expanded' : ''
return `ifql-schema-tree ifql-tree-node ${openClass}`
return `flux-schema-tree flux-tree-node ${openClass}`
}
}

View File

@ -4,8 +4,8 @@ import _ from 'lodash'
import {FluxTable} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import TableSidebarItem from 'src/ifql/components/TableSidebarItem'
import {vis} from 'src/ifql/constants'
import TableSidebarItem from 'src/flux/components/TableSidebarItem'
import {vis} from 'src/flux/constants'
interface Props {
data: FluxTable[]

View File

@ -1,7 +1,7 @@
import React, {PureComponent, MouseEvent} from 'react'
import {SchemaFilter, Service} from 'src/types'
import TagListItem from 'src/ifql/components/TagListItem'
import TagListItem from 'src/flux/components/TagListItem'
interface Props {
db: string
@ -41,8 +41,8 @@ export default class TagList extends PureComponent<Props, State> {
}
return (
<div className="ifql-schema-tree ifql-tree-node">
<div className="ifql-schema-item no-hover" onClick={this.handleClick}>
<div className="flux-schema-tree flux-tree-node">
<div className="flux-schema-item no-hover" onClick={this.handleClick}>
<div className="no-results">No more tag keys.</div>
</div>
</div>

View File

@ -8,12 +8,12 @@ import React, {
import _ from 'lodash'
import {Service, SchemaFilter, RemoteDataState} from 'src/types'
import {tagValues as fetchTagValues} from 'src/shared/apis/v2/metaQueries'
import {explorer} from 'src/ifql/constants'
import parseValuesColumn from 'src/shared/parsing/v2/tags'
import TagValueList from 'src/ifql/components/TagValueList'
import LoaderSkeleton from 'src/ifql/components/LoaderSkeleton'
import LoadingSpinner from 'src/ifql/components/LoadingSpinner'
import {tagValues as fetchTagValues} from 'src/shared/apis/flux/metaQueries'
import {explorer} from 'src/flux/constants'
import parseValuesColumn from 'src/shared/parsing/flux/values'
import TagValueList from 'src/flux/components/TagValueList'
import LoaderSkeleton from 'src/flux/components/LoaderSkeleton'
import LoadingSpinner from 'src/flux/components/LoadingSpinner'
interface Props {
tagKey: string
@ -60,10 +60,10 @@ export default class TagListItem extends PureComponent<Props, State> {
return (
<div className={this.className}>
<div className="ifql-schema-item" onClick={this.handleClick}>
<div className="ifql-schema-item-toggle" />
<div className="flux-schema-item" onClick={this.handleClick}>
<div className="flux-schema-item-toggle" />
{tagKey}
<span className="ifql-schema-type">Tag Key</span>
<span className="flux-schema-type">Tag Key</span>
</div>
{this.state.isOpen && (
<>
@ -71,7 +71,7 @@ export default class TagListItem extends PureComponent<Props, State> {
className="tag-value-list--header"
onClick={this.handleInputClick}
>
<div className="ifql-schema--filter">
<div className="flux-schema--filter">
<input
className="form-control input-sm"
placeholder={`Filter within ${tagKey}`}
@ -237,9 +237,9 @@ export default class TagListItem extends PureComponent<Props, State> {
const parsed = parseValuesColumn(response)
if (parsed.length !== 1) {
// We expect to never reach this state; instead, the IFQL server should
// We expect to never reach this state; instead, the Flux server should
// return a non-200 status code is handled earlier (after fetching).
// This return guards against some unexpected behavior---the IFQL server
// This return guards against some unexpected behavior---the Flux server
// returning a 200 status code but ALSO having an error in the CSV
// response body
return
@ -273,6 +273,6 @@ export default class TagListItem extends PureComponent<Props, State> {
const {isOpen} = this.state
const openClass = isOpen ? 'expanded' : ''
return `ifql-schema-tree ifql-tree-node ${openClass}`
return `flux-schema-tree flux-tree-node ${openClass}`
}
}

View File

@ -1,7 +1,7 @@
import React, {PureComponent, MouseEvent} from 'react'
import TagValueListItem from 'src/ifql/components/TagValueListItem'
import LoadingSpinner from 'src/ifql/components/LoadingSpinner'
import TagValueListItem from 'src/flux/components/TagValueListItem'
import LoadingSpinner from 'src/flux/components/LoadingSpinner'
import {Service, SchemaFilter} from 'src/types'
interface Props {
@ -40,8 +40,8 @@ export default class TagValueList extends PureComponent<Props> {
/>
))}
{shouldShowMoreValues && (
<div className="ifql-schema-tree ifql-tree-node">
<div className="ifql-schema-item no-hover">
<div className="flux-schema-tree flux-tree-node">
<div className="flux-schema-item no-hover">
<button
className="btn btn-xs btn-default increase-values-limit"
onClick={this.handleClick}

View File

@ -1,9 +1,9 @@
import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
import {tagKeys as fetchTagKeys} from 'src/shared/apis/v2/metaQueries'
import parseValuesColumn from 'src/shared/parsing/v2/tags'
import TagList from 'src/ifql/components/TagList'
import LoaderSkeleton from 'src/ifql/components/LoaderSkeleton'
import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
import parseValuesColumn from 'src/shared/parsing/flux/values'
import TagList from 'src/flux/components/TagList'
import LoaderSkeleton from 'src/flux/components/LoaderSkeleton'
import {Service, SchemaFilter, RemoteDataState} from 'src/types'
interface Props {
@ -38,10 +38,10 @@ class TagValueListItem extends PureComponent<Props, State> {
return (
<div className={this.className} onClick={this.handleClick}>
<div className="ifql-schema-item">
<div className="ifql-schema-item-toggle" />
<div className="flux-schema-item">
<div className="flux-schema-item-toggle" />
{value}
<span className="ifql-schema-type">Tag Value</span>
<span className="flux-schema-type">Tag Value</span>
</div>
{this.state.isOpen && (
<>
@ -49,7 +49,7 @@ class TagValueListItem extends PureComponent<Props, State> {
{!this.isLoading && (
<>
{!!this.tags.length && (
<div className="ifql-schema--filter">
<div className="flux-schema--filter">
<input
className="form-control input-sm"
placeholder={`Filter within ${value}`}
@ -110,7 +110,7 @@ class TagValueListItem extends PureComponent<Props, State> {
const {isOpen} = this.state
const openClass = isOpen ? 'expanded' : ''
return `ifql-schema-tree ifql-tree-node ${openClass}`
return `flux-schema-tree flux-tree-node ${openClass}`
}
private handleInputClick = (e: MouseEvent<HTMLInputElement>) => {

View File

@ -1,8 +1,8 @@
import React, {PureComponent, CSSProperties} from 'react'
import SchemaExplorer from 'src/ifql/components/SchemaExplorer'
import BodyBuilder from 'src/ifql/components/BodyBuilder'
import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor'
import TimeMachineVis from 'src/ifql/components/TimeMachineVis'
import SchemaExplorer from 'src/flux/components/SchemaExplorer'
import BodyBuilder from 'src/flux/components/BodyBuilder'
import TimeMachineEditor from 'src/flux/components/TimeMachineEditor'
import TimeMachineVis from 'src/flux/components/TimeMachineVis'
import Threesizer from 'src/shared/components/threesizer/Threesizer'
import {
Suggestion,
@ -11,7 +11,7 @@ import {
FlatBody,
ScriptStatus,
FluxTable,
} from 'src/types/ifql'
} from 'src/types/flux'
import {Service} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'

View File

@ -3,8 +3,8 @@ import {Controlled as CodeMirror, IInstance} from 'react-codemirror2'
import {EditorChange} from 'codemirror'
import 'src/external/codemirror'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeScript, OnSubmitScript} from 'src/types/ifql'
import {editor} from 'src/ifql/constants'
import {OnChangeScript, OnSubmitScript} from 'src/types/flux'
import {editor} from 'src/flux/constants'
interface Gutter {
line: number
@ -66,7 +66,7 @@ class TimeMachineEditor extends PureComponent<Props> {
extraKeys: {'Ctrl-Space': 'autocomplete'},
completeSingle: false,
autoRefresh: true,
mode: 'ifql',
mode: 'flux',
gutters: ['error-gutter'],
}

View File

@ -4,7 +4,7 @@ import {Grid, GridCellProps, AutoSizer, ColumnSizer} from 'react-virtualized'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {FluxTable} from 'src/types'
import {vis} from 'src/ifql/constants'
import {vis} from 'src/flux/constants'
const NUM_FIXED_ROWS = 1

View File

@ -4,10 +4,10 @@ import _ from 'lodash'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {FluxTable} from 'src/types'
import VisHeaderTabs from 'src/data_explorer/components/VisHeaderTabs'
import TableSidebar from 'src/ifql/components/TableSidebar'
import TimeMachineTable from 'src/ifql/components/TimeMachineTable'
import FluxGraph from 'src/ifql/components/FluxGraph'
import NoResults from 'src/ifql/components/NoResults'
import TableSidebar from 'src/flux/components/TableSidebar'
import TimeMachineTable from 'src/flux/components/TimeMachineTable'
import FluxGraph from 'src/flux/components/FluxGraph'
import NoResults from 'src/flux/components/NoResults'
interface Props {
data: FluxTable[]

View File

@ -0,0 +1,9 @@
import {ast} from 'src/flux/constants/ast'
import * as editor from 'src/flux/constants/editor'
import * as argTypes from 'src/flux/constants/argumentTypes'
import * as funcNames from 'src/flux/constants/funcNames'
import * as builder from 'src/flux/constants/builder'
import * as vis from 'src/flux/constants/vis'
import * as explorer from 'src/flux/constants/explorer'
export {ast, funcNames, argTypes, editor, builder, vis, explorer}

View File

@ -2,16 +2,16 @@ import React, {PureComponent, ReactChildren} from 'react'
import {connect} from 'react-redux'
import {WithRouterProps} from 'react-router'
import {IFQLPage} from 'src/ifql'
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
import {FluxPage} from 'src/flux'
import FluxOverlay from 'src/flux/components/FluxOverlay'
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
import {Source, Service, Notification} from 'src/types'
import {Links} from 'src/types/ifql'
import {Links} from 'src/types/flux'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {
updateScript as updateScriptAction,
UpdateScript,
} from 'src/ifql/actions'
} from 'src/flux/actions'
import * as a from 'src/shared/actions/overlayTechnology'
import * as b from 'src/shared/actions/services'
@ -54,7 +54,7 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
}
return (
<IFQLPage
<FluxPage
source={this.source}
services={services}
links={links}
@ -81,7 +81,7 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
showOverlay(
<OverlayContext.Consumer>
{({onDismissOverlay}) => (
<IFQLOverlay
<FluxOverlay
mode="new"
source={this.source}
onDismiss={onDismissOverlay}
@ -102,7 +102,7 @@ const mdtp = {
const mstp = ({sources, services, links, script}) => {
return {
links: links.ifql,
links: links.flux,
script,
sources,
services,

View File

@ -1,20 +1,20 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import TimeMachine from 'src/ifql/components/TimeMachine'
import IFQLHeader from 'src/ifql/components/IFQLHeader'
import TimeMachine from 'src/flux/components/TimeMachine'
import FluxHeader from 'src/flux/components/FluxHeader'
import {ErrorHandling} from 'src/shared/decorators/errors'
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
import {
analyzeSuccess,
ifqlTimeSeriesError,
fluxTimeSeriesError,
} from 'src/shared/copy/notifications'
import {UpdateScript} from 'src/ifql/actions'
import {UpdateScript} from 'src/flux/actions'
import {bodyNodes} from 'src/ifql/helpers'
import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis'
import {builder, argTypes} from 'src/ifql/constants'
import {bodyNodes} from 'src/flux/helpers'
import {getSuggestions, getAST, getTimeSeries} from 'src/flux/apis'
import {builder, argTypes} from 'src/flux/constants'
import {Source, Service, Notification, FluxTable} from 'src/types'
import {
@ -26,7 +26,7 @@ import {
DeleteFuncNodeArgs,
Func,
ScriptStatus,
} from 'src/types/ifql'
} from 'src/types/flux'
interface Status {
type: string
@ -54,10 +54,10 @@ interface State {
suggestions: Suggestion[]
}
export const IFQLContext = React.createContext()
export const FluxContext = React.createContext()
@ErrorHandling
export class IFQLPage extends PureComponent<Props, State> {
export class FluxPage extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
@ -90,7 +90,7 @@ export class IFQLPage extends PureComponent<Props, State> {
const {script} = this.props
return (
<IFQLContext.Provider value={this.getContext}>
<FluxContext.Provider value={this.getContext}>
<KeyboardShortcuts onControlEnter={this.getTimeSeries}>
<div className="page hosts-list-page">
{this.header}
@ -109,7 +109,7 @@ export class IFQLPage extends PureComponent<Props, State> {
/>
</div>
</KeyboardShortcuts>
</IFQLContext.Provider>
</FluxContext.Provider>
)
}
@ -121,7 +121,7 @@ export class IFQLPage extends PureComponent<Props, State> {
}
return (
<IFQLHeader service={this.service} onGetTimeSeries={this.getTimeSeries} />
<FluxHeader service={this.service} onGetTimeSeries={this.getTimeSeries} />
)
}
@ -371,7 +371,7 @@ export class IFQLPage extends PureComponent<Props, State> {
return `${source}\n\n`
}
// funcsToSource takes a list of funtion nodes and returns an ifql script
// funcsToSource takes a list of funtion nodes and returns an flux script
private funcsToSource = (funcs): string => {
return funcs.reduce((acc, f, i) => {
if (i === 0) {
@ -437,7 +437,7 @@ export class IFQLPage extends PureComponent<Props, State> {
} catch (error) {
this.setState({data: []})
notify(ifqlTimeSeriesError(error))
notify(fluxTimeSeriesError(error))
console.error('Could not get timeSeries', error)
}
@ -451,4 +451,4 @@ export class IFQLPage extends PureComponent<Props, State> {
}
}
export default IFQLPage
export default FluxPage

View File

@ -1,9 +1,9 @@
import uuid from 'uuid'
import _ from 'lodash'
import Walker from 'src/ifql/ast/walker'
import Walker from 'src/flux/ast/walker'
import {FlatBody, Func, Suggestion} from 'src/types/ifql'
import {FlatBody, Func, Suggestion} from 'src/types/flux'
interface Body extends FlatBody {
id: string

4
ui/src/flux/index.ts Normal file
View File

@ -0,0 +1,4 @@
import FluxPage from 'src/flux/containers/FluxPage'
import CheckServices from 'src/flux/containers/CheckServices'
export {FluxPage, CheckServices}

View File

@ -1,5 +1,5 @@
import {Action, ActionTypes} from 'src/ifql/actions'
import {editor} from 'src/ifql/constants'
import {Action, ActionTypes} from 'src/flux/actions'
import {editor} from 'src/flux/constants'
const scriptReducer = (
state: string = editor.DEFAULT_SCRIPT,

View File

@ -1,32 +0,0 @@
import React, {SFC, MouseEvent} from 'react'
const handleClick = (e: MouseEvent<HTMLDivElement>): void => {
e.stopPropagation()
}
const LoaderSkeleton: SFC = () => {
return (
<>
<div className="ifql-schema-tree ifql-tree-node" onClick={handleClick}>
<div className="ifql-schema-item no-hover">
<div className="ifql-schema-item-toggle" />
<div className="ifql-schema-item-skeleton" style={{width: '160px'}} />
</div>
</div>
<div className="ifql-schema-tree ifql-tree-node">
<div className="ifql-schema-item no-hover">
<div className="ifql-schema-item-toggle" />
<div className="ifql-schema-item-skeleton" style={{width: '200px'}} />
</div>
</div>
<div className="ifql-schema-tree ifql-tree-node">
<div className="ifql-schema-item no-hover">
<div className="ifql-schema-item-toggle" />
<div className="ifql-schema-item-skeleton" style={{width: '120px'}} />
</div>
</div>
</>
)
}
export default LoaderSkeleton

View File

@ -1,9 +0,0 @@
import {ast} from 'src/ifql/constants/ast'
import * as editor from 'src/ifql/constants/editor'
import * as argTypes from 'src/ifql/constants/argumentTypes'
import * as funcNames from 'src/ifql/constants/funcNames'
import * as builder from 'src/ifql/constants/builder'
import * as vis from 'src/ifql/constants/vis'
import * as explorer from 'src/ifql/constants/explorer'
export {ast, funcNames, argTypes, editor, builder, vis, explorer}

View File

@ -1,4 +0,0 @@
import IFQLPage from 'src/ifql/containers/IFQLPage'
import CheckServices from 'src/ifql/containers/CheckServices'
export {IFQLPage, CheckServices}

View File

@ -36,7 +36,7 @@ import {
} from 'src/kapacitor'
import {AdminChronografPage, AdminInfluxDBPage} from 'src/admin'
import {SourcePage, ManageSources} from 'src/sources'
import {CheckServices} from 'src/ifql'
import {CheckServices} from 'src/flux'
import NotFound from 'src/shared/components/NotFound'
import {getLinksAsync} from 'src/shared/actions/links'

View File

@ -322,7 +322,7 @@ export const createService = async (
source: Source,
{
url,
name = 'My IFQLD',
name = 'My FluxD',
type,
username,
password,

View File

@ -0,0 +1,216 @@
import React, {PureComponent, ReactElement, DragEvent} from 'react'
import classnames from 'classnames'
// import {notifyDashboardUploadFailed} from 'src/shared/copy/notifications'
interface Props {
fileTypesToAccept?: string
containerClass?: string
handleSubmit: (uploadContent: string, fileName: string) => void
submitText?: string
}
interface State {
inputContent: string | null
uploadContent: string
fileName: string
progress: string
dragClass: string
}
let dragCounter = 0
class DragAndDrop extends PureComponent<Props, State> {
public static defaultProps: Partial<Props> = {
submitText: 'Write this File',
}
private fileInput: HTMLInputElement
constructor(props: Props) {
super(props)
this.state = {
inputContent: null,
uploadContent: '',
fileName: '',
progress: '',
dragClass: 'drag-none',
}
}
public render() {
return (
<div className={this.containerClass}>
{/* (Invisible, covers entire screen)
This div handles drag only*/}
<div
onDrop={this.handleFile(true)}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragExit={this.handleDragLeave}
onDragLeave={this.handleDragLeave}
className="drag-and-drop--dropzone"
/>
{/* visible form, handles drag & click */}
{this.dragArea}
</div>
)
}
private get dragArea(): ReactElement<HTMLDivElement> {
return (
<div
className={this.dragAreaClass}
onClick={this.handleFileOpen}
onDrop={this.handleFile(true)}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragExit={this.handleDragLeave}
onDragLeave={this.handleDragLeave}
>
{this.dragAreaHeader}
<div className={this.infoClass} />
<input
type="file"
ref={r => (this.fileInput = r)}
className="drag-and-drop--input"
accept={this.fileTypesToAccept}
onChange={this.handleFile(false)}
/>
{this.buttons}
</div>
)
}
private get fileTypesToAccept(): string {
const {fileTypesToAccept} = this.props
if (!fileTypesToAccept) {
return '*'
}
return fileTypesToAccept
}
private get containerClass(): string {
const {dragClass} = this.state
return `drag-and-drop ${dragClass}`
}
private get infoClass(): string {
const {uploadContent} = this.state
return classnames('drag-and-drop--graphic', {success: uploadContent})
}
private get dragAreaClass(): string {
const {uploadContent} = this.state
return classnames('drag-and-drop--form', {active: !uploadContent})
}
private get dragAreaHeader(): ReactElement<HTMLHeadElement> {
const {uploadContent, fileName} = this.state
if (uploadContent) {
return <div className="drag-and-drop--header selected">{fileName}</div>
}
return (
<div className="drag-and-drop--header empty">
Drop a file here or click to upload
</div>
)
}
private get buttons(): ReactElement<HTMLSpanElement> | null {
const {uploadContent} = this.state
const {submitText} = this.props
if (!uploadContent) {
return null
}
return (
<span className="drag-and-drop--buttons">
<button className="btn btn-sm btn-success" onClick={this.handleSubmit}>
{submitText}
</button>
<button
className="btn btn-sm btn-default"
onClick={this.handleCancelFile}
>
Cancel
</button>
</span>
)
}
private handleSubmit = () => {
const {handleSubmit} = this.props
const {uploadContent, fileName} = this.state
handleSubmit(uploadContent, fileName)
}
private handleFile = (drop: boolean) => (e: any): void => {
let file
if (drop) {
file = e.dataTransfer.files[0]
this.setState({
dragClass: 'drag-none',
})
} else {
file = e.currentTarget.files[0]
}
if (!file) {
return
}
e.preventDefault()
e.stopPropagation()
const reader = new FileReader()
reader.readAsText(file)
reader.onload = loadEvent => {
this.setState({
uploadContent: loadEvent.target.result,
fileName: file.name,
})
}
}
private handleFileOpen = (): void => {
const {uploadContent} = this.state
if (uploadContent === '') {
this.fileInput.click()
}
}
private handleCancelFile = (): void => {
this.setState({uploadContent: ''})
this.fileInput.value = ''
}
private handleDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault()
e.stopPropagation()
}
private handleDragEnter = (e: DragEvent<HTMLDivElement>): void => {
dragCounter += 1
e.preventDefault()
this.setState({dragClass: 'drag-over'})
}
private handleDragLeave = (e: DragEvent<HTMLDivElement>): void => {
dragCounter -= 1
e.preventDefault()
if (dragCounter === 0) {
this.setState({dragClass: 'drag-none'})
}
}
}
export default DragAndDrop

View File

@ -15,7 +15,7 @@ const FuncSelectorInput: SFC<Props> = ({
onFilterKeyPress,
}) => (
<input
className="form-control input-sm ifql-func--input"
className="form-control input-sm flux-func--input"
type="text"
autoFocus={true}
placeholder="Add a Function..."

View File

@ -1,12 +1,11 @@
import React, {PureComponent, ComponentClass} from 'react'
import React, {PureComponent, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {dismissOverlay} from 'src/shared/actions/overlayTechnology'
interface Props {
OverlayNode?: ComponentClass<any>
OverlayNode?: Component<any>
dismissOnClickOutside?: boolean
dismissOnEscape?: boolean
transitionTime?: number
@ -98,8 +97,8 @@ const mapStateToProps = ({
transitionTime,
})
const mapDispatchToProps = dispatch => ({
handleDismissOverlay: bindActionCreators(dismissOverlay, dispatch),
})
const mapDispatchToProps = {
handleDismissOverlay: dismissOverlay,
}
export default connect(mapStateToProps, mapDispatchToProps)(Overlay)

View File

@ -0,0 +1,11 @@
import React, {SFC, ReactNode} from 'react'
interface Props {
children: ReactNode
}
const OverlayBody: SFC<Props> = ({children}) => (
<div className="overlay--body">{children}</div>
)
export default OverlayBody

View File

@ -0,0 +1,30 @@
import React, {Component, ReactNode, CSSProperties} from 'react'
interface Props {
children: ReactNode
maxWidth?: number
}
class OverlayContainer extends Component<Props> {
public static defaultProps: Partial<Props> = {
maxWidth: 600,
}
public render() {
const {children} = this.props
return (
<div className="overlay--container" style={this.style}>
{children}
</div>
)
}
private get style(): CSSProperties {
const {maxWidth} = this.props
return {maxWidth: `${maxWidth}px`}
}
}
export default OverlayContainer

View File

@ -0,0 +1,28 @@
import React, {PureComponent, ReactChildren} from 'react'
interface Props {
children?: ReactChildren
title: string
onDismiss?: () => void
}
class OverlayHeading extends PureComponent<Props> {
constructor(props: Props) {
super(props)
}
public render() {
const {title, onDismiss, children} = this.props
return (
<div className="overlay--heading">
<div className="overlay--title">{title}</div>
{onDismiss && (
<button className="overlay--dismiss" onClick={onDismiss} />
)}
{children && children}
</div>
)
}
}
export default OverlayHeading

View File

@ -1,7 +1,7 @@
export const modeIFQL = {
export const modeFlux = {
// The start state contains the rules that are intially used
start: [
// IFQL Syntax
// Flux Syntax
{
regex: /[|][>]/,
token: 'pipe-forward',

View File

@ -414,6 +414,30 @@ export const notifyDashboardDeleted = name => ({
message: `Dashboard ${name} deleted successfully.`,
})
export const notifyDashboardExported = name => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `Dashboard ${name} exported successfully.`,
})
export const notifyDashboardExportFailed = (name, errorMessage) => ({
...defaultErrorNotification,
duration: INFINITE,
message: `Failed to export Dashboard ${name}: ${errorMessage}.`,
})
export const notifyDashboardImported = name => ({
...defaultSuccessNotification,
icon: 'dash-h',
message: `Dashboard ${name} imported successfully.`,
})
export const notifyDashboardImportFailed = (fileName, errorMessage) => ({
...defaultErrorNotification,
duration: INFINITE,
message: `Failed to import Dashboard from file ${fileName}: ${errorMessage}.`,
})
export const notifyDashboardDeleteFailed = (name, errorMessage) =>
`Failed to delete Dashboard ${name}: ${errorMessage}.`
@ -609,7 +633,7 @@ export const notifyKapacitorNotFound = () => ({
message: 'We could not find a Kapacitor configuration for this source.',
})
// IFQL notifications
// Flux notifications
export const analyzeSuccess = {
...defaultSuccessNotification,
message: 'No errors found. Happy Happy Joy Joy!',
@ -621,27 +645,27 @@ export const couldNotGetServices = {
message: 'We could not get services',
}
export const ifqlCreated = {
export const fluxCreated = {
...defaultSuccessNotification,
message: 'IFQL Connection Created. Script your heart out!',
message: 'Flux Connection Created. Script your heart out!',
}
export const ifqlNotCreated = (message: string) => ({
export const fluxNotCreated = (message: string) => ({
...defaultErrorNotification,
message,
})
export const ifqlNotUpdated = (message: string) => ({
export const fluxNotUpdated = (message: string) => ({
...defaultErrorNotification,
message,
})
export const ifqlUpdated = {
export const fluxUpdated = {
...defaultSuccessNotification,
message: 'Connection Updated. Rejoice!',
}
export const ifqlTimeSeriesError = (message: string) => ({
export const fluxTimeSeriesError = (message: string) => ({
...defaultErrorNotification,
message: `Could not get data: ${message}`,
})

View File

@ -1,7 +1,7 @@
import _ from 'lodash'
import {FluxTable} from 'src/types'
import {parseResponse} from 'src/shared/parsing/v2/results'
import {parseResponse} from 'src/shared/parsing/flux/response'
const parseValuesColumn = (resp: string): string[] => {
const results = parseResponse(resp)

Some files were not shown because too many files have changed in this diff Show More