Merge pull request #247 from CatalystCode/ibex-version-1.0
Ibex version 1.0
This commit is contained in:
Коммит
a2222b503a
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"main.css": "static/css/main.43e63d1e.css",
|
||||
"main.css.map": "static/css/main.43e63d1e.css.map",
|
||||
"main.js": "static/js/main.7f2eae21.js",
|
||||
"main.js.map": "static/js/main.7f2eae21.js.map"
|
||||
"main.js": "static/js/main.203f58e7.js",
|
||||
"main.js.map": "static/js/main.203f58e7.js.map"
|
||||
}
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"/><title>React App</title><link href="/static/css/main.43e63d1e.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.7f2eae21.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"/><title>React App</title><link href="/static/css/main.43e63d1e.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.203f58e7.js"></script></body></html>
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,5 +1,6 @@
|
|||
import alt, { AbstractActions } from '../alt';
|
||||
import * as request from 'xhr-request';
|
||||
import utils from '../utils';
|
||||
|
||||
interface IConfigurationsActions {
|
||||
loadConfiguration(): any;
|
||||
|
@ -85,7 +86,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
createDashboard(dashboard: IDashboardConfig) {
|
||||
return (dispatcher: (dashboard: IDashboardConfig) => void) => {
|
||||
|
||||
let script = this.objectToString(dashboard);
|
||||
let script = utils.objectToString(dashboard);
|
||||
request('/api/dashboards/' + dashboard.id, {
|
||||
method: 'PUT',
|
||||
json: true,
|
||||
|
@ -122,7 +123,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
saveAsTemplate(template: IDashboardConfig) {
|
||||
|
||||
return (dispatcher: (result: { template: IDashboardConfig }) => void) => {
|
||||
let script = this.objectToString(template);
|
||||
let script = utils.objectToString(template);
|
||||
|
||||
script = '/// <reference path="../../../client/@types/types.d.ts"/>\n' +
|
||||
'import * as _ from \'lodash\';\n\n' +
|
||||
|
@ -149,7 +150,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
saveConfiguration(dashboard: IDashboardConfig) {
|
||||
return (dispatcher: (dashboard: IDashboardConfig) => void) => {
|
||||
|
||||
let stringDashboard = this.objectToString(dashboard);
|
||||
let stringDashboard = utils.objectToString(dashboard);
|
||||
|
||||
request('/api/dashboards/' + dashboard.id, {
|
||||
method: 'POST',
|
||||
|
@ -171,10 +172,6 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
};
|
||||
}
|
||||
|
||||
convertDashboardToString(dashboard: IDashboardConfig) {
|
||||
return this.objectToString(dashboard);
|
||||
}
|
||||
|
||||
failure(error: any) {
|
||||
return { error };
|
||||
}
|
||||
|
@ -196,6 +193,10 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
};
|
||||
}
|
||||
|
||||
convertDashboardToString(dashboard: IDashboardConfig) {
|
||||
return utils.convertDashboardToString(dashboard);
|
||||
}
|
||||
|
||||
private getScript(source: string, callback?: () => void): boolean {
|
||||
let script: any = document.createElement('script');
|
||||
let prior = document.getElementsByTagName('script')[0];
|
||||
|
@ -219,141 +220,6 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
|
|||
script.src = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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': {
|
||||
|
||||
if (obj === null) { return result = 'null'; }
|
||||
|
||||
// Iterating through all values in object
|
||||
let objectValue = '';
|
||||
let objectValues = [];
|
||||
let valuesStringLength = 0;
|
||||
Object.keys(obj).forEach((key: string, idx: number) => {
|
||||
|
||||
let value = this.objectToString(obj[key], indent + 1, true);
|
||||
|
||||
// if key contains '.' or '-'
|
||||
let skey = key.search(/\.|\-/g) >= 0 ? `"${key}"` : `${key}`;
|
||||
let mapping = `${skey}: ${value}`;
|
||||
valuesStringLength += mapping.length;
|
||||
|
||||
objectValues.push(mapping);
|
||||
});
|
||||
|
||||
if (valuesStringLength <= 120) {
|
||||
result += `{ ${objectValues.join()} }`;
|
||||
} else {
|
||||
result += `{\n${sind}\t${objectValues.join(',\n' + sind + '\t')}\n${sind}}`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'string':
|
||||
let stringValue = obj.toString();
|
||||
let htmlString = stringValue.replace(/^\s+|\s+$/g, ''); // trim any leading and trailing whitespace
|
||||
if ( htmlString.startsWith('<') && htmlString.endsWith('>') ) {
|
||||
result += '`' + htmlString + '`'; // html needs to be wrapped in back ticks
|
||||
} else {
|
||||
stringValue = stringValue.replace(/\"/g, '\\"');
|
||||
result += `"${stringValue}"`;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'function': {
|
||||
result += obj.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'number':
|
||||
case 'boolean': {
|
||||
result += `${obj}`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
let arrayStringLength = 0;
|
||||
let mappedValues = (obj as any[]).map(value => {
|
||||
let res = this.objectToString(value, indent + 1, true);
|
||||
arrayStringLength += res.length;
|
||||
return res;
|
||||
});
|
||||
|
||||
if (arrayStringLength <= 120) {
|
||||
result += `[${mappedValues.join()}]`;
|
||||
} else {
|
||||
result += `[\n${sind}\t${mappedValues.join(',\n' + sind + '\t')}\n${sind}]`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'undefined': {
|
||||
result += `undefined`;
|
||||
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') {
|
||||
global['eval']('obj[i] = ' + parsedString[i] );
|
||||
|
||||
} 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);
|
||||
global['eval']('dataSource.calculated = ' + calculated);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const configurationsActions = alt.createActions<IConfigurationsActions>(ConfigurationsActions);
|
||||
|
|
|
@ -201,6 +201,10 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
|
|||
template.description = this.state.newTemplateDescription;
|
||||
template.category = 'Custom Templates';
|
||||
template.id = template.url = dashboard.id + (Math.floor(Math.random() * 1000) + 1); // generate random id
|
||||
|
||||
// Removing connections so private info will not be included
|
||||
template.config.connections = {};
|
||||
|
||||
ConfigurationsActions.saveAsTemplate(template);
|
||||
window.location.href = '/';
|
||||
this.setState({ askSaveAsTemplate: false });
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import Toolbar from 'react-md/lib/Toolbars';
|
||||
import Button from 'react-md/lib/Buttons/Button';
|
||||
import CircularProgress from 'react-md/lib/Progress/CircularProgress';
|
||||
import { Card, CardTitle, CardActions, CardText } from 'react-md/lib/Cards';
|
||||
|
@ -13,9 +14,13 @@ import SetupStore from '../../stores/SetupStore';
|
|||
|
||||
import ConfigurationStore from '../../stores/ConfigurationsStore';
|
||||
import ConfigurationsActions from '../../actions/ConfigurationsActions';
|
||||
import utils from '../../utils';
|
||||
|
||||
import IDownloadFile, { exportDataSources, createDownloadFiles, downloadBlob } from '../Dashboard/DownloadFile';
|
||||
|
||||
const renderHTML = require('react-render-html');
|
||||
|
||||
const MultipleSpacesRegex = / +/g;
|
||||
const styles = {
|
||||
card: {
|
||||
minWidth: 400,
|
||||
|
@ -97,6 +102,8 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
this.onLoad = this.onLoad.bind(this);
|
||||
this.setFile = this.setFile.bind(this);
|
||||
this.updateFileName = this.updateFileName.bind(this);
|
||||
this.onExportTemplate = this.onExportTemplate.bind(this);
|
||||
this.downloadTemplate = this.downloadTemplate.bind(this);
|
||||
}
|
||||
|
||||
updateConfiguration(state: {templates: IDashboardConfig[], template: IDashboardConfig, creationState: string}) {
|
||||
|
@ -105,6 +112,9 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
template: state.template,
|
||||
creationState: state.creationState
|
||||
});
|
||||
if (this.state.stage === 'requestDownloadTemplate') {
|
||||
this.downloadTemplate(this.state.template);
|
||||
}
|
||||
}
|
||||
|
||||
updateSetup(state: IHomeState) {
|
||||
|
@ -211,6 +221,19 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
this.setState({ importedFileContent });
|
||||
}
|
||||
|
||||
onExportTemplate(templateId: string) {
|
||||
this.setState({ stage: 'requestDownloadTemplate' });
|
||||
ConfigurationsActions.loadTemplate(templateId);
|
||||
}
|
||||
|
||||
downloadTemplate(template: IDashboardConfig) {
|
||||
template.layouts = template.layouts || {};
|
||||
let stringDashboard = utils.convertDashboardToString(template);
|
||||
var dashboardName = template.id.replace(MultipleSpacesRegex, ' ');
|
||||
dashboardName = template.id.replace(MultipleSpacesRegex, '_');
|
||||
downloadBlob('return ' + stringDashboard, 'application/json', dashboardName + '.private.ts');
|
||||
}
|
||||
|
||||
render() {
|
||||
let { loaded, redirectUrl, templates, selectedTemplateId, template } = this.state;
|
||||
let { importVisible } = this.state;
|
||||
|
@ -241,6 +264,14 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
</MediaOverlay>
|
||||
</Media>
|
||||
<CardActions style={styles.fabs}>
|
||||
<Button
|
||||
floating
|
||||
secondary
|
||||
style={{ backgroundColor: '#959ba5', marginRight: '2px' }}
|
||||
onClick={this.onExportTemplate.bind(this, tmpl.id)}
|
||||
>
|
||||
file_download
|
||||
</Button>
|
||||
<Button
|
||||
floating
|
||||
secondary
|
||||
|
@ -268,17 +299,37 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
categories[category].push(createCard(tmpl, index));
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<Button
|
||||
let toolbarActions = [];
|
||||
toolbarActions.push(
|
||||
(
|
||||
<Button
|
||||
flat
|
||||
tooltipLabel="Import dashboard"
|
||||
onClick={this.onOpenImport.bind(this)}
|
||||
label="Import dashboard"
|
||||
>file_upload
|
||||
</Button>
|
||||
<Dialog
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="md-cell md-cell--12">
|
||||
<Toolbar actions={toolbarActions} />
|
||||
{
|
||||
Object.keys(categories).map((category, index) => {
|
||||
if (!categories[category].length) { return null; }
|
||||
return (
|
||||
<div key={index}>
|
||||
<h1>{category}</h1>
|
||||
<div className="md-grid">
|
||||
{categories[category]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
<Dialog
|
||||
id="ImportDashboard"
|
||||
visible={importVisible || false}
|
||||
title="Import dashboard"
|
||||
|
@ -304,22 +355,7 @@ export default class Home extends React.Component<any, IHomeState> {
|
|||
lineDirection="center"
|
||||
placeholder="Choose an ID for the imported dashboard"
|
||||
/>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{
|
||||
Object.keys(categories).map((category, index) => {
|
||||
if (!categories[category].length) { return null; }
|
||||
return (
|
||||
<div key={index}>
|
||||
<h1>{category}</h1>
|
||||
<div className="md-grid">
|
||||
{categories[category]}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<Dialog
|
||||
id="templateInfoDialog"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as moment from 'moment';
|
||||
|
||||
export default {
|
||||
kmNumber: (num: number): string => {
|
||||
export default class Utils {
|
||||
static kmNumber(num: number): string {
|
||||
if (isNaN(num)) { return ''; }
|
||||
|
||||
return (
|
||||
|
@ -11,9 +11,148 @@ export default {
|
|||
(num / 1000).toFixed(1) + 'K' :
|
||||
(num % 1 * 10) !== 0 ?
|
||||
num.toFixed(1).toString() : num.toString());
|
||||
},
|
||||
}
|
||||
|
||||
ago: (date: Date): string => {
|
||||
static ago(date: Date): string {
|
||||
return moment(date).fromNow();
|
||||
}
|
||||
|
||||
static convertDashboardToString(dashboard: IDashboardConfig) {
|
||||
return Utils.objectToString(dashboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convret a json object with functions to string
|
||||
* @param obj an object with functions to convert to string
|
||||
*/
|
||||
static 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': {
|
||||
|
||||
if (obj === null) { return result = 'null'; }
|
||||
|
||||
// Iterating through all values in object
|
||||
let objectValue = '';
|
||||
let objectValues = [];
|
||||
let valuesStringLength = 0;
|
||||
Object.keys(obj).forEach((key: string, idx: number) => {
|
||||
|
||||
let value = Utils.objectToString(obj[key], indent + 1, true);
|
||||
|
||||
// if key contains '.' or '-'
|
||||
let skey = key.search(/\.|\-/g) >= 0 ? `"${key}"` : `${key}`;
|
||||
let mapping = `${skey}: ${value}`;
|
||||
valuesStringLength += mapping.length;
|
||||
|
||||
objectValues.push(mapping);
|
||||
});
|
||||
|
||||
if (valuesStringLength <= 120) {
|
||||
result += `{ ${objectValues.join()} }`;
|
||||
} else {
|
||||
result += `{\n${sind}\t${objectValues.join(',\n' + sind + '\t')}\n${sind}}`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'string':
|
||||
let stringValue = obj.toString();
|
||||
let htmlString = stringValue.replace(/^\s+|\s+$/g, ''); // trim any leading and trailing whitespace
|
||||
if ( htmlString.startsWith('<') && htmlString.endsWith('>') ) {
|
||||
result += '`' + htmlString + '`'; // html needs to be wrapped in back ticks
|
||||
} else {
|
||||
stringValue = stringValue.replace(/\"/g, '\\"');
|
||||
result += `"${stringValue}"`;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'function': {
|
||||
result += obj.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'number':
|
||||
case 'boolean': {
|
||||
result += `${obj}`;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
let arrayStringLength = 0;
|
||||
let mappedValues = (obj as any[]).map(value => {
|
||||
let res = Utils.objectToString(value, indent + 1, true);
|
||||
arrayStringLength += res.length;
|
||||
return res;
|
||||
});
|
||||
|
||||
if (arrayStringLength <= 120) {
|
||||
result += `[${mappedValues.join()}]`;
|
||||
} else {
|
||||
result += `[\n${sind}\t${mappedValues.join(',\n' + sind + '\t')}\n${sind}]`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'undefined': {
|
||||
result += `undefined`;
|
||||
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
|
||||
*/
|
||||
static 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') {
|
||||
global['eval']('obj[i] = ' + parsedString[i] );
|
||||
|
||||
} else {
|
||||
obj[i] = parsedString[i];
|
||||
}
|
||||
|
||||
} else if (typeof parsedString[i] === 'object') {
|
||||
obj[i] = Utils.stringToObject(parsedString[i]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static 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);
|
||||
global['eval']('dataSource.calculated = ' + calculated);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -80,7 +80,7 @@ router.get('/dashboards', (req, res) => {
|
|||
const jsonDefinition = getMetadata(fileContents);
|
||||
let content = 'return ' + JSON.stringify(jsonDefinition);
|
||||
|
||||
// Ensuing this dashboard is loaded into the dashboards array on the page
|
||||
// Ensuring this dashboard is loaded into the dashboards array on the page
|
||||
script += `
|
||||
(function (window) {
|
||||
var dashboard = (function () {
|
||||
|
@ -229,8 +229,12 @@ router.get('/templates/:id', (req, res) => {
|
|||
});
|
||||
|
||||
router.put('/templates/:id', (req, res) => {
|
||||
let { id } = req.params;
|
||||
let { script } = req.body || '';
|
||||
let { id } = req.params || {};
|
||||
let { script } = req.body || {};
|
||||
|
||||
if (!id || !script) {
|
||||
return res.end({ error: 'No id or scripts were supplied for saving the template' });
|
||||
}
|
||||
|
||||
const { privateTemplate } = paths();
|
||||
|
||||
|
@ -255,8 +259,12 @@ router.put('/templates/:id', (req, res) => {
|
|||
});
|
||||
|
||||
router.put('/dashboards/:id', (req, res) => {
|
||||
let { id } = req.params;
|
||||
let { script } = req.body || '';
|
||||
let { id } = req.params || {};
|
||||
let { script } = req.body || {};
|
||||
|
||||
if (!id || !script) {
|
||||
return res.end({ error: 'No id or script were supplied for the new dashboard' });
|
||||
}
|
||||
|
||||
const { privateDashboard } = paths();
|
||||
let dashboardPath = path.join(privateDashboard, id + '.private.js');
|
||||
|
|
Загрузка…
Ссылка в новой задаче