Merge pull request #247 from CatalystCode/ibex-version-1.0

Ibex version 1.0
This commit is contained in:
Mor Shemesh 2017-07-30 10:08:49 +03:00 коммит произвёл GitHub
Родитель 1f09310236 02ba6f83d2
Коммит a2222b503a
9 изменённых файлов: 304 добавлений и 251 удалений

Просмотреть файл

@ -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');