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

Ibex version 1.0
This commit is contained in:
Mor Shemesh 2017-07-20 15:38:54 +03:00 коммит произвёл GitHub
Родитель 36cf41a6b1 ac96e6977e
Коммит 1f09310236
9 изменённых файлов: 240 добавлений и 58 удалений

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

@ -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.d2afa0dc.js",
"main.js.map": "static/js/main.d2afa0dc.js.map"
"main.js": "static/js/main.7f2eae21.js",
"main.js.map": "static/js/main.7f2eae21.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.d2afa0dc.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.7f2eae21.js"></script></body></html>

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@ -11,6 +11,7 @@ interface IConfigurationsActions {
submitDashboardFile(content: string, fileName: string): void;
convertDashboardToString(dashboard: IDashboardConfig): string;
deleteDashboard(id: string): any;
saveAsTemplate(template: IDashboardConfig): any;
}
class ConfigurationsActions extends AbstractActions implements IConfigurationsActions {
@ -18,15 +19,15 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
super(alt);
}
submitDashboardFile = (content, dashboardId) => {
submitDashboardFile(content: string, dashboardId: string) {
return (dispatcher: (json: any) => void) => {
// Replace both 'id' and 'url' with the requested id from the user
var idRegExPattern = /id: \".*\",/i;
var urlRegExPatternt = /url: \".*\",/i;
var updatedContent =
const idRegExPattern = /id: \".*\",/i;
const urlRegExPatternt = /url: \".*\",/i;
const updatedContent =
content.replace(idRegExPattern, 'id: \"' + dashboardId + '\",')
.replace(urlRegExPatternt, 'url: \"' + dashboardId + '\",');
.replace(urlRegExPatternt, 'url: \"' + dashboardId + '\",');
request(
'/api/dashboards/' + dashboardId,
@ -39,6 +40,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
if (error || (json && json.errors)) {
return this.failure(error || json.errors);
}
// redirect to the newly imported dashboard
window.location.replace('dashboard/' + dashboardId);
return dispatcher(json);
@ -117,6 +119,33 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
};
}
saveAsTemplate(template: IDashboardConfig) {
return (dispatcher: (result: { template: IDashboardConfig }) => void) => {
let script = this.objectToString(template);
script = '/// <reference path="../../../client/@types/types.d.ts"/>\n' +
'import * as _ from \'lodash\';\n\n' +
'export const config: IDashboardConfig = /*return*/ ' + script;
return request(
'/api/templates/' + template.id,
{
method: 'PUT',
json: true,
body: { script: script }
},
(error: any, json: any) => {
if (error || (json && json.errors)) {
return this.failure(error || json.errors);
}
return dispatcher(json);
}
);
};
}
saveConfiguration(dashboard: IDashboardConfig) {
return (dispatcher: (dashboard: IDashboardConfig) => void) => {
@ -299,7 +328,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
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 */
global['eval']('obj[i] = ' + parsedString[i] );
} else {
obj[i] = parsedString[i];
@ -321,7 +350,7 @@ class ConfigurationsActions extends AbstractActions implements IConfigurationsAc
}
calculated = calculated.substr('function(){return'.length, calculated.length - 'function(){return'.length - 1);
eval('dataSource.calculated = ' + calculated); /* tslint:disable-line */
global['eval']('dataSource.calculated = ' + calculated);
}
});
}

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

@ -32,6 +32,7 @@ import FontIcon from 'react-md/lib/FontIcons';
import Avatar from 'react-md/lib/Avatars';
import Subheader from 'react-md/lib/Subheaders';
import Divider from 'react-md/lib/Dividers';
import TextField from 'react-md/lib/TextFields';
interface IDashboardProps {
dashboard?: IDashboardConfig;
@ -41,6 +42,7 @@ interface IDashboardState {
editMode?: boolean;
askDelete?: boolean;
askDownload?: boolean;
askSaveAsTemplate?: boolean;
downloadFiles?: IDownloadFile[];
downloadFormat?: string;
mounted?: boolean;
@ -51,15 +53,18 @@ interface IDashboardState {
visibilityFlags?: IDict<boolean>;
infoVisible?: boolean;
infoHtml?: string;
newTemplateName?: string;
newTemplateDescription?: string;
}
export default class Dashboard extends React.Component<IDashboardProps, IDashboardState> {
layouts = {};
state = {
editMode: false,
askDelete: false,
askSaveAsTemplate: false,
askDownload: false,
downloadFiles: [],
downloadFormat: 'json',
@ -71,6 +76,8 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
visibilityFlags: {},
infoVisible: false,
infoHtml: '',
newTemplateName: '',
newTemplateDescription: ''
};
constructor(props: IDashboardProps) {
@ -91,10 +98,17 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
this.onClickDownloadFile = this.onClickDownloadFile.bind(this);
this.onChangeDownloadFormat = this.onChangeDownloadFormat.bind(this);
this.onDownloadDashboard = this.onDownloadDashboard.bind(this);
this.onSaveAsTemplate = this.onSaveAsTemplate.bind(this);
this.newTemplateNameChange = this.newTemplateNameChange.bind(this);
this.onSaveAsTemplateApprove = this.onSaveAsTemplateApprove.bind(this);
this.onSaveAsTemplateCancel = this.onSaveAsTemplateCancel.bind(this);
this.newTemplateDescriptionChange = this.newTemplateDescriptionChange.bind(this);
VisibilityStore.listen(state => {
this.setState({ visibilityFlags: state.flags });
});
this.state.newTemplateName = this.props.dashboard.name;
this.state.newTemplateDescription = this.props.dashboard.description;
}
componentDidMount() {
@ -176,6 +190,34 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
this.setState({ askDelete: true });
}
onSaveAsTemplate() {
this.setState({ askSaveAsTemplate: true });
}
onSaveAsTemplateApprove() {
let { dashboard } = this.props;
var template = _.cloneDeep(dashboard);
template.name = this.state.newTemplateName;
template.description = this.state.newTemplateDescription;
template.category = 'Custom Templates';
template.id = template.url = dashboard.id + (Math.floor(Math.random() * 1000) + 1); // generate random id
ConfigurationsActions.saveAsTemplate(template);
window.location.href = '/';
this.setState({ askSaveAsTemplate: false });
}
onSaveAsTemplateCancel() {
this.setState({ askSaveAsTemplate: false });
}
newTemplateNameChange(value: string, e: any) {
this.setState({ newTemplateName: value });
}
newTemplateDescriptionChange(value: string, e: any) {
this.setState({ newTemplateDescription: value });
}
onDeleteDashboardApprove() {
let { dashboard } = this.props;
if (!dashboard) {
@ -252,7 +294,10 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
askDownload,
downloadFiles,
downloadFormat,
askConfig
askConfig ,
askSaveAsTemplate,
newTemplateName,
newTemplateDescription
} = this.state;
const { infoVisible, infoHtml } = this.state;
const layout = this.state.layouts[currentBreakpoint];
@ -317,6 +362,12 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
<span>
<Button key="delete" icon tooltipLabel="Delete dashboard" onClick={this.onDeleteDashboard}>delete</Button>
</span>
),
(
<span>
<Button key="saveAsTemplate" icon tooltipLabel="Save as template"
onClick={this.onSaveAsTemplate}>cloud_download</Button>
</span>
)
);
toolbarActions.reverse();
@ -451,6 +502,38 @@ export default class Dashboard extends React.Component<IDashboardProps, IDashboa
Are you sure you want to permanently delete this dashboard?
</p>
</Dialog>
<Dialog
dialogStyle={{ width: '50%' }}
id="saveAsTemplateDialog"
visible={askSaveAsTemplate}
title="Save this dashoard as a custom template"
modal
actions={[
{ onClick: this.onSaveAsTemplateApprove, primary: false, label: 'Save as custom template', },
{ onClick: this.onSaveAsTemplateCancel, primary: true, label: 'Cancel' }
]}
>
<p>You can save this dashboard as a custom template for a future reuse</p>
<TextField
id="templateName"
label="New Template Name"
placeholder="Template Name"
className="md-cell md-cell--bottom"
value={newTemplateName}
onChange={this.newTemplateNameChange}
required
/>
<TextField
id="templateDescription"
label="New Template Description"
placeholder="Template Description"
className="md-cell md-cell--bottom"
value={newTemplateDescription}
onChange={this.newTemplateDescriptionChange}
required
/>
</Dialog>
</div>
);
}

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

@ -272,6 +272,7 @@ export default class Home extends React.Component<any, IHomeState> {
<div>
<div style={{ textAlign: 'right' }}>
<Button
flat
tooltipLabel="Import dashboard"
onClick={this.onOpenImport.bind(this)}
label="Import dashboard"
@ -279,7 +280,7 @@ export default class Home extends React.Component<any, IHomeState> {
</Button>
<Dialog
id="ImportDashboard"
visible={importVisible}
visible={importVisible || false}
title="Import dashboard"
modal
actions={[
@ -297,7 +298,7 @@ export default class Home extends React.Component<any, IHomeState> {
<TextField
id="dashboardFileName"
label="Dashboard ID"
value={fileName}
value={fileName || ''}
onChange={this.updateFileName}
disabled={!importedFileContent}
lineDirection="center"
@ -307,10 +308,10 @@ export default class Home extends React.Component<any, IHomeState> {
</div>
{
Object.keys(categories).map(category => {
Object.keys(categories).map((category, index) => {
if (!categories[category].length) { return null; }
return (
<div>
<div key={index}>
<h1>{category}</h1>
<div className="md-grid">
{categories[category]}
@ -323,7 +324,7 @@ export default class Home extends React.Component<any, IHomeState> {
<Dialog
id="templateInfoDialog"
title={infoTitle}
visible={infoVisible}
visible={infoVisible || false}
onHide={this.onCloseInfo}
dialogStyle={{ width: '80%' }}
contentStyle={{ padding: '0', maxHeight: 'calc(100vh - 148px)' }}

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

@ -105,7 +105,10 @@ export default class MapData extends GenericComponent<IMapDataProps, IMapDataSta
promises.push(promise);
promise.then(results => {
let markupPopup = (popup && L.popup().setContent(popup)) || null;
markers.push({ lat: results[0].y, lng: results[0].x, popup: markupPopup });
if (results.length) {
markers.push({ lat: results[0].y, lng: results[0].x, popup: markupPopup });
}
});
});

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

@ -42,7 +42,8 @@ const getMetadata = (text) => {
const paths = () => ({
privateDashboard: path.join(__dirname, '..', 'dashboards'),
preconfDashboard: path.join(__dirname, '..', 'dashboards', 'preconfigured')
preconfDashboard: path.join(__dirname, '..', 'dashboards', 'preconfigured'),
privateTemplate: path.join(__dirname, '..', 'dashboards', 'customTemplates')
});
const isValidFile = (filePath) => {
@ -57,10 +58,18 @@ const getFileContents = (filePath) => {
: contents;
}
const ensureCustomTemplatesFolderExists = () => {
const { privateTemplate } = paths();
if (!fs.existsSync(privateTemplate)) {
fs.mkdirSync(privateTemplate);
}
}
router.get('/dashboards', (req, res) => {
const { privateDashboard, preconfDashboard } = paths();
const { privateDashboard, preconfDashboard, privateTemplate } = paths();
let script = '';
let files = fs.readdirSync(privateDashboard);
if (files && files.length) {
@ -85,6 +94,7 @@ router.get('/dashboards', (req, res) => {
});
}
// read preconfigured templates and custom templates
let templates = fs.readdirSync(preconfDashboard);
if (templates && templates.length) {
templates.forEach((fileName) => {
@ -107,6 +117,30 @@ router.get('/dashboards', (req, res) => {
}
});
}
ensureCustomTemplatesFolderExists();
let customTemplates = fs.readdirSync(privateTemplate);
if (customTemplates && customTemplates.length) {
customTemplates.forEach((fileName) => {
let filePath = path.join(privateTemplate, fileName);
if (isValidFile(filePath)) {
const fileContents = getFileContents(filePath);
const jsonDefinition = getMetadata(fileContents);
let content = 'return ' + JSON.stringify(jsonDefinition);
// Ensuing this dashboard is loaded into the dashboards array on the page
script += `
(function (window) {
var dashboardTemplate = (function () {
${content}
})();
window.dashboardTemplates = window.dashboardTemplates || [];
window.dashboardTemplates.push(dashboardTemplate);
})(window);
`;
}
});
}
res.send(script);
});
@ -163,13 +197,19 @@ router.post('/dashboards/:id', (req, res) => {
router.get('/templates/:id', (req, res) => {
let templateId = req.params.id;
let preconfDashboard = path.join(__dirname, '..', 'dashboards', 'preconfigured');
let templatePath = path.join(__dirname, '..', 'dashboards', 'preconfigured');
let script = '';
let dashboardFile = getFileById(preconfDashboard, templateId);
if (dashboardFile) {
let filePath = path.join(preconfDashboard, dashboardFile);
let templateFile = getFileById(templatePath, templateId);
if (!templateFile) {
//fallback to custom template
templatePath = path.join(__dirname, '..', 'dashboards', 'customTemplates');
templateFile = getFileById(templatePath, templateId);
}
if (templateFile) {
let filePath = path.join(templatePath, templateFile);
if (isValidFile(filePath)) {
const content = getFileContents(filePath);
@ -188,6 +228,32 @@ router.get('/templates/:id', (req, res) => {
res.send(script);
});
router.put('/templates/:id', (req, res) => {
let { id } = req.params;
let { script } = req.body || '';
const { privateTemplate } = paths();
ensureCustomTemplatesFolderExists();
let templatePath = path.join(privateTemplate, id + '.private.ts');
let templateFile = getFileById(privateTemplate, id);
let exists = fs.existsSync(templatePath);
if (templateFile || exists) {
return res.json({ errors: ['Dashboard id or filename already exists'] });
}
fs.writeFile(templatePath, script, err => {
if (err) {
console.error(err);
return res.end(err);
}
res.json({ script });
});
});
router.put('/dashboards/:id', (req, res) => {
let { id } = req.params;
let { script } = req.body || '';