loading configuration dynamically + edit
This commit is contained in:
Родитель
4703576429
Коммит
270d1f91e6
|
@ -20,4 +20,7 @@ yarn-error.log*
|
|||
*.css
|
||||
|
||||
# generated merge temp files
|
||||
*.orig
|
||||
*.orig
|
||||
|
||||
# private files
|
||||
*.private.*
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Server",
|
||||
"program": "${workspaceRoot}\\server\\index.js",
|
||||
"outFiles": []
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceRoot}\\client:start",
|
||||
"outFiles": []
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Process",
|
||||
"address": "localhost",
|
||||
"port": 5858,
|
||||
"outFiles": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"dependencies": {
|
||||
"alt": "^0.18.6",
|
||||
"alt-utils": "^1.0.0",
|
||||
"body-parser": "^1.17.1",
|
||||
"lodash": "^4.17.4",
|
||||
"material-colors": "^1.2.5",
|
||||
"moment": "^2.18.0",
|
||||
|
|
|
@ -3,8 +3,13 @@ const express = require('express');
|
|||
const morgan = require('morgan');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json()); // to support JSON-encoded bodies
|
||||
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
|
||||
extended: true
|
||||
}));
|
||||
|
||||
// Setup logger
|
||||
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));
|
||||
|
@ -12,20 +17,58 @@ app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:htt
|
|||
// Serve static assets
|
||||
app.use(express.static(path.resolve(__dirname, '..', 'build')));
|
||||
|
||||
app.get('/api/config.js', (req, res) => {
|
||||
fs.readFile(path.join(__dirname, 'dashboards', 'bot-framework.js'), 'utf8', (err, data) => {
|
||||
function getFiles(dir, files_) {
|
||||
files_ = files_ || [];
|
||||
var files = fs.readdirSync(dir);
|
||||
for (var i in files){
|
||||
var name = dir + '/' + files[i];
|
||||
if (fs.statSync(name).isDirectory()){
|
||||
getFiles(name, files_);
|
||||
} else {
|
||||
files_.push(name);
|
||||
}
|
||||
}
|
||||
return files_;
|
||||
}
|
||||
|
||||
app.get('/api/dashboard.js', (req, res) => {
|
||||
|
||||
let privateDashboard = path.join(__dirname, 'dashboards', 'dashboard.private.js');
|
||||
let preconfDashboard = path.join(__dirname, 'dashboards', 'preconfigured', 'bot-framework.js');
|
||||
let dashboardPath = fs.existsSync(privateDashboard) ? privateDashboard : preconfDashboard;
|
||||
|
||||
fs.readFile(dashboardPath, 'utf8', (err, data) => {
|
||||
if (err) throw err;
|
||||
|
||||
// Ensuing this dashboard is loaded into the dashboards array on the page
|
||||
data += `
|
||||
window.dashboards = window.dashboards || [];
|
||||
window.dashboards.push(dashboard);
|
||||
let script = `
|
||||
(function (window) {
|
||||
var dashboard = (function () {
|
||||
${data}
|
||||
})();
|
||||
window.dashboards = window.dashboards || [];
|
||||
window.dashboards.push(dashboard);
|
||||
})(window);
|
||||
`;
|
||||
|
||||
res.send(data);
|
||||
res.send(script);
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/dashboard.js', (req, res) => {
|
||||
var content = (req.body && req.body.script) || '';
|
||||
console.dir(content);
|
||||
|
||||
fs.writeFile(path.join(__dirname, 'dashboards', 'dashboard.private.js'), content, err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.end(err);
|
||||
}
|
||||
|
||||
res.end(content);
|
||||
})
|
||||
});
|
||||
|
||||
// Always return the main index.html, so react-router render the route in the client
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
var dashboard = {
|
||||
return {
|
||||
config: {
|
||||
connections: {
|
||||
"application-insights": {
|
||||
appId: '4d567b3c-e52c-4139-8e56-8e573e55a06c',
|
||||
apiKey: 'tn6hhxs60afz4yzd7cp2nreph1wja3ecxtflq8rs'
|
||||
}
|
||||
},
|
||||
connections: { },
|
||||
layout: {
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
|
@ -1,7 +1,9 @@
|
|||
import alt, { AbstractActions } from '../alt';
|
||||
import * as request from 'xhr-request';
|
||||
|
||||
interface IConfigurationsActions {
|
||||
loadConfiguration(): any;
|
||||
saveConfiguration(dashboard: IDashboardConfig): any;
|
||||
failure(error: any): void;
|
||||
}
|
||||
|
||||
|
@ -10,30 +12,34 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
super(alt);
|
||||
}
|
||||
|
||||
private getScript(source: string, callback?: () => void): void {
|
||||
let script: any = document.createElement('script');
|
||||
let prior = document.getElementsByTagName('script')[0];
|
||||
script.async = 1;
|
||||
prior.parentNode.insertBefore(script, prior);
|
||||
|
||||
script.onload = script.onreadystatechange = (_, isAbort) => {
|
||||
if(isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
|
||||
script.onload = script.onreadystatechange = null;
|
||||
script = undefined;
|
||||
|
||||
if(!isAbort) { if(callback) callback(); }
|
||||
}
|
||||
};
|
||||
|
||||
script.src = source;
|
||||
}
|
||||
|
||||
loadConfiguration() {
|
||||
|
||||
return (dispatcher: (dashboard: IDashboardConfig) => void) => {
|
||||
|
||||
// request('/api/dashboard.js',
|
||||
// (error: any, scriptString: string) => {
|
||||
|
||||
// // do something
|
||||
// debugger;
|
||||
|
||||
// if (!scriptString || !scriptString.length) {
|
||||
// return this.failure(new Error('Could not load dashboard configuration from server'));
|
||||
// }
|
||||
|
||||
// let dashboard: IDashboardConfig = null;
|
||||
// eval(`dashboard = (function() { ${scriptString} })()`); /* tslint:disable-line */
|
||||
|
||||
// if (!dashboard) {
|
||||
// return this.failure(new Error('Could not load configuration from script: ' + scriptString));
|
||||
// }
|
||||
|
||||
// this.fixCalculatedProperties(dashboard);
|
||||
|
||||
// return dispatcher(dashboard);
|
||||
// });
|
||||
|
||||
this.getScript('/api/config.js', () => {
|
||||
let dashboards: IDashboardConfig[] = (window as any)["dashboards"];
|
||||
this.getScript('/api/dashboard.js', () => {
|
||||
let dashboards: IDashboardConfig[] = (window as any)['dashboards'];
|
||||
|
||||
if (!dashboards || !dashboards.length) {
|
||||
return this.failure(new Error('Could not load configuration'));
|
||||
|
@ -45,9 +51,158 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
};
|
||||
}
|
||||
|
||||
saveConfiguration(dashboard: IDashboardConfig) {
|
||||
return (dispatcher: (dashboard: IDashboardConfig) => void) => {
|
||||
|
||||
let stringDashboard = this.objectToString(dashboard);
|
||||
|
||||
request('/api/dashboard.js', {
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: { script: 'return ' + stringDashboard }
|
||||
},
|
||||
(error: any, json: any) => {
|
||||
|
||||
if (error) {
|
||||
return this.failure(error);
|
||||
}
|
||||
|
||||
return dispatcher(json);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
failure(error: any) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
private getScript(source: string, callback?: () => void): void {
|
||||
let script: any = document.createElement('script');
|
||||
let prior = document.getElementsByTagName('script')[0];
|
||||
script.async = 1;
|
||||
prior.parentNode.insertBefore(script, prior);
|
||||
|
||||
script.onload = script.onreadystatechange = (_, isAbort) => {
|
||||
if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
|
||||
script.onload = script.onreadystatechange = null;
|
||||
script = undefined;
|
||||
|
||||
if (!isAbort) { if (callback) { callback(); } }
|
||||
}
|
||||
};
|
||||
|
||||
script.src = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convret a json object with functions to string
|
||||
* @param obj an object with functions to convert to string
|
||||
*/
|
||||
private objectToString(obj: Object, indent: number = 0, lf: boolean = false): string {
|
||||
|
||||
let result = ''; //(lf ? '\n' : '') + '\t'.repeat(indent);
|
||||
let sind = '\t'.repeat(indent);
|
||||
let objectType = (Array.isArray(obj) && 'array') || typeof obj;
|
||||
|
||||
switch (objectType) {
|
||||
case 'object': {
|
||||
|
||||
// Iterating through all values in object
|
||||
let objectValue = '';
|
||||
Object.keys(obj).forEach((key: string, idx: number) => {
|
||||
|
||||
if (idx > 0) { objectValue += ',\n'; }
|
||||
|
||||
let value = this.objectToString(obj[key], indent + 1, true);
|
||||
|
||||
// if key contains '.' or '-'
|
||||
let skey = key.search(/\.|\-/g) >= 0 ? `"${key}"` : `${key}`;
|
||||
|
||||
objectValue += `${sind}\t${skey}: ${value}`;
|
||||
});
|
||||
|
||||
result += `{\n${objectValue}\n${sind}}`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'string':
|
||||
let stringValue = obj.toString().replace(/\"/g, '\\"');
|
||||
result += `"${stringValue}"`;
|
||||
break;
|
||||
|
||||
case 'function': {
|
||||
result += obj.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'number':
|
||||
case 'boolean': {
|
||||
result += `${obj}`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
let arrayValue = '';
|
||||
(obj as any[]).forEach((value: any, idx: number) => {
|
||||
arrayValue += idx > 0 ? ',' : '';
|
||||
arrayValue += this.objectToString(value, indent + 1, true);
|
||||
});
|
||||
|
||||
result += `[${arrayValue}]`;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error('An unhandled type was found: ' + typeof objectType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a string to object (with strings)
|
||||
* @param str a string to turn to object with functions
|
||||
*/
|
||||
private stringToObject(str: string): Object {
|
||||
// we doing this recursively so after the first one it will be an object
|
||||
let parsedString: Object;
|
||||
try {
|
||||
parsedString = JSON.parse(`{${str}}`);
|
||||
} catch (e) {
|
||||
parsedString = str;
|
||||
}
|
||||
|
||||
var obj = {};
|
||||
for (var i in parsedString) {
|
||||
if (typeof parsedString[i] === 'string') {
|
||||
if (parsedString[i].substring(0, 8) === 'function') {
|
||||
eval('obj[i] = ' + parsedString[i] ); /* tslint:disable-line */
|
||||
|
||||
} else {
|
||||
obj[i] = parsedString[i];
|
||||
}
|
||||
|
||||
} else if (typeof parsedString[i] === 'object') {
|
||||
obj[i] = this.stringToObject(parsedString[i]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private fixCalculatedProperties(dashboard: IDashboardConfig): void {
|
||||
dashboard.dataSources.forEach(dataSource => {
|
||||
let calculated: string = dataSource.calculated as any;
|
||||
if (calculated) {
|
||||
if (!calculated.startsWith('function(){return')) {
|
||||
throw new Error('calculated function format is not recognized: ' + calculated);
|
||||
}
|
||||
|
||||
calculated = calculated.substr('function(){return'.length, calculated.length - 'function(){return'.length - 1);
|
||||
eval('dataSource.calculated = ' + calculated); /* tslint:disable-line */
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const configurationsActions = alt.createActions<IConfigurationsActions>(ConfigurationsActions);
|
||||
|
|
|
@ -13,97 +13,44 @@ import ConnectionsStore from '../../stores/ConnectionsStore';
|
|||
import ConnectionsActions from '../../actions/ConnectionsActions';
|
||||
|
||||
interface IConfigDashboardState {
|
||||
dashboard?: IDashboardConfig;
|
||||
connections: IDictionary;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export default class ConfigDashboard extends React.Component<null, IConfigDashboardState> {
|
||||
interface IConfigDashboardProps {
|
||||
dashboard: IDashboardConfig;
|
||||
connections: IDictionary;
|
||||
}
|
||||
|
||||
state = {
|
||||
dashboard: null,
|
||||
export default class ConfigDashboard extends React.Component<IConfigDashboardProps, IConfigDashboardState> {
|
||||
|
||||
state: IConfigDashboardState = {
|
||||
connections: {},
|
||||
error: null
|
||||
};
|
||||
|
||||
dataSources: IDataSourceDictionary = {};
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.loadParams = this.loadParams.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.state.connections = ConnectionsStore.getState();
|
||||
this.onSave = this.onSave.bind(this);
|
||||
this.onSaveGoToDashboard = this.onSaveGoToDashboard.bind(this);
|
||||
|
||||
ConfigurationsActions.loadConfiguration();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
ConnectionsStore.listen(this.onChange);
|
||||
let { dashboard } = ConfigurationsStore.getState();
|
||||
|
||||
if (dashboard) {
|
||||
DataSourceConnector.createDataSources(dashboard, this.dataSources);
|
||||
this.setState({ dashboard });
|
||||
}
|
||||
|
||||
ConfigurationsStore.listen(state => {
|
||||
|
||||
let { dashboard } = state;
|
||||
|
||||
DataSourceConnector.createDataSources(dashboard, this.dataSources);
|
||||
this.setState({ dashboard });
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ConnectionsStore.unlisten(this.onChange);
|
||||
}
|
||||
|
||||
private loadParams(): any {
|
||||
var requiredParameters = {};
|
||||
_.values(this.dataSources).forEach(dataSource => {
|
||||
|
||||
// If no connection requirements were set, return
|
||||
if (!dataSource.plugin.connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connections[dataSource.plugin.connection]) {
|
||||
throw new Error(`No connection names ${dataSource.plugin.connection} was defined`);
|
||||
}
|
||||
|
||||
var connectionType = connections[dataSource.plugin.connection];
|
||||
requiredParameters[dataSource.plugin.connection] = {};
|
||||
connectionType.params.forEach(param => { requiredParameters[dataSource.plugin.connection][param] = null });
|
||||
|
||||
// Connection type is already defined - check params
|
||||
let { dashboard } = this.state;
|
||||
if (dashboard.config.connections[dataSource.plugin.connection]) {
|
||||
var connectionParams = dashboard.config.connections[dataSource.plugin.connection];
|
||||
|
||||
// Checking that all param definitions are defined
|
||||
connectionType.params.forEach(param => {
|
||||
requiredParameters[dataSource.plugin.connection][param] = connectionParams[param];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return requiredParameters;
|
||||
}
|
||||
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
onParamChange(connectionKey, paramKey, value) {
|
||||
//debugger;
|
||||
this.state.connections[connectionKey][paramKey] = value;
|
||||
this.setState({ connections: this.state.connections });
|
||||
let { connections } = this.state;
|
||||
connections[connectionKey] = connections[connectionKey] || {};
|
||||
connections[connectionKey][paramKey] = value;
|
||||
this.setState({ connections });
|
||||
}
|
||||
|
||||
onSave() {
|
||||
|
||||
let { dashboard } = this.props;
|
||||
let { connections } = this.state;
|
||||
dashboard.config.connections = connections;
|
||||
ConfigurationsActions.saveConfiguration(dashboard);
|
||||
}
|
||||
|
||||
onSaveGoToDashboard() {
|
||||
|
@ -112,11 +59,11 @@ export default class ConfigDashboard extends React.Component<null, IConfigDashbo
|
|||
|
||||
render() {
|
||||
|
||||
if (!this.state.dashboard) {
|
||||
if (!this.props.dashboard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let connections = this.loadParams();
|
||||
let { connections } = this.props;
|
||||
let { error } = this.state;
|
||||
|
||||
return (
|
||||
|
@ -130,7 +77,7 @@ export default class ConfigDashboard extends React.Component<null, IConfigDashbo
|
|||
<TextField
|
||||
id="paramKey"
|
||||
label={paramKey}
|
||||
defaultValue={connections[connectionKey][paramKey]}
|
||||
defaultValue={connections[connectionKey] && connections[connectionKey][paramKey] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in required connection parameter"
|
||||
className="md-cell md-cell--bottom"
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import Toolbar from 'react-md/lib/Toolbars';
|
||||
import { Spinner } from '../Spinner';
|
||||
|
||||
import * as ReactGridLayout from 'react-grid-layout';
|
||||
var ResponsiveReactGridLayout = ReactGridLayout.Responsive;
|
||||
var WidthProvider = ReactGridLayout.WidthProvider;
|
||||
ResponsiveReactGridLayout = WidthProvider(ResponsiveReactGridLayout);
|
||||
|
||||
import ElementConnector from '../ElementConnector';
|
||||
import { loadDialogsFromDashboard } from '../generic/Dialogs';
|
||||
|
||||
import ConfigurationsActions from '../../actions/ConfigurationsActions';
|
||||
import ConfigurationsStore from '../../stores/ConfigurationsStore';
|
||||
|
||||
interface IDashboardState {
|
||||
mounted?: boolean;
|
||||
currentBreakpoint?: string;
|
||||
layouts?: ILayouts;
|
||||
grid?: any;
|
||||
}
|
||||
|
||||
interface IDashboardProps {
|
||||
dashboard?: IDashboardConfig;
|
||||
}
|
||||
|
||||
export default class Dashboard extends React.Component<IDashboardProps, IDashboardState> {
|
||||
|
||||
layouts = {};
|
||||
|
||||
state = {
|
||||
currentBreakpoint: 'lg',
|
||||
mounted: false,
|
||||
layouts: { },
|
||||
grid: null
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
let { dashboard } = this.props;
|
||||
let { mounted } = this.state;
|
||||
|
||||
if (dashboard && !mounted) {
|
||||
|
||||
const layout = dashboard.config.layout;
|
||||
|
||||
// For each column, create a layout according to number of columns
|
||||
var layouts = ElementConnector.loadLayoutFromDashboard(dashboard, dashboard);
|
||||
|
||||
this.layouts = layouts;
|
||||
this.setState({
|
||||
mounted: true,
|
||||
layouts: { lg: layouts['lg'] },
|
||||
grid: {
|
||||
className: 'layout',
|
||||
rowHeight: layout.rowHeight || 30,
|
||||
cols: layout.cols,
|
||||
breakpoints: layout.breakpoints,
|
||||
verticalCompact: false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.componentDidMount();
|
||||
}
|
||||
|
||||
onBreakpointChange = (breakpoint) => {
|
||||
var layouts = this.state.layouts;
|
||||
layouts[breakpoint] = layouts[breakpoint] || this.layouts[breakpoint];
|
||||
this.setState({
|
||||
currentBreakpoint: breakpoint,
|
||||
layouts: layouts
|
||||
});
|
||||
}
|
||||
|
||||
onLayoutChange = (layout, layouts) => {
|
||||
// this.props.onLayoutChange(layout, layouts);
|
||||
var breakpoint = this.state.currentBreakpoint;
|
||||
var newLayouts = this.state.layouts;
|
||||
newLayouts[breakpoint] = layout;
|
||||
this.setState({
|
||||
layouts: newLayouts
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let { dashboard } = this.props;
|
||||
var { currentBreakpoint, grid } = this.state;
|
||||
var layout = this.state.layouts[currentBreakpoint];
|
||||
|
||||
if (!grid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Creating visual elements
|
||||
var elements = ElementConnector.loadElementsFromDashboard(dashboard, layout);
|
||||
|
||||
// Creating filter elements
|
||||
var { filters, /*additionalFilters*/ } = ElementConnector.loadFiltersFromDashboard(dashboard);
|
||||
|
||||
// Loading dialogs
|
||||
var dialogs = loadDialogsFromDashboard(dashboard);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Toolbar>
|
||||
{filters}
|
||||
<Spinner />
|
||||
</Toolbar>
|
||||
<ResponsiveReactGridLayout
|
||||
{...grid}
|
||||
layouts={this.state.layouts}
|
||||
onBreakpointChange={this.onBreakpointChange}
|
||||
onLayoutChange={this.onLayoutChange}
|
||||
// WidthProvider option
|
||||
measureBeforeMount={false}
|
||||
// I like to have it animate on mount. If you don't, delete `useCSSTransforms` (it's default `true`)
|
||||
// and set `measureBeforeMount={true}`.
|
||||
useCSSTransforms={this.state.mounted}
|
||||
>
|
||||
{elements}
|
||||
</ResponsiveReactGridLayout>
|
||||
{dialogs}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,16 +17,16 @@ var WidthProvider = ReactGridLayout.WidthProvider;
|
|||
ResponsiveReactGridLayout = WidthProvider(ResponsiveReactGridLayout);
|
||||
|
||||
interface IDialogProps {
|
||||
dialogData: IDialog
|
||||
dashboard: IDashboardConfig
|
||||
dialogData: IDialog;
|
||||
dashboard: IDashboardConfig;
|
||||
}
|
||||
|
||||
interface IDialogState {
|
||||
dialogId?: string
|
||||
dialogArgs?: IDictionary
|
||||
mounted?: boolean
|
||||
currentBreakpoint?: string
|
||||
layouts?: ILayouts
|
||||
dialogId?: string;
|
||||
dialogArgs?: IDictionary;
|
||||
mounted?: boolean;
|
||||
currentBreakpoint?: string;
|
||||
layouts?: ILayouts;
|
||||
}
|
||||
|
||||
export default class Dialog extends React.PureComponent<IDialogProps, IDialogState> {
|
||||
|
@ -48,10 +48,10 @@ export default class Dialog extends React.PureComponent<IDialogProps, IDialogSta
|
|||
selectedValue: null
|
||||
}
|
||||
};
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dialogDS ] }, this.dataSources);
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dialogDS ] }, this.props.dashboard.config.connections);
|
||||
|
||||
// Adding other data sources
|
||||
DataSourceConnector.createDataSources(this.props.dialogData, this.dataSources);
|
||||
DataSourceConnector.createDataSources(this.props.dialogData, this.props.dashboard.config.connections);
|
||||
|
||||
var layouts = ElementConnector.loadLayoutFromDashboard(this.props.dialogData, this.props.dashboard);
|
||||
|
||||
|
@ -62,8 +62,6 @@ export default class Dialog extends React.PureComponent<IDialogProps, IDialogSta
|
|||
componentDidMount() {
|
||||
this.setState({ mounted: true });
|
||||
DialogsStore.listen(this.onChange);
|
||||
|
||||
DataSourceConnector.connectDataSources(this.dataSources);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface IDataSource {
|
|||
plugin : IDataSourcePlugin;
|
||||
action: any;
|
||||
store: any;
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
export interface IDataSourceDictionary {
|
||||
|
@ -24,7 +25,7 @@ export class DataSourceConnector {
|
|||
|
||||
private static dataSources: IDataSourceDictionary = {};
|
||||
|
||||
static createDataSource(dataSourceConfig) {
|
||||
static createDataSource(dataSourceConfig: any, connections: IConnections) {
|
||||
|
||||
var config = dataSourceConfig || {};
|
||||
if (!config.id || !config.type) {
|
||||
|
@ -34,7 +35,7 @@ export class DataSourceConnector {
|
|||
// Dynamically load the plugin from the plugins directory
|
||||
var pluginPath = './plugins/' + config.type;
|
||||
var PluginClass = require(pluginPath);
|
||||
var plugin : any = new PluginClass.default(config);
|
||||
var plugin : any = new PluginClass.default(config, connections);
|
||||
|
||||
// Creating actions class
|
||||
var ActionClass = DataSourceConnector.createActionClass(plugin);
|
||||
|
@ -47,52 +48,56 @@ export class DataSourceConnector {
|
|||
config,
|
||||
plugin,
|
||||
action: ActionClass,
|
||||
store: StoreClass
|
||||
store: StoreClass,
|
||||
initialized: false
|
||||
}
|
||||
|
||||
return DataSourceConnector.dataSources[config.id];
|
||||
}
|
||||
|
||||
static createDataSources(dsContainer: IDataSourceContainer, containerDataSources: IDataSourceDictionary) {
|
||||
static createDataSources(dsContainer: IDataSourceContainer, connections: IConnections) {
|
||||
dsContainer.dataSources.forEach(source => {
|
||||
var dataSource = DataSourceConnector.createDataSource(source);
|
||||
containerDataSources[dataSource.id] = dataSource;
|
||||
var dataSource = DataSourceConnector.createDataSource(source, connections);
|
||||
DataSourceConnector.connectDataSource(dataSource);
|
||||
});
|
||||
|
||||
DataSourceConnector.initializeDataSources();
|
||||
}
|
||||
|
||||
private static connectDataSource(sourceDS: IDataSource) {
|
||||
// Connect sources and dependencies
|
||||
sourceDS.store.listen((state) => {
|
||||
|
||||
Object.keys(this.dataSources).forEach(checkDSId => {
|
||||
var checkDS = this.dataSources[checkDSId];
|
||||
var dependencies = checkDS.plugin.getDependencies() || {};
|
||||
|
||||
let connected = _.find(_.keys(dependencies), dependencyKey => {
|
||||
let dependencyValue = dependencies[dependencyKey] || '';
|
||||
return (dependencyValue === sourceDS.id || dependencyValue.startsWith(sourceDS.id + ':'));
|
||||
})
|
||||
|
||||
if (connected) {
|
||||
|
||||
// Todo: add check that all dependencies are met
|
||||
checkDS.action.updateDependencies.defer(state);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static connectDataSources(dataSources: IDataSourceDictionary) {
|
||||
// Connect sources and dependencies
|
||||
var sourcesIDs = Object.keys(dataSources);
|
||||
sourcesIDs.forEach(sourceDSId => {
|
||||
var sourceDS = dataSources[sourceDSId];
|
||||
|
||||
sourceDS.store.listen((state) => {
|
||||
|
||||
sourcesIDs.forEach(checkDSId => {
|
||||
var checkDS = dataSources[checkDSId];
|
||||
var dependencies = checkDS.plugin.getDependencies() || {};
|
||||
|
||||
let connected = _.find(_.keys(dependencies), dependencyKey => {
|
||||
let dependencyValue = dependencies[dependencyKey] || '';
|
||||
return (dependencyValue === sourceDSId || dependencyValue.startsWith(sourceDSId + ':'));
|
||||
})
|
||||
|
||||
if (connected) {
|
||||
|
||||
// Todo: add check that all dependencies are met
|
||||
checkDS.action.updateDependencies.defer(state);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
static initializeDataSources() {
|
||||
// Call initalize methods
|
||||
sourcesIDs.forEach(sourceDSId => {
|
||||
var sourceDS = dataSources[sourceDSId];
|
||||
Object.keys(this.dataSources).forEach(sourceDSId => {
|
||||
var sourceDS = this.dataSources[sourceDSId];
|
||||
|
||||
if (sourceDS.initialized) { return; }
|
||||
|
||||
if (typeof sourceDS.action['initialize'] === 'function') {
|
||||
sourceDS.action.initialize.defer();
|
||||
}
|
||||
|
||||
sourceDS.initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,6 +163,10 @@ export class DataSourceConnector {
|
|||
}
|
||||
}
|
||||
|
||||
static getDataSources(): IDataSourceDictionary {
|
||||
return this.dataSources;
|
||||
}
|
||||
|
||||
private static createActionClass(plugin: IDataSourcePlugin) : any {
|
||||
class NewActionClass {
|
||||
constructor() {}
|
||||
|
|
|
@ -23,10 +23,9 @@ export default class ApplicationInsightsEvents extends DataSourcePlugin {
|
|||
|
||||
type = 'ApplicationInsights-Events';
|
||||
defaultProperty = 'values';
|
||||
connection = 'application-insights';
|
||||
|
||||
constructor(options: IEventsOptions) {
|
||||
super(options);
|
||||
constructor(options: IEventsOptions, connections: IDict<IStringDictionary>) {
|
||||
super(options, connections);
|
||||
|
||||
// var props = this._props;
|
||||
// var params: any = props.params;
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
//import * as $ from 'jquery';
|
||||
import * as request from 'xhr-request';
|
||||
import * as _ from 'lodash';
|
||||
import {DataSourcePlugin, IDataSourceOptions} from '../DataSourcePlugin';
|
||||
import { appInsightsUri, appId, apiKey } from './common';
|
||||
import { DataSourcePlugin, IDataSourceOptions } from '../DataSourcePlugin';
|
||||
import { appInsightsUri } from './common';
|
||||
import ApplicationInsightsConnection from '../../connections/application-insights';
|
||||
|
||||
let connectionType = new ApplicationInsightsConnection();
|
||||
|
||||
interface IQueryOptions extends IDataSourceOptions {
|
||||
query: string
|
||||
|
@ -14,13 +17,13 @@ export default class ApplicationInsightsQuery extends DataSourcePlugin {
|
|||
|
||||
type = 'ApplicationInsights-Query';
|
||||
defaultProperty = 'values';
|
||||
connection = 'application-insights';
|
||||
connectionType = connectionType.type;
|
||||
|
||||
/**
|
||||
* @param options - Options object
|
||||
*/
|
||||
constructor(options: IQueryOptions) {
|
||||
super(options);
|
||||
constructor(options: IQueryOptions, connections: IDict<IStringDictionary>) {
|
||||
super(options, connections);
|
||||
|
||||
var props = this._props;
|
||||
var params: any = props.params;
|
||||
|
@ -50,6 +53,15 @@ export default class ApplicationInsightsQuery extends DataSourcePlugin {
|
|||
};
|
||||
}
|
||||
|
||||
// Validate connection
|
||||
let connection = this.getConnection();
|
||||
let { appId, apiKey } = connection;
|
||||
if (!connection || !apiKey || !appId) {
|
||||
return (dispatch) => {
|
||||
return dispatch();
|
||||
};
|
||||
}
|
||||
|
||||
var { queryTimespan } = dependencies;
|
||||
|
||||
var params: any = this._props.params;
|
||||
|
|
|
@ -14,8 +14,8 @@ export default class Constant extends DataSourcePlugin {
|
|||
type = 'Constant';
|
||||
defaultProperty = 'selectedValue';
|
||||
|
||||
constructor(options: IConstantOptions) {
|
||||
super(options);
|
||||
constructor(options: IConstantOptions, connections: IDict<IStringDictionary>) {
|
||||
super(options, connections);
|
||||
|
||||
var props = this._props;
|
||||
var params = options.params;
|
||||
|
|
|
@ -13,7 +13,7 @@ export interface IDataSourcePlugin {
|
|||
|
||||
type: string;
|
||||
defaultProperty: string;
|
||||
connection: string;
|
||||
connectionType: string;
|
||||
|
||||
_props: {
|
||||
id: string,
|
||||
|
@ -32,15 +32,15 @@ export interface IDataSourcePlugin {
|
|||
getParamKeys(): string[];
|
||||
getParams(): IDictionary;
|
||||
getCalculated(): ICalculated;
|
||||
getConnection(): IStringDictionary;
|
||||
}
|
||||
|
||||
export abstract class DataSourcePlugin implements IDataSourcePlugin {
|
||||
|
||||
abstract type: string;
|
||||
abstract defaultProperty: string;
|
||||
connectionType: string = null;
|
||||
|
||||
connection: string = null;
|
||||
|
||||
_props = {
|
||||
id: '',
|
||||
dependencies: {} as any,
|
||||
|
@ -53,7 +53,7 @@ export abstract class DataSourcePlugin implements IDataSourcePlugin {
|
|||
/**
|
||||
* @param {DataSourcePlugin} options - Options object
|
||||
*/
|
||||
constructor(options: IDictionary) {
|
||||
constructor(options: IDictionary, protected connections: IConnections = {}) {
|
||||
|
||||
var props = this._props;
|
||||
props.id = options.id;
|
||||
|
@ -62,6 +62,8 @@ export abstract class DataSourcePlugin implements IDataSourcePlugin {
|
|||
props.actions.push.apply(props.actions, options.actions || []);
|
||||
props.params = options.params || {};
|
||||
props.calculated = options.calculated || {};
|
||||
|
||||
this.updateDependencies = this.updateDependencies.bind(this);
|
||||
}
|
||||
|
||||
bind (actionClass: any) {
|
||||
|
@ -69,6 +71,15 @@ export abstract class DataSourcePlugin implements IDataSourcePlugin {
|
|||
actionClass._props = this._props;
|
||||
}
|
||||
|
||||
updateConnections(connections: IConnections) {
|
||||
this.connections = connections;
|
||||
}
|
||||
|
||||
getConnection(): IConnection {
|
||||
return (this.connections && this.connections[this.connectionType]) || {};
|
||||
}
|
||||
|
||||
|
||||
abstract updateDependencies (dependencies: IDictionary, args: IDictionary, callback: () => void): void;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,39 +1,23 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import Toolbar from 'react-md/lib/Toolbars';
|
||||
import { Spinner } from '../components/Spinner';
|
||||
|
||||
import * as ReactGridLayout from 'react-grid-layout';
|
||||
var ResponsiveReactGridLayout = ReactGridLayout.Responsive;
|
||||
var WidthProvider = ReactGridLayout.WidthProvider;
|
||||
ResponsiveReactGridLayout = WidthProvider(ResponsiveReactGridLayout);
|
||||
|
||||
import { DataSourceConnector, IDataSourceDictionary } from '../data-sources';
|
||||
import ElementConnector from '../components/ElementConnector';
|
||||
import { loadDialogsFromDashboard } from '../components/generic/Dialogs';
|
||||
import DashboardComponent from '../components/Dashboard';
|
||||
import ConfigDashboard from '../components/ConfigDashboard';
|
||||
|
||||
import ConfigurationsActions from '../actions/ConfigurationsActions';
|
||||
import ConfigurationsStore from '../stores/ConfigurationsStore';
|
||||
|
||||
interface IDashboardState {
|
||||
dashboard?: IDashboardConfig;
|
||||
mounted?: boolean;
|
||||
currentBreakpoint?: string;
|
||||
layouts?: ILayouts;
|
||||
grid?: any;
|
||||
connections?: IConnections;
|
||||
connectionsMissing?: boolean;
|
||||
}
|
||||
|
||||
export default class Dashboard extends React.Component<any, IDashboardState> {
|
||||
|
||||
layouts = {};
|
||||
dataSources: IDataSourceDictionary = {};
|
||||
|
||||
state = {
|
||||
state: IDashboardState = {
|
||||
dashboard: null,
|
||||
currentBreakpoint: 'lg',
|
||||
mounted: false,
|
||||
layouts: { },
|
||||
grid: null
|
||||
connections: {},
|
||||
connectionsMissing: false
|
||||
};
|
||||
|
||||
constructor(props: any) {
|
||||
|
@ -43,96 +27,30 @@ export default class Dashboard extends React.Component<any, IDashboardState> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ mounted: true });
|
||||
|
||||
let { dashboard } = ConfigurationsStore.getState();
|
||||
this.setState({ dashboard });
|
||||
this.setState(ConfigurationsStore.getState());
|
||||
|
||||
ConfigurationsStore.listen(state => {
|
||||
|
||||
let { dashboard } = state;
|
||||
|
||||
const layout = dashboard.config.layout;
|
||||
|
||||
DataSourceConnector.createDataSources(dashboard, this.dataSources);
|
||||
DataSourceConnector.connectDataSources(this.dataSources);
|
||||
|
||||
// For each column, create a layout according to number of columns
|
||||
var layouts = ElementConnector.loadLayoutFromDashboard(dashboard, dashboard);
|
||||
|
||||
this.layouts = layouts;
|
||||
this.setState({
|
||||
dashboard,
|
||||
layouts: { lg: layouts['lg'] },
|
||||
grid: {
|
||||
className: 'layout',
|
||||
rowHeight: layout.rowHeight || 30,
|
||||
cols: layout.cols,
|
||||
breakpoints: layout.breakpoints,
|
||||
verticalCompact: false
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
onBreakpointChange = (breakpoint) => {
|
||||
var layouts = this.state.layouts;
|
||||
layouts[breakpoint] = layouts[breakpoint] || this.layouts[breakpoint];
|
||||
this.setState({
|
||||
currentBreakpoint: breakpoint,
|
||||
layouts: layouts
|
||||
});
|
||||
}
|
||||
|
||||
onLayoutChange = (layout, layouts) => {
|
||||
// this.props.onLayoutChange(layout, layouts);
|
||||
var breakpoint = this.state.currentBreakpoint;
|
||||
var newLayouts = this.state.layouts;
|
||||
newLayouts[breakpoint] = layout;
|
||||
this.setState({
|
||||
layouts: newLayouts
|
||||
this.setState(ConfigurationsStore.getState());
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
var { dashboard, currentBreakpoint, grid } = this.state;
|
||||
var layout = this.state.layouts[currentBreakpoint];
|
||||
var { dashboard, connections, connectionsMissing } = this.state;
|
||||
|
||||
if (!grid) {
|
||||
if (!dashboard) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Creating visual elements
|
||||
var elements = ElementConnector.loadElementsFromDashboard(dashboard, layout);
|
||||
|
||||
// Creating filter elements
|
||||
var { filters, /*additionalFilters*/ } = ElementConnector.loadFiltersFromDashboard(dashboard);
|
||||
|
||||
// Loading dialogs
|
||||
var dialogs = loadDialogsFromDashboard(dashboard);
|
||||
if (connectionsMissing) {
|
||||
return (
|
||||
<ConfigDashboard dashboard={dashboard} connections={connections} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Toolbar>
|
||||
{filters}
|
||||
<Spinner />
|
||||
</Toolbar>
|
||||
<ResponsiveReactGridLayout
|
||||
{...grid}
|
||||
layouts={this.state.layouts}
|
||||
onBreakpointChange={this.onBreakpointChange}
|
||||
onLayoutChange={this.onLayoutChange}
|
||||
// WidthProvider option
|
||||
measureBeforeMount={false}
|
||||
// I like to have it animate on mount. If you don't, delete `useCSSTransforms` (it's default `true`)
|
||||
// and set `measureBeforeMount={true}`.
|
||||
useCSSTransforms={this.state.mounted}
|
||||
>
|
||||
{elements}
|
||||
</ResponsiveReactGridLayout>
|
||||
{dialogs}
|
||||
</div>
|
||||
<DashboardComponent dashboard={dashboard} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
import alt, { AbstractStoreModel } from '../alt';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import connections from '../data-sources/connections';
|
||||
import { DataSourceConnector, IDataSourceDictionary } from '../data-sources';
|
||||
import configurationActions from '../actions/ConfigurationsActions';
|
||||
|
||||
interface IConfigurationsStoreState {
|
||||
dashboard: IDashboardConfig;
|
||||
connections: IDictionary;
|
||||
connectionsMissing: boolean;
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
class ConfigurationsStore extends AbstractStoreModel<IConfigurationsStoreState> implements IConfigurationsStoreState {
|
||||
|
||||
dashboard: IDashboardConfig;
|
||||
connections: IDictionary;
|
||||
connectionsMissing: boolean;
|
||||
loaded: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.dashboard = null;
|
||||
this.connections = {};
|
||||
this.connectionsMissing = false;
|
||||
this.loaded = false;
|
||||
|
||||
this.bindListeners({
|
||||
loadConfiguration: configurationActions.loadConfiguration
|
||||
|
@ -22,6 +34,52 @@ class ConfigurationsStore extends AbstractStoreModel<IConfigurationsStoreState>
|
|||
|
||||
loadConfiguration(dashboard: IDashboardConfig) {
|
||||
this.dashboard = dashboard;
|
||||
|
||||
if (this.dashboard && !this.loaded) {
|
||||
DataSourceConnector.createDataSources(dashboard, dashboard.config.connections);
|
||||
|
||||
this.connections = this.getConnections(dashboard);
|
||||
|
||||
// Checking for missing connection params
|
||||
this.connectionsMissing = Object.keys(this.connections).some(connectionKey => {
|
||||
var connection = this.connections[connectionKey];
|
||||
|
||||
return Object.keys(connection).some(paramKey => !connection[paramKey]);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private getConnections(dashboard: IDashboardConfig): any {
|
||||
let requiredParameters = {};
|
||||
let dataSources = DataSourceConnector.getDataSources();
|
||||
_.values(dataSources).forEach(dataSource => {
|
||||
|
||||
// If no connection requirements were set, return
|
||||
let connectionTypeName = dataSource.plugin.connectionType;
|
||||
if (!connectionTypeName) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connections[connectionTypeName]) {
|
||||
throw new Error(`No connection names ${connectionTypeName} was defined`);
|
||||
}
|
||||
|
||||
var connectionType = connections[connectionTypeName];
|
||||
requiredParameters[connectionTypeName] = {};
|
||||
connectionType.params.forEach(param => { requiredParameters[connectionTypeName][param] = null });
|
||||
|
||||
// Connection type is already defined - check params
|
||||
if (dashboard.config.connections[connectionTypeName]) {
|
||||
var connectionParams = dashboard.config.connections[connectionTypeName];
|
||||
|
||||
// Checking that all param definitions are defined
|
||||
connectionType.params.forEach(param => {
|
||||
requiredParameters[connectionTypeName][param] = connectionParams[param];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return requiredParameters;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ describe('Data Source: Application Insights: Query', () => {
|
|||
beforeAll(() => {
|
||||
|
||||
mockRequests();
|
||||
DataSourceConnector.createDataSources({ dataSources: dataSourcesMock }, dataSources);
|
||||
DataSourceConnector.connectDataSources(dataSources);
|
||||
DataSourceConnector.createDataSources({ dataSources: dataSourcesMock }, {});
|
||||
dataSources.timespan.action.updateDependencies();
|
||||
});
|
||||
|
||||
|
|
|
@ -7,10 +7,7 @@ describe('Data Source: Constant', () => {
|
|||
let dataSources: IDataSourceDictionary = {};
|
||||
|
||||
beforeAll(() => {
|
||||
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dataSourceMock ]}, dataSources);
|
||||
DataSourceConnector.connectDataSources(dataSources);
|
||||
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dataSourceMock ]}, {});
|
||||
});
|
||||
|
||||
it ('Check basic data == 3 rows', () => {
|
||||
|
|
|
@ -18,8 +18,7 @@ describe('Table', () => {
|
|||
|
||||
beforeAll(() => {
|
||||
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dataSourceMock ]}, dataSources);
|
||||
DataSourceConnector.connectDataSources(dataSources);
|
||||
DataSourceConnector.createDataSources({ dataSources: [ dataSourceMock ]}, {});
|
||||
|
||||
table = TestUtils.renderIntoDocument(<Table {...tablePropsMock} />);
|
||||
TestUtils.isElementOfType(table, 'div');
|
||||
|
|
|
@ -2,9 +2,12 @@ type IDict<T> = { [id: string]: T };
|
|||
type IDictionary = IDict<any>;
|
||||
type IStringDictionary = IDict<string>;
|
||||
|
||||
type IConnection = IStringDictionary;
|
||||
type IConnections = IDict<IConnection>;
|
||||
|
||||
interface IDashboardConfig extends IDataSourceContainer, IElementsContainer {
|
||||
config: {
|
||||
connections: IDictionary,
|
||||
connections: IConnections,
|
||||
layout: {
|
||||
isDraggable?: boolean
|
||||
isResizable?: boolean
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"acceptance-tests",
|
||||
"webpack",
|
||||
"jest",
|
||||
"server",
|
||||
"src/setupTests.ts"
|
||||
],
|
||||
"types": [
|
||||
|
|
41
yarn.lock
41
yarn.lock
|
@ -475,6 +475,21 @@ bluebird@^3.4.6:
|
|||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||
|
||||
body-parser@^1.17.1:
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.1.tgz#75b3bc98ddd6e7e0d8ffe750dfaca5c66993fa47"
|
||||
dependencies:
|
||||
bytes "2.4.0"
|
||||
content-type "~1.0.2"
|
||||
debug "2.6.1"
|
||||
depd "~1.1.0"
|
||||
http-errors "~1.6.1"
|
||||
iconv-lite "0.4.15"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.4.0"
|
||||
raw-body "~2.2.0"
|
||||
type-is "~1.6.14"
|
||||
|
||||
boolbase@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
|
@ -573,6 +588,10 @@ bytes@2.3.0:
|
|||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070"
|
||||
|
||||
bytes@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
|
||||
|
||||
callsites@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
|
||||
|
@ -1147,18 +1166,12 @@ date-now@^0.1.4:
|
|||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||
|
||||
debug@2.6.1:
|
||||
debug@2.6.1, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@^2.1.0, debug@^2.1.1, debug@^2.2.0:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.3.tgz#0f7eb8c30965ec08c72accfa0130c8b79984141d"
|
||||
dependencies:
|
||||
ms "0.7.2"
|
||||
|
||||
debug@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
|
||||
|
@ -2060,6 +2073,10 @@ iconv-lite@0.4.13, iconv-lite@~0.4.13:
|
|||
version "0.4.13"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
|
||||
|
||||
iconv-lite@0.4.15:
|
||||
version "0.4.15"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
|
||||
|
||||
icss-replace-symbols@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
|
||||
|
@ -3935,6 +3952,14 @@ range-parser@^1.0.3, range-parser@~1.2.0:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||
|
||||
raw-body@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
|
||||
dependencies:
|
||||
bytes "2.4.0"
|
||||
iconv-lite "0.4.15"
|
||||
unpipe "1.0.0"
|
||||
|
||||
rc@^1.0.1, rc@^1.1.6, rc@~1.1.6:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea"
|
||||
|
@ -5039,7 +5064,7 @@ unique-string@^1.0.0:
|
|||
dependencies:
|
||||
crypto-random-string "^1.0.0"
|
||||
|
||||
unpipe@~1.0.0:
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче