CosmosDB plugin and api
This commit is contained in:
Родитель
fbd383ec03
Коммит
7a02e8d031
|
@ -1,6 +1,8 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const request = require('xhr-request');
|
||||
|
||||
const privateSetupPath = path.join(__dirname, '..', 'config', 'setup.private.json');
|
||||
const initialSetupPath = path.join(__dirname, '..', 'config', 'setup.initial.json');
|
||||
|
@ -290,6 +292,76 @@ router.post('/setup', (req, res) => {
|
|||
})
|
||||
});
|
||||
|
||||
router.post('/cosmosdb/:id', (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { privateDashboard } = paths();
|
||||
const dashboardPath = path.join(privateDashboard, id + '.private.js');
|
||||
const fileContents = getFileContents(dashboardPath);
|
||||
if (!isValidFile(dashboardPath)) {
|
||||
return res.send({ error: new Error('Failed to get dashboard') });
|
||||
}
|
||||
|
||||
let docdb = getField(/("?cosmos-db"?:\s*)({.*?})/im, fileContents);
|
||||
if (docdb.startsWith("{") && docdb.endsWith("}")) {
|
||||
docdb = JSON.parse(docdb.replace(/(\s*?{\s*?|\s*?,\s*?)(['"])?([a-zA-Z0-9]+)(['"])?:/g, '$1"$3":'));
|
||||
}
|
||||
if (!docdb.key || !docdb.host) {
|
||||
console.error('Missing CosmosDB host/key config');
|
||||
return this.failure('CosmosDB host/key config required');
|
||||
}
|
||||
|
||||
const date = new Date().toUTCString();
|
||||
const verb = req.body.verb;
|
||||
const resourceType = req.body.resourceType; //'docs';
|
||||
const resourceLink = `dbs/${req.body.databaseId}/colls/${req.body.collectionId}`; //req.body.resourceLink;
|
||||
const masterKey = docdb.key;
|
||||
const auth = getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey);
|
||||
|
||||
let cosmosQuery = {
|
||||
query: req.body.query,
|
||||
parameters: req.body.parameters,
|
||||
};
|
||||
|
||||
const host = docdb.host;
|
||||
const url = `https://${host}.documents.azure.com/${resourceLink}/docs`;
|
||||
|
||||
request(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/query+json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': auth,
|
||||
'x-ms-date': date,
|
||||
'x-ms-version': '2015-04-08',
|
||||
'x-ms-documentdb-isquery': true,
|
||||
},
|
||||
body: cosmosQuery,
|
||||
responseType: 'json',
|
||||
json: true,
|
||||
}, (err, doc) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return this.failure(err);
|
||||
}
|
||||
res.send(doc);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey) {
|
||||
var key = new Buffer(masterKey, "base64");
|
||||
var text = (verb || "").toLowerCase() + "\n" +
|
||||
(resourceType || "").toLowerCase() + "\n" +
|
||||
(resourceLink || "") + "\n" +
|
||||
date.toLowerCase() + "\n" +
|
||||
"" + "\n";
|
||||
var body = new Buffer(text, "utf8");
|
||||
var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
|
||||
var MasterToken = "master";
|
||||
var TokenVersion = "1.0";
|
||||
return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import * as React from 'react';
|
||||
import { IConnection, ConnectionEditor, IConnectionProps } from './Connection';
|
||||
import InfoDrawer from '../../components/common/InfoDrawer';
|
||||
import TextField from 'react-md/lib/TextFields';
|
||||
import Checkbox from 'react-md/lib/SelectionControls/Checkbox';
|
||||
|
||||
export default class CosmosDBConnection implements IConnection {
|
||||
type = 'cosmos-db';
|
||||
params = ['host', 'key'];
|
||||
editor = CosmosDBConnectionEditor;
|
||||
}
|
||||
|
||||
class CosmosDBConnectionEditor extends ConnectionEditor<IConnectionProps, any> {
|
||||
|
||||
constructor(props: IConnectionProps) {
|
||||
super(props);
|
||||
this.onParamChange = this.onParamChange.bind(this);
|
||||
}
|
||||
|
||||
onParamChange(value: string, event: any) {
|
||||
if (typeof this.props.onParamChange === 'function') {
|
||||
this.props.onParamChange('cosmos-db', event.target.id, value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { connection } = this.props;
|
||||
// connection = connection || {'ssl':true };
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ float: 'left', padding: 9 }}>CosmosDB</h2>
|
||||
<InfoDrawer
|
||||
width={300}
|
||||
title="CosmosDB"
|
||||
buttonIcon="help"
|
||||
buttonTooltip="Click here to learn more about CosmosDB"
|
||||
>
|
||||
<div>
|
||||
<a href="https://azure.microsoft.com/en-us/services/cosmos-db/" target="_blank">Create Cosmos DB</a>
|
||||
<hr/>
|
||||
<a href="https://www.documentdb.com/sql/demo" target="_blank">Try CosmosDB demo queries</a>
|
||||
</div>
|
||||
</InfoDrawer>
|
||||
<TextField
|
||||
id="host"
|
||||
label={'Host'}
|
||||
defaultValue={connection['host'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in hostname"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
<TextField
|
||||
id="key"
|
||||
label={'Key'}
|
||||
defaultValue={connection['key'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in Key"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import ApplicationInsightsConnection from './application-insights';
|
||||
import MongoDBConnection from './mongodb';
|
||||
import CosmosDBConnection from './cosmos-db';
|
||||
import { IConnection } from './Connection';
|
||||
|
||||
var connectionTypes = [ ApplicationInsightsConnection, MongoDBConnection];
|
||||
var connectionTypes = [ ApplicationInsightsConnection, CosmosDBConnection];
|
||||
|
||||
var connections: IDict<IConnection> = {};
|
||||
connectionTypes.forEach(connectionType => {
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import { IConnection, ConnectionEditor, IConnectionProps } from './Connection';
|
||||
import InfoDrawer from '../../components/common/InfoDrawer';
|
||||
import TextField from 'react-md/lib/TextFields';
|
||||
import Checkbox from 'react-md/lib/SelectionControls/Checkbox';
|
||||
|
||||
export default class MongoDBConnection implements IConnection {
|
||||
type = 'mongodb';
|
||||
params = ['host', 'port', 'username', 'password', 'ssl'];
|
||||
editor = MongoDBConnectionEditor;
|
||||
}
|
||||
|
||||
|
||||
class MongoDBConnectionEditor extends ConnectionEditor<IConnectionProps, any> {
|
||||
|
||||
constructor(props: IConnectionProps) {
|
||||
super(props);
|
||||
this.onParamChange = this.onParamChange.bind(this);
|
||||
}
|
||||
|
||||
onParamChange(value: string, event: any) {
|
||||
if (typeof this.props.onParamChange === 'function') {
|
||||
this.props.onParamChange('mongodb', event.target.id, value);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { connection } = this.props;
|
||||
connection = connection || {'ssl':true };
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ float: 'left', padding: 9 }}>MongoDB</h2>
|
||||
<InfoDrawer
|
||||
width={300}
|
||||
title='Authentication'
|
||||
buttonIcon='help'
|
||||
buttonTooltip='Click here to learn more about authentications'
|
||||
>
|
||||
<div>
|
||||
I lied. Click <a href='https://docs.mongodb.com/manual/core/authentication/' target='_blank'>here</a> to
|
||||
learn more about MongoDB authentication.
|
||||
<hr/>
|
||||
Sorry.
|
||||
</div>
|
||||
</InfoDrawer>
|
||||
<TextField
|
||||
id="host"
|
||||
label={'Host'}
|
||||
defaultValue={connection['host'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in hostname"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
<TextField
|
||||
id="port"
|
||||
label={'Port'}
|
||||
defaultValue={connection['port'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in MongoDB port"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
<TextField
|
||||
id="username"
|
||||
label={'Username'}
|
||||
defaultValue={connection['username'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in username"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label={'Password'}
|
||||
defaultValue={connection['password'] || ''}
|
||||
lineDirection="center"
|
||||
placeholder="Fill in password"
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
<Checkbox
|
||||
id="ssl"
|
||||
label={'Use SSL?'}
|
||||
defaultChecked={connection['ssl']}
|
||||
className="md-cell md-cell--bottom"
|
||||
onChange={this.onParamChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import * as request from 'xhr-request';
|
||||
import { DataSourcePlugin, IOptions } from '../DataSourcePlugin';
|
||||
import { DataSourceConnector } from '../../DataSourceConnector';
|
||||
import CosmosDBConnection from '../../connections/cosmos-db';
|
||||
|
||||
let connectionType = new CosmosDBConnection();
|
||||
|
||||
interface IQueryParams {
|
||||
query?: ((dependencies: any) => string) | string;
|
||||
parameters?: (string | object)[];
|
||||
mappings?: (string | object)[];
|
||||
databaseId?: string;
|
||||
collectionId?: string;
|
||||
calculated?: (results: any) => object;
|
||||
}
|
||||
|
||||
export default class CosmosDBQuery extends DataSourcePlugin<IQueryParams> {
|
||||
type = 'CosmosDB-Query';
|
||||
defaultProperty = 'doc';
|
||||
connectionType = connectionType.type;
|
||||
|
||||
constructor(options: IOptions<IQueryParams>, connections: IDict<IStringDictionary>) {
|
||||
super(options, connections);
|
||||
this.validateTimespan(this._props);
|
||||
this.validateParams(this._props.params);
|
||||
}
|
||||
|
||||
/**
|
||||
* update - called when dependencies are created
|
||||
* @param {object} dependencies
|
||||
* @param {function} callback
|
||||
*/
|
||||
updateDependencies(dependencies: any) {
|
||||
let emptyDependency = false;
|
||||
Object.keys(this._props.dependencies).forEach((key) => {
|
||||
if (typeof dependencies[key] === 'undefined') { emptyDependency = true; }
|
||||
});
|
||||
|
||||
// If one of the dependencies is not supplied, do not run the query
|
||||
if (emptyDependency) {
|
||||
return (dispatch) => {
|
||||
return dispatch();
|
||||
};
|
||||
}
|
||||
|
||||
// Validate connection
|
||||
let connection = this.getConnection();
|
||||
let { host, key } = connection;
|
||||
if (!connection || !host || !key) {
|
||||
return (dispatch) => {
|
||||
return dispatch();
|
||||
};
|
||||
}
|
||||
|
||||
// Get Dashboard Id
|
||||
const paths = location.pathname.split('/');
|
||||
if (paths.length !== 3) {
|
||||
throw Error('Expected location pathname:' + paths);
|
||||
}
|
||||
const dashboardId = paths[paths.length - 1];
|
||||
|
||||
const params = this._props.params;
|
||||
const query: string = this.compileQuery(params.query, dependencies);
|
||||
|
||||
const url = `/api/cosmosdb/${dashboardId}`;
|
||||
const body = {
|
||||
verb: 'POST',
|
||||
databaseId: params.databaseId,
|
||||
collectionId: params.collectionId,
|
||||
resourceType: 'docs',
|
||||
query: query,
|
||||
parameters: params.parameters
|
||||
};
|
||||
|
||||
return (dispatch) => {
|
||||
request(url, {
|
||||
method: 'POST',
|
||||
json: true,
|
||||
body: body,
|
||||
}, (error, json) => {
|
||||
if (error || !json.Documents) {
|
||||
return this.failure(error);
|
||||
}
|
||||
return dispatch(json.Documents);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
updateSelectedValues(dependencies: IDictionary, selectedValues: any) {
|
||||
if (Array.isArray(selectedValues)) {
|
||||
return Object.assign(dependencies, { 'selectedValues': selectedValues });
|
||||
} else {
|
||||
return Object.assign(dependencies, { ... selectedValues });
|
||||
}
|
||||
}
|
||||
|
||||
private compileQuery(query: any, dependencies: any): string {
|
||||
return typeof query === 'function' ? query(dependencies) : query;
|
||||
}
|
||||
|
||||
private validateTimespan(props: any) {
|
||||
}
|
||||
|
||||
private validateParams(params: IQueryParams): void {
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче