in with the new
This commit is contained in:
38
.babelrc
Normal file
38
.babelrc
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"presets": [
|
||||
"es2015",
|
||||
"react",
|
||||
"stage-0"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"module-alias",
|
||||
[
|
||||
{
|
||||
"expose": "components",
|
||||
"src": "src/core/components"
|
||||
},
|
||||
{
|
||||
"expose": "core",
|
||||
"src": "src/core"
|
||||
},
|
||||
{
|
||||
"expose": "img",
|
||||
"src": "src/img"
|
||||
},
|
||||
{
|
||||
"expose": "corePlugins",
|
||||
"src": "src/core/plugins"
|
||||
},
|
||||
{
|
||||
"expose": "less",
|
||||
"src": "src/less"
|
||||
},
|
||||
{
|
||||
"expose": "base",
|
||||
"src": "npm:getbase/src/less/base"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.idea
|
||||
.deps_check
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
11
LICENSE
Normal file
11
LICENSE
Normal file
@@ -0,0 +1,11 @@
|
||||
Copyright 2017 SmartBear Software
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
111
README.md
Normal file
111
README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Swagger UI
|
||||
|
||||
[](http://badge.fury.io/js/swagger-ui)
|
||||
|
||||
## New!
|
||||
|
||||
This is the new version of swagger-ui, 3.x.
|
||||
|
||||
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
|
||||
|
||||
## Compatibility
|
||||
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
|
||||
|
||||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status
|
||||
------------------ | ------------ | -------------------------- | ----- | ------
|
||||
3.0.0 | 2017-03-17 | 2.0 | [tag v3.0.0](https://github.com/swagger-api/swagger-ui/tree/v3.0.0) |
|
||||
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) |
|
||||
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) |
|
||||
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) |
|
||||
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) |
|
||||
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) |
|
||||
|
||||
|
||||
### How to run
|
||||
|
||||
##### Prerequisites
|
||||
- Node 6.x
|
||||
- NPM 3.x
|
||||
|
||||
If you just want to see your specs, open `public/index.html` in your browser directly from your filesystem.
|
||||
|
||||
If you'd like to make modifications to the codebase, run the dev server with: `npm run dev`.
|
||||
|
||||
|
||||
##### Browser support
|
||||
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11.
|
||||
|
||||
## CORS Support
|
||||
|
||||
CORS is a technique to prevent websites from doing bad things with your personal data. Most browsers + JavaScript toolkits not only support CORS but enforce it, which has implications for your API server which supports Swagger.
|
||||
|
||||
You can read about CORS here: http://www.w3.org/TR/cors.
|
||||
|
||||
There are two cases where no action is needed for CORS support:
|
||||
|
||||
1. swagger-ui is hosted on the same server as the application itself (same host *and* port).
|
||||
2. The application is located behind a proxy that enables the required CORS headers. This may already be covered within your organization.
|
||||
|
||||
Otherwise, CORS support needs to be enabled for:
|
||||
|
||||
1. Your Swagger docs. For Swagger 2.0 it's the `swagger.json`/`swagger.yaml` and any externally `$ref`ed docs.
|
||||
2. For the `Try it now` button to work, CORS needs to be enabled on your API endpoints as well.
|
||||
|
||||
### Testing CORS Support
|
||||
|
||||
You can verify CORS support with one of three techniques:
|
||||
|
||||
- Curl your API and inspect the headers. For instance:
|
||||
|
||||
```bash
|
||||
$ curl -I "http://petstore.swagger.io/v2/swagger.json"
|
||||
HTTP/1.1 200 OK
|
||||
Date: Sat, 31 Jan 2015 23:05:44 GMT
|
||||
Access-Control-Allow-Origin: *
|
||||
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS
|
||||
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
|
||||
Content-Type: application/json
|
||||
Content-Length: 0
|
||||
```
|
||||
|
||||
This tells us that the petstore resource listing supports OPTIONS, and the following headers: `Content-Type`, `api_key`, `Authorization`.
|
||||
|
||||
- Try swagger-ui from your file system and look at the debug console. If CORS is not enabled, you'll see something like this:
|
||||
|
||||
```
|
||||
XMLHttpRequest cannot load http://sad.server.com/v2/api-docs. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
|
||||
```
|
||||
|
||||
Swagger-UI cannot easily show this error state.
|
||||
|
||||
- Using the http://www.test-cors.org website. Keep in mind this will show a successful result even if `Access-Control-Allow-Headers` is not available, which is still required for Swagger-UI to function properly.
|
||||
|
||||
### Enabling CORS
|
||||
|
||||
The method of enabling CORS depends on the server and/or framework you use to host your application. http://enable-cors.org provides information on how to enable CORS in some common web servers.
|
||||
|
||||
Other servers/frameworks may provide you information on how to enable it specifically in their use case.
|
||||
|
||||
### CORS and Header Parameters
|
||||
|
||||
Swagger lets you easily send headers as parameters to requests. The name of these headers *MUST* be supported in your CORS configuration as well. From our example above:
|
||||
|
||||
```
|
||||
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
|
||||
```
|
||||
|
||||
Only headers with these names will be allowed to be sent by Swagger-UI.
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2017 SmartBear Software
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
28
build-tools/loadersByExtension.js
Normal file
28
build-tools/loadersByExtension.js
Normal file
@@ -0,0 +1,28 @@
|
||||
function extsToRegExp(exts) {
|
||||
return new RegExp("\\.(" + exts.map(function(ext) {
|
||||
return ext.replace(/\./g, "\\.");
|
||||
}).join("|") + ")(\\?.*)?$");
|
||||
}
|
||||
|
||||
module.exports = function loadersByExtension(obj) {
|
||||
var loaders = [];
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
var exts = key.split("|");
|
||||
var value = obj[key];
|
||||
var entry = {
|
||||
extensions: exts,
|
||||
test: extsToRegExp(exts)
|
||||
};
|
||||
if(Array.isArray(value)) {
|
||||
entry.loaders = value;
|
||||
} else if(typeof value === "string") {
|
||||
entry.loader = value;
|
||||
} else {
|
||||
Object.keys(value).forEach(function(valueKey) {
|
||||
entry[valueKey] = value[valueKey];
|
||||
});
|
||||
}
|
||||
loaders.push(entry);
|
||||
});
|
||||
return loaders;
|
||||
};
|
||||
92
dist/index.html
vendored
Normal file
92
dist/index.html
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- this is for the dev server -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<!-- don't be alarmed, these don't match what's in dist, because webpack-dev-server serves them in memory. -->
|
||||
<script src="/dist/SwaggerUIBundle.js"> </script>
|
||||
<script src="/dist/SwaggerUIStandalonePreset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "http://petstore.swagger.io/v2/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
// yay ES6 modules ↘
|
||||
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
105
dist/swagger-ui-bundle.js
vendored
Normal file
105
dist/swagger-ui-bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/swagger-ui-bundle.js.map
vendored
Normal file
1
dist/swagger-ui-bundle.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA6OA;;;;;;AAoIA;AAm7FA;AAwtCA;AAg0IA;;;;;AAkxBA;AAo8IA;AA41GA;AA23FA;AAmqFA;AA0nFA;AA49CA;AAwhDA;AAkrCA;AAumFA;AAmnHA;;;;;;;;;;;;;;AAqjHA;AAyoIA;AAkuJA;AAilHA;AA4kGA;AAwkEA;AAs3DA;AAovDA;AAotBA;AAoqGA;;;;;;AAueA;AAimGA;AA44EA;;;;;AAoGA;AA2qFA;AAo2CA;AAgvDA;AA8tCA;AAoiEA;AA69FA;;;;;;;;;AA20BA;AA2zIA;AAm4DA","sourceRoot":""}
|
||||
20
dist/swagger-ui-standalone-preset.js
vendored
Normal file
20
dist/swagger-ui-standalone-preset.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/swagger-ui-standalone-preset.js.map
vendored
Normal file
1
dist/swagger-ui-standalone-preset.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AAwSA;AAyiGA;AAqwFA;;;;;;AA4eA;AAkvFA;AAu+CA;AAo+CA;AAgrCA;AAgyEA","sourceRoot":""}
|
||||
2
dist/swagger-ui.css
vendored
Normal file
2
dist/swagger-ui.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/swagger-ui.css.map
vendored
Normal file
1
dist/swagger-ui.css.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
|
||||
9
dist/swagger-ui.js
vendored
Normal file
9
dist/swagger-ui.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/swagger-ui.js.map
vendored
Normal file
1
dist/swagger-ui.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;AAooGA;AAw0HA;AAijGA;AA6lCA;AA29BA;AAmwCA;AAw4BA","sourceRoot":""}
|
||||
145
make-webpack-config.js
Normal file
145
make-webpack-config.js
Normal file
@@ -0,0 +1,145 @@
|
||||
var path = require('path')
|
||||
|
||||
var webpack = require('webpack')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
var deepExtend = require('deep-extend')
|
||||
var autoprefixer = require('autoprefixer')
|
||||
|
||||
var loadersByExtension = require('./build-tools/loadersByExtension')
|
||||
|
||||
module.exports = function(options) {
|
||||
|
||||
// Special options, that have logic in this file
|
||||
// ...with defaults
|
||||
var specialOptions = deepExtend({}, {
|
||||
hot: false,
|
||||
separateStylesheets: true,
|
||||
minimize: false,
|
||||
longTermCaching: false,
|
||||
sourcemaps: false,
|
||||
}, options._special)
|
||||
|
||||
var loadersMap = {
|
||||
'js(x)?': {
|
||||
loader: 'babel?retainLines=true',
|
||||
include: [ path.join(__dirname, 'src') ],
|
||||
},
|
||||
'json': 'json-loader',
|
||||
'txt|yaml': 'raw-loader',
|
||||
'png|jpg|jpeg|gif|svg': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=10000',
|
||||
'woff|woff2': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=100000',
|
||||
'ttf|eot': specialOptions.disableAssets ? 'null-loader' : 'file-loader',
|
||||
"worker.js": ["worker-loader?inline=true", "babel"]
|
||||
}
|
||||
|
||||
var plugins = []
|
||||
|
||||
if( specialOptions.separateStylesheets ) {
|
||||
plugins.push(new ExtractTextPlugin('[name].css' + (specialOptions.longTermCaching ? '?[contenthash]' : ''), {
|
||||
allChunks: true
|
||||
}))
|
||||
}
|
||||
|
||||
if( specialOptions.minimize ) {
|
||||
|
||||
plugins.push(
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compressor: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.DedupePlugin()
|
||||
)
|
||||
|
||||
plugins.push( new webpack.NoErrorsPlugin())
|
||||
|
||||
}
|
||||
|
||||
plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: specialOptions.minimize ? JSON.stringify('production') : null,
|
||||
WEBPACK_INLINE_STYLES: !Boolean(specialOptions.separateStylesheets)
|
||||
},
|
||||
}))
|
||||
|
||||
var cssLoader = 'css-loader!postcss-loader'
|
||||
|
||||
var completeStylesheetLoaders = deepExtend({
|
||||
'css': cssLoader,
|
||||
'scss': cssLoader + '!' + 'sass-loader?outputStyle=expanded&sourceMap=true&sourceMapContents=true',
|
||||
'less': cssLoader + '!' + 'less-loader',
|
||||
}, specialOptions.stylesheetLoaders)
|
||||
|
||||
if(specialOptions.cssModules) {
|
||||
cssLoader = cssLoader + '?module' + (specialOptions.minimize ? '' : '&localIdentName=[path][name]---[local]---[hash:base64:5]')
|
||||
}
|
||||
|
||||
Object.keys(completeStylesheetLoaders).forEach(function(ext) {
|
||||
var ori = completeStylesheetLoaders[ext]
|
||||
if(specialOptions.separateStylesheets) {
|
||||
completeStylesheetLoaders[ext] = ExtractTextPlugin.extract('style-loader', ori)
|
||||
} else {
|
||||
completeStylesheetLoaders[ext] = 'style-loader!' + ori
|
||||
}
|
||||
})
|
||||
|
||||
var loaders = loadersByExtension(deepExtend({}, loadersMap, specialOptions.loaders, completeStylesheetLoaders))
|
||||
var extraLoaders = (options.module || {} ).loaders
|
||||
|
||||
if(Array.isArray(extraLoaders)) {
|
||||
loaders = loaders.concat(extraLoaders)
|
||||
delete options.module.loaders
|
||||
}
|
||||
|
||||
var completeConfig = deepExtend({
|
||||
entry: {},
|
||||
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].js'
|
||||
},
|
||||
|
||||
target: 'web',
|
||||
|
||||
// yaml-js has a reference to `fs`, this is a workaround
|
||||
node: {
|
||||
fs: 'empty'
|
||||
},
|
||||
|
||||
module: {
|
||||
loaders: loaders,
|
||||
},
|
||||
|
||||
resolveLoader: {
|
||||
root: path.join(__dirname, 'node_modules'),
|
||||
},
|
||||
|
||||
externals: {
|
||||
'buffertools': true // json-react-schema/deeper depends on buffertools, which fails.
|
||||
},
|
||||
|
||||
resolve: {
|
||||
root: path.join(__dirname, './src'),
|
||||
modulesDirectories: ['node_modules'],
|
||||
extensions: ["", ".web.js", ".js", ".jsx", ".json", ".less"],
|
||||
packageAlias: 'browser',
|
||||
alias: {
|
||||
base: "getbase/src/less/base"
|
||||
}
|
||||
},
|
||||
|
||||
postcss: function() {
|
||||
return [autoprefixer]
|
||||
},
|
||||
|
||||
devtool: specialOptions.sourcemaps ? 'cheap-module-source-map' : null,
|
||||
|
||||
plugins,
|
||||
|
||||
}, options)
|
||||
|
||||
return completeConfig
|
||||
}
|
||||
124
package.json
Normal file
124
package.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"name": "swagger-ui",
|
||||
"version": "3.0.0",
|
||||
"main": "dist/swagger-ui.js",
|
||||
"repository": "git@github.com:swagger-api/swagger-ui.git",
|
||||
"contributors": [
|
||||
"(in alphabetical order)",
|
||||
"Anna Bodnia <anna.bodnia@gmail.com>",
|
||||
"Buu Nguyen <buunguyen@gmail.com>",
|
||||
"Josh Ponelat <jponelat@gmail.com>",
|
||||
"Kyle Shockey <kyleshockey1@gmail.com>",
|
||||
"Robert Barnwell <robert@robertismy.name>",
|
||||
"Sahar Jafari <shr.jafari@gmail.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "http-server -i -a 0.0.0.0 -p 3001",
|
||||
"build": "npm run build-core && npm run build-bundle && npm run build-standalone",
|
||||
"build-bundle": "webpack --config webpack-dist-bundle.config.js --colors",
|
||||
"build-core": "webpack --config webpack-dist.config.js --colors",
|
||||
"build-standalone": "webpack --config webpack-dist-standalone.config.js --colors",
|
||||
"predev": "npm install",
|
||||
"dev": "npm-run-all --parallel hot-server watch open-localhost",
|
||||
"watch": "webpack --config webpack-watch.config.js --watch --progress",
|
||||
"open-localhost": "node -e 'require(\"open\")(\"http://localhost:3200\")'",
|
||||
"hot-server": "webpack-dev-server --host 0.0.0.0 --config webpack-hot-dev-server.config.js --inline --hot --progress --content-base dist/",
|
||||
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
|
||||
"deps-size": "webpack -p --config webpack.check.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
|
||||
"deps-check": "npm run deps-license && npm run deps-size",
|
||||
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace": "0.7.0",
|
||||
"btoa": "^1.1.2",
|
||||
"debounce": "1.0.0",
|
||||
"deep-extend": "0.4.1",
|
||||
"expect": "1.20.2",
|
||||
"getbase": "^2.8.2",
|
||||
"immutable": "^3.x.x",
|
||||
"js-yaml": "^3.5.5",
|
||||
"jsonschema": "^1.1.0",
|
||||
"less": "2.7.1",
|
||||
"lodash": "4.17.2",
|
||||
"matcher": "^0.1.2",
|
||||
"memoizee": "0.4.1",
|
||||
"promise-worker": "^1.1.1",
|
||||
"react": "^15.4.0",
|
||||
"react-addons-perf": "0.14.8",
|
||||
"react-addons-shallow-compare": "0.14.8",
|
||||
"react-addons-test-utils": "0.14.8",
|
||||
"react-collapse": "2.3.1",
|
||||
"react-dom": "^15.4.0",
|
||||
"react-height": "^2.0.0",
|
||||
"react-hot-loader": "1.3.1",
|
||||
"react-immutable-proptypes": "2.1.0",
|
||||
"react-motion": "0.4.4",
|
||||
"react-object-inspector": "0.2.1",
|
||||
"react-redux": "^4.x.x",
|
||||
"react-remarkable": "1.1.1",
|
||||
"react-split-pane": "0.1.57",
|
||||
"redux": "^3.x.x",
|
||||
"redux-immutable": "3.0.8",
|
||||
"redux-logger": "*",
|
||||
"reselect": "2.5.3",
|
||||
"serialize-error": "2.0.0",
|
||||
"swagger-client": "^3.0.1",
|
||||
"whatwg-fetch": "0.11.1",
|
||||
"worker-loader": "^0.7.1",
|
||||
"xml": "1.0.1",
|
||||
"yaml-js": "^0.1.3",
|
||||
"yaml-worker": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.6.1",
|
||||
"babel-core": "^6.23.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.3.2",
|
||||
"babel-plugin-module-alias": "^1.6.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babel-preset-es2015-ie": "^6.6.2",
|
||||
"babel-preset-react": "^6.23.0",
|
||||
"babel-preset-stage-0": "^6.22.0",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"css-loader": "0.22.0",
|
||||
"deep-extend": "^0.4.1",
|
||||
"deepmerge": "^1.3.2",
|
||||
"extract-text-webpack-plugin": "0.8.2",
|
||||
"file-loader": "0.8.4",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"imports-loader": "0.6.5",
|
||||
"json-loader": "0.5.3",
|
||||
"less": "2.5.3",
|
||||
"less-loader": "2.2.1",
|
||||
"license-checker": "^8.0.4",
|
||||
"mocha": "^2.5.3",
|
||||
"node-sass": "^4.5.0",
|
||||
"npm-run-all": "3.1.1",
|
||||
"null-loader": "0.1.1",
|
||||
"open": "0.0.5",
|
||||
"postcss-loader": "0.7.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"react-hot-loader": "^1.3.1",
|
||||
"rimraf": "^2.6.0",
|
||||
"sass-loader": "^6.0.2",
|
||||
"shallowequal": "0.2.2",
|
||||
"standard": "^8.6.0",
|
||||
"standard-loader": "^5.0.0",
|
||||
"style-loader": "0.13.0",
|
||||
"url-loader": "0.5.6",
|
||||
"webpack": "^1.14.0",
|
||||
"webpack-bundle-size-analyzer": "^2.5.0"
|
||||
},
|
||||
"config": {
|
||||
"deps_check_dir": ".deps_check"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"IE 10"
|
||||
],
|
||||
"optionalDependencies": {
|
||||
"webpack-dev-server": "1.14.0"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon-16x16.png
Normal file
BIN
public/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
BIN
public/favicon-32x32.png
Normal file
BIN
public/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
93
public/index.html
Normal file
93
public/index.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="../dist/swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="../dist/swagger-ui-bundle.js"> </script>
|
||||
<script src="../dist/swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "http://petstore.swagger.io/v2/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
// yay ES6 modules ↘
|
||||
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
83
public/oauth2-redirect.html
Normal file
83
public/oauth2-redirect.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var isValid, qp;
|
||||
|
||||
qp = (window.location.hash || location.search).substring(1);
|
||||
|
||||
qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
createForm(oauth2.auth, qp).submit();
|
||||
} else {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: "Authorization failed: no accessCode came from the server"
|
||||
});
|
||||
window.close();
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid});
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
|
||||
function createForm(auth, qp) {
|
||||
var form = document.createElement("form");
|
||||
var schema = auth.schema;
|
||||
var action = schema.get("tokenUrl");
|
||||
var name, input;
|
||||
|
||||
var fields = {
|
||||
code: qp.code,
|
||||
"redirect_uri": location.protocol + "//" + location.host + location.pathname,
|
||||
"grant_type": "authorization_code",
|
||||
"client_secret": auth.clientSecret,
|
||||
"client_id": auth.clientId
|
||||
}
|
||||
|
||||
for ( name in fields ) {
|
||||
input = document.createElement("input");
|
||||
input.name = name;
|
||||
input.value = fields[name];
|
||||
input.type = "hidden";
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
|
||||
form.method = "POST";
|
||||
form.action = action;
|
||||
|
||||
document.body.appendChild(form);
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
</script>
|
||||
6
src/core/brace-snippets-yaml.js
Normal file
6
src/core/brace-snippets-yaml.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/* global ace */
|
||||
ace.define("ace/snippets/yaml",
|
||||
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
|
||||
t.snippetText=undefined
|
||||
t.scope="yaml"
|
||||
})
|
||||
27
src/core/components/app.jsx
Normal file
27
src/core/components/app.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class App extends React.Component {
|
||||
|
||||
getLayout() {
|
||||
let { getComponent, layoutSelectors } = this.props
|
||||
const layoutName = layoutSelectors.current()
|
||||
const Component = getComponent(layoutName, true)
|
||||
return Component ? Component : ()=> <h1> No layout defined for "{layoutName}" </h1>
|
||||
}
|
||||
|
||||
render() {
|
||||
const Layout = this.getLayout()
|
||||
|
||||
return (
|
||||
<Layout/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
App.defaultProps = {
|
||||
}
|
||||
82
src/core/components/auth/api-key-auth.jsx
Normal file
82
src/core/components/auth/api-key-auth.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class ApiKeyAuth extends React.Component {
|
||||
static propTypes = {
|
||||
authorized: PropTypes.object,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
let { name, schema } = this.props
|
||||
let value = this.getValue()
|
||||
|
||||
this.state = {
|
||||
name: name,
|
||||
schema: schema,
|
||||
value: value
|
||||
}
|
||||
}
|
||||
|
||||
getValue () {
|
||||
let { name, authorized } = this.props
|
||||
|
||||
return authorized && authorized.getIn([name, "value"])
|
||||
}
|
||||
|
||||
onChange =(e) => {
|
||||
let { onChange } = this.props
|
||||
let value = e.target.value
|
||||
let newState = Object.assign({}, this.state, { value: value })
|
||||
|
||||
this.setState(newState)
|
||||
onChange(newState)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, getComponent, errSelectors, name } = this.props
|
||||
const Input = getComponent("Input")
|
||||
const Row = getComponent("Row")
|
||||
const Col = getComponent("Col")
|
||||
const AuthError = getComponent("authError")
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
let value = this.getValue()
|
||||
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
|
||||
{ value && <h6>Authorized</h6>}
|
||||
<Row>
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={ schema.get("description") } />
|
||||
</Row>
|
||||
<Row>
|
||||
<p>Name: <code>{ schema.get("name") }</code></p>
|
||||
</Row>
|
||||
<Row>
|
||||
<p>In: <code>{ schema.get("in") }</code></p>
|
||||
</Row>
|
||||
<Row>
|
||||
<label>Value:</label>
|
||||
<Col>
|
||||
{
|
||||
value || <Input type="text" onChange={ this.onChange }/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
errors.valueSeq().map( (error, key) => {
|
||||
return <AuthError error={ error }
|
||||
key={ key }/>
|
||||
} )
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
59
src/core/components/auth/authorization-popup.jsx
Normal file
59
src/core/components/auth/authorization-popup.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class AuthorizationPopup extends React.Component {
|
||||
close =() => {
|
||||
let { authActions } = this.props
|
||||
|
||||
authActions.showDefinitions(false)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST } } = this.props
|
||||
let definitions = authSelectors.shownDefinitions()
|
||||
const Auths = getComponent("auths")
|
||||
|
||||
return (
|
||||
<div className="dialog-ux">
|
||||
<div className="backdrop-ux"></div>
|
||||
<div className="modal-ux">
|
||||
<div className="modal-dialog-ux">
|
||||
<div className="modal-ux-inner">
|
||||
<div className="modal-ux-header">
|
||||
<h3>Available authorizations</h3>
|
||||
<button type="button" className="close-modal" onClick={ this.close }>
|
||||
<svg width="20" height="20">
|
||||
<use xlinkHref="#close" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-ux-content">
|
||||
|
||||
{
|
||||
definitions.valueSeq().map(( definition, key ) => {
|
||||
return <Auths key={ key }
|
||||
AST={AST}
|
||||
definitions={ definition }
|
||||
getComponent={ getComponent }
|
||||
errSelectors={ errSelectors }
|
||||
authSelectors={ authSelectors }
|
||||
authActions={ authActions }
|
||||
specSelectors={ specSelectors }/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
fn: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
}
|
||||
}
|
||||
42
src/core/components/auth/authorize-btn.jsx
Normal file
42
src/core/components/auth/authorize-btn.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class AuthorizeBtn extends React.Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
onClick =() => {
|
||||
let { authActions, authSelectors, errActions} = this.props
|
||||
let definitions = authSelectors.definitionsToAuthorize()
|
||||
|
||||
authActions.showDefinitions(definitions)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { authSelectors, getComponent } = this.props
|
||||
//must be moved out of button component
|
||||
const AuthorizationPopup = getComponent("authorizationPopup", true)
|
||||
let showPopup = !!authSelectors.shownDefinitions()
|
||||
let isAuthorized = !!authSelectors.authorized().size
|
||||
|
||||
return (
|
||||
<div className="auth-wrapper">
|
||||
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={ this.onClick }>
|
||||
<span>Authorize</span>
|
||||
<svg width="20" height="20">
|
||||
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
|
||||
</svg>
|
||||
</button>
|
||||
{ showPopup && <AuthorizationPopup /> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
errActions: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
}
|
||||
}
|
||||
34
src/core/components/auth/authorize-operation-btn.jsx
Normal file
34
src/core/components/auth/authorize-operation-btn.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class AuthorizeOperationBtn extends React.Component {
|
||||
onClick =(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
let { security, authActions, authSelectors } = this.props
|
||||
let definitions = authSelectors.getDefinitionsByNames(security)
|
||||
|
||||
authActions.showDefinitions(definitions)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { security, authSelectors } = this.props
|
||||
|
||||
let isAuthorized = authSelectors.isAuthorized(security)
|
||||
|
||||
return (
|
||||
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
|
||||
<svg width="20" height="20">
|
||||
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
security: ImPropTypes.iterable.isRequired
|
||||
}
|
||||
}
|
||||
138
src/core/components/auth/auths.jsx
Normal file
138
src/core/components/auth/auths.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class Auths extends React.Component {
|
||||
static propTypes = {
|
||||
definitions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
onAuthChange =(auth) => {
|
||||
let { name } = auth
|
||||
|
||||
this.setState({ [name]: auth })
|
||||
}
|
||||
|
||||
submitAuth =(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
let { authActions } = this.props
|
||||
|
||||
authActions.authorize(this.state)
|
||||
}
|
||||
|
||||
logoutClick =(e) => {
|
||||
e.preventDefault()
|
||||
|
||||
let { authActions, definitions } = this.props
|
||||
let auths = definitions.map( (val, key) => {
|
||||
return key
|
||||
}).toArray()
|
||||
|
||||
authActions.logout(auths)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { definitions, getComponent, authSelectors, errSelectors, specSelectors } = this.props
|
||||
const ApiKeyAuth = getComponent("apiKeyAuth")
|
||||
const BasicAuth = getComponent("basicAuth")
|
||||
const Oauth2 = getComponent("oauth2", true)
|
||||
const Button = getComponent("Button")
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
|
||||
let specStr = specSelectors.specStr()
|
||||
let authorized = authSelectors.authorized()
|
||||
|
||||
let authorizedAuth = definitions.filter( (definition, key) => {
|
||||
return !!authorized.get(key)
|
||||
})
|
||||
|
||||
let nonOauthDefinitions = definitions.filter( schema => schema.get("type") !== "oauth2")
|
||||
let oauthDefinitions = definitions.filter( schema => schema.get("type") === "oauth2")
|
||||
|
||||
return (
|
||||
<div className="auth-container">
|
||||
{
|
||||
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
|
||||
{
|
||||
nonOauthDefinitions.map( (schema, name) => {
|
||||
let type = schema.get("type")
|
||||
let authEl
|
||||
|
||||
switch(type) {
|
||||
case "apiKey": authEl = <ApiKeyAuth key={ name }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
errSelectors={ errSelectors }
|
||||
authorized={ authorized }
|
||||
getComponent={ getComponent }
|
||||
onChange={ this.onAuthChange } />
|
||||
break
|
||||
case "basic": authEl = <BasicAuth key={ name }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
errSelectors={ errSelectors }
|
||||
authorized={ authorized }
|
||||
getComponent={ getComponent }
|
||||
onChange={ this.onAuthChange } />
|
||||
break
|
||||
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
|
||||
}
|
||||
|
||||
return (<div key={`${name}-jump`}>
|
||||
{ authEl }
|
||||
</div>)
|
||||
|
||||
}).toArray()
|
||||
}
|
||||
<div className="auth-btn-wrapper">
|
||||
{
|
||||
nonOauthDefinitions.size === authorizedAuth.size ? <Button className="btn modal-btn auth" onClick={ this.logoutClick }>Logout</Button>
|
||||
: <Button type="submit" className="btn modal-btn auth authorize">Authorize</Button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
{
|
||||
oauthDefinitions && oauthDefinitions.size ? <div>
|
||||
<div className="scope-def">
|
||||
<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.</p>
|
||||
<p>API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>
|
||||
</div>
|
||||
{
|
||||
definitions.filter( schema => schema.get("type") === "oauth2")
|
||||
.map( (schema, name) =>{
|
||||
return (<div key={ name }>
|
||||
<Oauth2 authorized={ authorized }
|
||||
schema={ schema }
|
||||
name={ name } />
|
||||
</div>)
|
||||
}
|
||||
).toArray()
|
||||
}
|
||||
</div> : null
|
||||
}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
definitions: ImPropTypes.iterable.isRequired
|
||||
}
|
||||
}
|
||||
97
src/core/components/auth/basic-auth.jsx
Normal file
97
src/core/components/auth/basic-auth.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class BasicAuth extends React.Component {
|
||||
static propTypes = {
|
||||
authorized: PropTypes.object,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
let { schema, name } = this.props
|
||||
|
||||
let value = this.getValue()
|
||||
let username = value.username
|
||||
|
||||
this.state = {
|
||||
name: name,
|
||||
schema: schema,
|
||||
value: !username ? {} : {
|
||||
username: username
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValue () {
|
||||
let { authorized, name } = this.props
|
||||
|
||||
return authorized && authorized.getIn([name, "value"]) || {}
|
||||
}
|
||||
|
||||
onChange =(e) => {
|
||||
let { onChange } = this.props
|
||||
let { value, name } = e.target
|
||||
|
||||
let newValue = this.state.value
|
||||
newValue[name] = value
|
||||
|
||||
this.setState({ value: newValue })
|
||||
|
||||
onChange(this.state)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, getComponent, name, errSelectors } = this.props
|
||||
const Input = getComponent("Input")
|
||||
const Row = getComponent("Row")
|
||||
const Col = getComponent("Col")
|
||||
const AuthError = getComponent("authError")
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
let username = this.getValue().username
|
||||
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
|
||||
{ username && <h6>Authorized</h6> }
|
||||
<Row>
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={ schema.get("description") } />
|
||||
</Row>
|
||||
<Row>
|
||||
<Col tablet={2} desktop={2}>username:</Col>
|
||||
<Col tablet={10} desktop={10}>
|
||||
{
|
||||
username || <Input type="text" required="required" name="username" onChange={ this.onChange }/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
!username && <Row>
|
||||
<Col tablet={2} desktop={2}>password:</Col>
|
||||
<Col tablet={10} desktop={10}><Input required="required" autoComplete="new-password" name="password" type="password" onChange={ this.onChange }/></Col>
|
||||
</Row>
|
||||
}
|
||||
{
|
||||
errors.valueSeq().map( (error, key) => {
|
||||
return <AuthError error={ error }
|
||||
key={ key }/>
|
||||
} )
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
schema: ImPropTypes.map,
|
||||
authorized: ImPropTypes.map
|
||||
}
|
||||
}
|
||||
23
src/core/components/auth/error.jsx
Normal file
23
src/core/components/auth/error.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class AuthError extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
error: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
let { error } = this.props
|
||||
|
||||
let level = error.get("level")
|
||||
let message = error.get("message")
|
||||
let source = error.get("source")
|
||||
|
||||
return (
|
||||
<div className="errors" style={{ backgroundColor: "#ffeeee", color: "red", margin: "1em" }}>
|
||||
<b style={{ textTransform: "capitalize", marginRight: "1em"}} >{ source } { level }</b>
|
||||
<span>{ message }</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
218
src/core/components/auth/oauth2.jsx
Normal file
218
src/core/components/auth/oauth2.jsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import oauth2Authorize from "core/oauth2-authorize"
|
||||
|
||||
const IMPLICIT = "implicit"
|
||||
const ACCESS_CODE = "accessCode"
|
||||
const PASSWORD = "password"
|
||||
const APPLICATION = "application"
|
||||
|
||||
export default class Oauth2 extends React.Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
authorized: PropTypes.object,
|
||||
configs: PropTypes.object,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
errActions: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
let { name, schema, authorized } = this.props
|
||||
let auth = authorized && authorized.get(name)
|
||||
let username = auth && auth.get("username") || ""
|
||||
let clientId = auth && auth.get("clientId") || ""
|
||||
let clientSecret = auth && auth.get("clientSecret") || ""
|
||||
let passwordType = auth && auth.get("passwordType") || "none"
|
||||
|
||||
this.state = {
|
||||
name: name,
|
||||
schema: schema,
|
||||
scopes: [],
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
username: username,
|
||||
password: "",
|
||||
passwordType: passwordType
|
||||
}
|
||||
}
|
||||
|
||||
authorize =() => {
|
||||
let { authActions, errActions, getConfigs } = this.props
|
||||
let configs = getConfigs()
|
||||
|
||||
errActions.clear({authId: name,type: "auth", source: "auth"})
|
||||
oauth2Authorize(this.state, authActions, errActions, configs)
|
||||
}
|
||||
|
||||
onScopeChange =(e) => {
|
||||
let { target } = e
|
||||
let { checked } = target
|
||||
let scope = target.dataset.value
|
||||
|
||||
if ( checked && this.state.scopes.indexOf(scope) === -1 ) {
|
||||
let newScopes = this.state.scopes.concat([scope])
|
||||
this.setState({ scopes: newScopes })
|
||||
} else if ( !checked && this.state.scopes.indexOf(scope) > -1) {
|
||||
this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })
|
||||
}
|
||||
}
|
||||
|
||||
onInputChange =(e) => {
|
||||
let { target : { dataset : { name }, value } } = e
|
||||
let state = {
|
||||
[name]: value
|
||||
}
|
||||
|
||||
this.setState(state)
|
||||
}
|
||||
|
||||
logout =(e) => {
|
||||
e.preventDefault()
|
||||
let { authActions, errActions, name } = this.props
|
||||
|
||||
errActions.clear({authId: name, type: "auth", source: "auth"})
|
||||
authActions.logout([ name ])
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, getComponent, authSelectors, errSelectors, name } = this.props
|
||||
const Input = getComponent("Input")
|
||||
const Row = getComponent("Row")
|
||||
const Col = getComponent("Col")
|
||||
const Button = getComponent("Button")
|
||||
const AuthError = getComponent("authError")
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
|
||||
let flow = schema.get("flow")
|
||||
let scopes = schema.get("allowedScopes") || schema.get("scopes")
|
||||
let authorizedAuth = authSelectors.authorized().get(name)
|
||||
let isAuthorized = !!authorizedAuth
|
||||
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
|
||||
let isValid = !errors.filter( err => err.get("source") === "validation").size
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={ schema.get("description") } />
|
||||
|
||||
{ isAuthorized && <h6>Authorized</h6> }
|
||||
|
||||
{ ( flow === IMPLICIT || flow === ACCESS_CODE ) && <p>Authorization URL: <code>{ schema.get("authorizationUrl") }</code></p> }
|
||||
{ ( flow === PASSWORD || flow === ACCESS_CODE || flow === APPLICATION ) && <p>Token URL:<code> { schema.get("tokenUrl") }</code></p> }
|
||||
<p className="flow">Flow: <code>{ schema.get("flow") }</code></p>
|
||||
|
||||
{
|
||||
flow === PASSWORD && ( !isAuthorized || isAuthorized && this.state.username) && <Row>
|
||||
<Col tablet={2} desktop={2}>username:</Col>
|
||||
<Col tablet={10} desktop={10}>
|
||||
{
|
||||
isAuthorized ? <span>{ this.state.username }</span>
|
||||
: <input type="text" data-name="username" onChange={ this.onInputChange }/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
|
||||
{
|
||||
flow === PASSWORD && !isAuthorized && <Row>
|
||||
<Col tablet={2} desktop={2}>password:</Col>
|
||||
<Col tablet={10} desktop={10}>
|
||||
<input type="password" data-name="password" onChange={ this.onInputChange }/>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
|
||||
{
|
||||
flow === PASSWORD && <Row>
|
||||
<Col tablet={2} desktop={2}>type:</Col>
|
||||
<Col tablet={10} desktop={10}>
|
||||
{
|
||||
isAuthorized ? <span>{ this.state.passwordType }</span>
|
||||
: <select data-name="passwordType" onChange={ this.onInputChange }>
|
||||
<option value="none">None or other</option>
|
||||
<option value="basic">Basic auth</option>
|
||||
<option value="request">Request body</option>
|
||||
</select>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
|
||||
{
|
||||
( flow === IMPLICIT || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) &&
|
||||
( !isAuthorized || isAuthorized && this.state.clientId) && <Row>
|
||||
<label htmlFor="client_id">client_id:</label>
|
||||
<Col tablet={10} desktop={10}>
|
||||
{
|
||||
isAuthorized ? <span>{ this.state.clientId }</span>
|
||||
: <input id="client_id" type="text" required={ flow === PASSWORD } data-name="clientId"
|
||||
onChange={ this.onInputChange }/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
|
||||
{
|
||||
( flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) && <Row>
|
||||
<label htmlFor="client_secret">client_secret:</label>
|
||||
<Col tablet={10} desktop={10}>
|
||||
{
|
||||
isAuthorized ? <span>{ this.state.clientSecret }</span>
|
||||
: <input id="client_secret" type="text" data-name="clientSecret"
|
||||
onChange={ this.onInputChange }/>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
|
||||
{
|
||||
!isAuthorized && flow !== PASSWORD && scopes && scopes.size ? <div className="scopes">
|
||||
<h2>Scopes:</h2>
|
||||
{ scopes.map((description, name) => {
|
||||
return (
|
||||
<Row key={ name }>
|
||||
<div className="checkbox">
|
||||
<Input data-value={ name }
|
||||
id={`${name}-checkbox`}
|
||||
disabled={ isAuthorized }
|
||||
type="checkbox"
|
||||
onChange={ this.onScopeChange }/>
|
||||
<label htmlFor={`${name}-checkbox`}>
|
||||
<span className="item"></span>
|
||||
<div className="text">
|
||||
<p className="name">{name}</p>
|
||||
<p className="description">{description}</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
</div> : null
|
||||
}
|
||||
|
||||
{
|
||||
errors.valueSeq().map( (error, key) => {
|
||||
return <AuthError error={ error }
|
||||
key={ key }/>
|
||||
} )
|
||||
}
|
||||
<div className="auth-btn-wrapper">
|
||||
{ isValid && flow !== APPLICATION &&
|
||||
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout }>Logout</Button>
|
||||
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize }>Authorize</Button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
24
src/core/components/clear.jsx
Normal file
24
src/core/components/clear.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
|
||||
export default class Clear extends Component {
|
||||
|
||||
onClick =() => {
|
||||
let { specActions, path, method } = this.props
|
||||
specActions.clearResponse( path, method )
|
||||
specActions.clearRequest( path, method )
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<button className="btn btn-clear opblock-control__btn" onClick={ this.onClick }>
|
||||
Clear
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
}
|
||||
}
|
||||
45
src/core/components/content-type.jsx
Normal file
45
src/core/components/content-type.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { fromJS } from 'immutable'
|
||||
|
||||
const noop = ()=>{}
|
||||
|
||||
export default class ContentType extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]),
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
onChange: noop,
|
||||
value: null,
|
||||
contentTypes: fromJS(["application/json"]),
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Needed to populate the form, initially
|
||||
this.props.onChange(this.props.contentTypes.first())
|
||||
}
|
||||
|
||||
onChangeWrapper = e => this.props.onChange(e.target.value)
|
||||
|
||||
render() {
|
||||
let { contentTypes, className, value } = this.props
|
||||
|
||||
if ( !contentTypes || !contentTypes.size )
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={ "content-type-wrapper " + ( className || "" ) }>
|
||||
<select className="content-type" value={value} onChange={this.onChangeWrapper} >
|
||||
{ contentTypes.map( (val) => {
|
||||
return <option key={ val } value={ val }>{ val }</option>
|
||||
}).toArray()}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
28
src/core/components/curl.jsx
Normal file
28
src/core/components/curl.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import curlify from "core/curlify"
|
||||
|
||||
export default class Curl extends React.Component {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
handleFocus(e) {
|
||||
e.target.select()
|
||||
document.execCommand("copy")
|
||||
}
|
||||
|
||||
render() {
|
||||
let { request } = this.props
|
||||
let curl = curlify(request)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Curl</h4>
|
||||
<div className="copy-paste">
|
||||
<textarea onFocus={this.handleFocus} className="curl" style={{ whiteSpace: "normal" }} defaultValue={curl}></textarea>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
51
src/core/components/debug.jsx
Normal file
51
src/core/components/debug.jsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import Collapse from "react-collapse"
|
||||
import { presets } from "react-motion"
|
||||
import ObjectInspector from "react-object-inspector"
|
||||
import Perf from "react-addons-perf"
|
||||
|
||||
export default class Debug extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
jsonDumpOpen: false
|
||||
}
|
||||
this.toggleJsonDump = (e) => {
|
||||
e.preventDefault()
|
||||
this.setState({jsonDumpOpen: !this.state.jsonDumpOpen})
|
||||
}
|
||||
window.Perf = Perf
|
||||
}
|
||||
|
||||
plusOrMinus(bool) {
|
||||
return bool ? "-" : "+"
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
let { getState } = this.props
|
||||
|
||||
window.props = this.props
|
||||
|
||||
return (
|
||||
<div className="info">
|
||||
<h3><a onClick={this.toggleJsonDump}> {this.plusOrMinus(this.state.jsonDumpOpen)} App </a></h3>
|
||||
|
||||
<Collapse isOpened={this.state.jsonDumpOpen} springConfig={presets.noWobble}>
|
||||
|
||||
<ObjectInspector data={getState().toJS() || {}} name="state" initialExpandedPaths={["state"]}/>
|
||||
|
||||
</Collapse>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Debug.propTypes = {
|
||||
getState: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
113
src/core/components/errors.jsx
Normal file
113
src/core/components/errors.jsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import Im, { List } from "immutable"
|
||||
import Collapse from "react-collapse"
|
||||
import sortBy from "lodash/sortBy"
|
||||
|
||||
export default class Errors extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
jumpToLine: PropTypes.func,
|
||||
errSelectors: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
let { jumpToLine, errSelectors, layoutSelectors, layoutActions } = this.props
|
||||
|
||||
let errors = errSelectors.allErrors()
|
||||
|
||||
// all thrown errors, plus error-level everything else
|
||||
let allErrorsToDisplay = errors.filter(err => err.get("type") === "thrown" ? true :err.get("level") === "error")
|
||||
|
||||
if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
let isVisible = layoutSelectors.isShown(["errorPane"], true)
|
||||
let toggleVisibility = () => layoutActions.show(["errorPane"], !isVisible)
|
||||
|
||||
let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get("line"))
|
||||
|
||||
return (
|
||||
<pre className="errors-wrapper">
|
||||
<hgroup className="error">
|
||||
<h4 className="errors__title">Errors</h4>
|
||||
<button className="btn errors__clear-btn" onClick={ toggleVisibility }>{ isVisible ? "Hide" : "Show" }</button>
|
||||
</hgroup>
|
||||
<Collapse isOpened={ isVisible } animated >
|
||||
<div className="errors">
|
||||
{ sortedJSErrors.map((err, i) => {
|
||||
if(err.get("type") === "thrown") {
|
||||
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} />
|
||||
}
|
||||
if(err.get("type") === "spec") {
|
||||
return <SpecErrorItem key={ i } error={ err } jumpToLine={jumpToLine} />
|
||||
}
|
||||
}) }
|
||||
</div>
|
||||
</Collapse>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ThrownErrorItem = ( { error, jumpToLine } ) => {
|
||||
if(!error) {
|
||||
return null
|
||||
}
|
||||
let errorLine = error.get("line")
|
||||
|
||||
return (
|
||||
<div className="error-wrapper">
|
||||
{ !error ? null :
|
||||
<div>
|
||||
<h4>{ (error.get("source") && error.get("level")) ?
|
||||
toTitleCase(error.get("source")) + " " + error.get("level") : "" }
|
||||
{ error.get("path") ? <small> at {error.get("path")}</small>: null }</h4>
|
||||
<span style={{ whiteSpace: "pre-line", "maxWidth": "100%" }}>
|
||||
{ error.get("message") }
|
||||
</span>
|
||||
<div>
|
||||
{ errorLine ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SpecErrorItem = ( { error, jumpToLine } ) => {
|
||||
return (
|
||||
<div className="error-wrapper">
|
||||
{ !error ? null :
|
||||
<div>
|
||||
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }{ error.get("path") ? <small> at {List.isList(error.get("path")) ? error.get("path").join(".") : error.get("path")}</small>: null }</h4>
|
||||
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span>
|
||||
<div>
|
||||
{ jumpToLine ? (
|
||||
<a onClick={jumpToLine.bind(null, error.get("line"))}>Jump to line { error.get("line") }</a>
|
||||
) : null }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function toTitleCase(str) {
|
||||
return str
|
||||
.split(" ")
|
||||
.map(substr => substr[0].toUpperCase() + substr.slice(1))
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
ThrownErrorItem.propTypes = {
|
||||
error: PropTypes.object.isRequired,
|
||||
jumpToLine: PropTypes.func
|
||||
}
|
||||
|
||||
SpecErrorItem.propTypes = {
|
||||
error: PropTypes.object.isRequired,
|
||||
jumpToLine: PropTypes.func
|
||||
}
|
||||
41
src/core/components/execute.jsx
Normal file
41
src/core/components/execute.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import { fromJS } from "immutable"
|
||||
|
||||
export default class Execute extends Component {
|
||||
|
||||
static propTypes = {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
operation: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
onExecute: PropTypes.func
|
||||
}
|
||||
|
||||
onClick=()=>{
|
||||
let { specSelectors, specActions, operation, path, method } = this.props
|
||||
|
||||
specActions.validateParams( [path, method] )
|
||||
|
||||
if ( specSelectors.validateBeforeExecute([path, method]) ) {
|
||||
if(this.props.onExecute) {
|
||||
this.props.onExecute()
|
||||
}
|
||||
specActions.execute( { operation, path, method } )
|
||||
}
|
||||
}
|
||||
|
||||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
|
||||
|
||||
render(){
|
||||
let { getComponent, operation, specActions, path, method } = this.props
|
||||
const ContentType = getComponent( "contentType" )
|
||||
|
||||
return (
|
||||
<button className="btn execute opblock-control__btn" onClick={ this.onClick }>
|
||||
Execute
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
9
src/core/components/footer.jsx
Normal file
9
src/core/components/footer.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
|
||||
export default class Footer extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="footer"></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
46
src/core/components/headers.jsx
Normal file
46
src/core/components/headers.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import Im from "immutable"
|
||||
|
||||
export default class Headers extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
headers: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
|
||||
let { headers } = this.props
|
||||
|
||||
if ( !headers || !headers.size )
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className="headers-wrapper">
|
||||
<h4 className="headers__title">Headers:</h4>
|
||||
<table className="headers">
|
||||
<thead>
|
||||
<tr className="header-row">
|
||||
<th className="header-col">Name</th>
|
||||
<th className="header-col">Description</th>
|
||||
<th className="header-col">Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
headers.entrySeq().map( ([ key, header ]) => {
|
||||
if(!Im.Map.isMap(header)) {
|
||||
return null
|
||||
}
|
||||
return (<tr key={ key }>
|
||||
<td className="header-col">{ key }</td>
|
||||
<td className="header-col">{ header.get( "description" ) }</td>
|
||||
<td className="header-col">{ header.get( "type" ) }</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
24
src/core/components/highlight-code.jsx
Normal file
24
src/core/components/highlight-code.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import { highlight } from "core/utils"
|
||||
|
||||
export default class HighlightCode extends Component {
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
highlight(this.refs.el)
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
highlight(this.refs.el)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { value, className } = this.props
|
||||
className = className || ""
|
||||
|
||||
return <pre ref="el" className={className + " microlight"}>{ value }</pre>
|
||||
}
|
||||
}
|
||||
128
src/core/components/info.jsx
Normal file
128
src/core/components/info.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { fromJS } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
|
||||
class Path extends React.Component {
|
||||
static propTypes = {
|
||||
host: PropTypes.string,
|
||||
basePath: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
let { host, basePath } = this.props
|
||||
|
||||
return (
|
||||
<pre className="base-url">
|
||||
[ Base url: {host}{basePath}]
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Contact extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.object
|
||||
}
|
||||
|
||||
render(){
|
||||
let { data } = this.props
|
||||
let name = data.get("name") || "the developer"
|
||||
let url = data.get("url")
|
||||
let email = data.get("email")
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> }
|
||||
{ email &&
|
||||
<a href={`mailto:${email}`}>
|
||||
{ url ? `Send email to ${name}` : `Contact ${name}`}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class License extends React.Component {
|
||||
static propTypes = {
|
||||
license: PropTypes.object
|
||||
}
|
||||
|
||||
render(){
|
||||
let { license } = this.props
|
||||
let name = license.get("name") || "License"
|
||||
let url = license.get("url")
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
url ? <a href={ url }>{ name }</a>
|
||||
: <span>{ name }</span>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default class Info extends React.Component {
|
||||
static propTypes = {
|
||||
info: PropTypes.object,
|
||||
url: PropTypes.string,
|
||||
host: PropTypes.string,
|
||||
basePath: PropTypes.string,
|
||||
externalDocs: ImPropTypes.map,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
let { info, url, host, basePath, getComponent, externalDocs } = this.props
|
||||
let version = info.get("version")
|
||||
let description = info.get("description")
|
||||
let title = info.get("title")
|
||||
let termsOfService = info.get("termsOfService")
|
||||
let contact = info.get("contact")
|
||||
let license = info.get("license")
|
||||
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
|
||||
|
||||
const Markdown = getComponent("Markdown")
|
||||
|
||||
return (
|
||||
<div className="info">
|
||||
<hgroup className="main">
|
||||
<h2 className="title" >{ title }
|
||||
{ version && <small><pre className="version"> { version } </pre></small> }
|
||||
</h2>
|
||||
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
|
||||
{ url && <a href={ url }><span className="url"> { url } </span></a> }
|
||||
</hgroup>
|
||||
|
||||
<div className="description">
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
|
||||
</div>
|
||||
|
||||
{
|
||||
termsOfService && <div>
|
||||
<a href={ termsOfService }>Terms of service</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
{ contact && contact.size ? <Contact data={ contact } /> : null }
|
||||
{ license && license.size ? <License license={ license } /> : null }
|
||||
{ externalDocsUrl ?
|
||||
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a>
|
||||
: null }
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Info.propTypes = {
|
||||
title: PropTypes.any,
|
||||
description: PropTypes.any,
|
||||
version: PropTypes.any,
|
||||
url: PropTypes.string
|
||||
}
|
||||
252
src/core/components/layout-utils.jsx
Normal file
252
src/core/components/layout-utils.jsx
Normal file
@@ -0,0 +1,252 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import OriCollapse from "react-collapse"
|
||||
import _Markdown from "react-remarkable"
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
function xclass(...args) {
|
||||
return args.filter(a => !!a).join(" ").trim()
|
||||
}
|
||||
|
||||
export const Markdown = _Markdown
|
||||
|
||||
export class Container extends React.Component {
|
||||
render() {
|
||||
let { fullscreen, full, ...rest } = this.props
|
||||
// Normal element
|
||||
|
||||
if(fullscreen)
|
||||
return <section {...rest}/>
|
||||
|
||||
let containerClass = "container" + (full ? "-full" : "")
|
||||
return (
|
||||
<section {...rest} className={xclass(rest.className, containerClass)}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Container.propTypes = {
|
||||
fullscreen: PropTypes.bool,
|
||||
full: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
const DEVICES = {
|
||||
"mobile": "",
|
||||
"tablet": "-tablet",
|
||||
"desktop": "-desktop",
|
||||
"large": "-hd"
|
||||
}
|
||||
|
||||
export class Col extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
hide,
|
||||
keepContents,
|
||||
|
||||
mobile, /* we don't want these in the final component, since React now complains. So we extract them */
|
||||
tablet,
|
||||
desktop,
|
||||
large,
|
||||
|
||||
...rest
|
||||
} = this.props
|
||||
|
||||
if(hide && !keepContents)
|
||||
return <span/>
|
||||
|
||||
let classesAr = []
|
||||
|
||||
for (let device in DEVICES) {
|
||||
let deviceClass = DEVICES[device]
|
||||
if(device in this.props) {
|
||||
let val = this.props[device]
|
||||
|
||||
if(val < 1) {
|
||||
classesAr.push("none" + deviceClass)
|
||||
continue
|
||||
}
|
||||
|
||||
classesAr.push("block" + deviceClass)
|
||||
classesAr.push("col-" + val + deviceClass)
|
||||
}
|
||||
}
|
||||
|
||||
let classes = xclass(rest.className, "clear", ...classesAr)
|
||||
|
||||
return (
|
||||
<section {...rest} style={{display: hide ? "none": null}} className={classes}/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Col.propTypes = {
|
||||
hide: PropTypes.bool,
|
||||
keepContents: PropTypes.bool,
|
||||
mobile: PropTypes.number,
|
||||
tablet: PropTypes.number,
|
||||
desktop: PropTypes.number,
|
||||
large: PropTypes.number,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
export class Row extends React.Component {
|
||||
|
||||
render() {
|
||||
return <div {...this.props} className={xclass(this.props.className, "wrapper")} />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row.propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
export class Button extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
className: ""
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button {...this.props} className={xclass(this.props.className, "button")} />
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const TextArea = (props) => <textarea {...props} />
|
||||
|
||||
export const Input = (props) => <input {...props} />
|
||||
|
||||
export class Select extends React.Component {
|
||||
static propTypes = {
|
||||
allowedValues: PropTypes.object,
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
multiple: PropTypes.bool,
|
||||
allowEmptyValue: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
multiple: false,
|
||||
allowEmptyValue: true
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let value
|
||||
|
||||
if (props.value !== undefined) {
|
||||
value = props.value
|
||||
} else {
|
||||
value = props.multiple ? [""] : ""
|
||||
}
|
||||
|
||||
this.state = { value: value }
|
||||
}
|
||||
|
||||
onChange = (e) => {
|
||||
let { onChange, multiple } = this.props
|
||||
let options = [].slice.call(e.target.options)
|
||||
let value
|
||||
|
||||
|
||||
if (multiple) {
|
||||
value = options.filter(function (option) {
|
||||
return option.selected
|
||||
})
|
||||
.map(function (option){
|
||||
return option.value
|
||||
})
|
||||
} else {
|
||||
value = e.target.value
|
||||
}
|
||||
|
||||
this.setState({value: value})
|
||||
|
||||
onChange && onChange(value)
|
||||
}
|
||||
|
||||
render(){
|
||||
let { allowedValues, multiple, allowEmptyValue } = this.props
|
||||
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
|
||||
|
||||
return (
|
||||
<select multiple={ multiple } value={ value } onChange={ this.onChange } >
|
||||
{ allowEmptyValue ? <option value="">--</option> : null }
|
||||
{
|
||||
allowedValues.map(function (item, key) {
|
||||
return <option key={ key } value={ String(item) }>{ item }</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Link extends React.Component {
|
||||
|
||||
render() {
|
||||
return <a {...this.props} className={xclass(this.props.className, "link")}/>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Link.propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
const NoMargin = ({children}) => <div style={{height: "auto", border: "none", margin: 0, padding: 0}}> {children} </div>
|
||||
|
||||
NoMargin.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export class Collapse extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
isOpened: PropTypes.bool,
|
||||
children: PropTypes.node.isRequired,
|
||||
animated: PropTypes.bool
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
isOpened: false,
|
||||
animated: false
|
||||
}
|
||||
|
||||
renderNotAnimated() {
|
||||
if(!this.props.isOpened)
|
||||
return <noscript/>
|
||||
return (
|
||||
<NoMargin>
|
||||
{this.props.children}
|
||||
</NoMargin>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { animated, isOpened, children } = this.props
|
||||
|
||||
if(!animated)
|
||||
return this.renderNotAnimated()
|
||||
|
||||
children = isOpened ? children : null
|
||||
return (
|
||||
<OriCollapse isOpened={isOpened}>
|
||||
<NoMargin>
|
||||
{children}
|
||||
</NoMargin>
|
||||
</OriCollapse>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
72
src/core/components/layouts/xpane.jsx
Normal file
72
src/core/components/layouts/xpane.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class XPane extends React.Component {
|
||||
|
||||
render() {
|
||||
let { getComponent, specSelectors, specActions, layoutSelectors, layoutActions } = this.props
|
||||
let info = specSelectors.info()
|
||||
let url = specSelectors.url()
|
||||
let showEditor = layoutSelectors.isShown("editor")
|
||||
|
||||
let Info = getComponent("info")
|
||||
let Operations = getComponent("operations", true)
|
||||
let Overview = getComponent("overview", true)
|
||||
let Editor = getComponent("editor", true)
|
||||
let Footer = getComponent("footer", true)
|
||||
let Header = getComponent("header", true)
|
||||
|
||||
let Container = getComponent("Container")
|
||||
let Row = getComponent("Row")
|
||||
let Col = getComponent("Col")
|
||||
let Button = getComponent("Button")
|
||||
|
||||
let showEditorAction = ()=> layoutActions.show("editor", !showEditor)
|
||||
|
||||
return (
|
||||
<Container fullscreen>
|
||||
|
||||
<Header/>
|
||||
|
||||
{
|
||||
info && info.size ? <Info version={info.get("version")}
|
||||
description={info.get("description")}
|
||||
title={info.get("title")}
|
||||
url={url}/>
|
||||
: null
|
||||
}
|
||||
<Button onClick={showEditorAction}>{showEditor ? "Hide" : "Show"} Editor</Button>
|
||||
<Button onClick={specActions.formatIntoYaml}>Format contents</Button>
|
||||
|
||||
<Row>
|
||||
|
||||
<Col desktop={3} >
|
||||
<Overview/>
|
||||
</Col>
|
||||
|
||||
<Col hide={!showEditor} keepContents={true} desktop={5} >
|
||||
<Editor/>
|
||||
</Col>
|
||||
|
||||
<Col desktop={showEditor ? 4 : 9} >
|
||||
<Operations/>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
|
||||
<Footer></Footer>
|
||||
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
XPane.propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
|
||||
91
src/core/components/live-response.jsx
Normal file
91
src/core/components/live-response.jsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
const Headers = ( { headers } )=>{
|
||||
return (
|
||||
<div>
|
||||
<h5>Response headers</h5>
|
||||
<pre>{headers}</pre>
|
||||
</div>)
|
||||
}
|
||||
|
||||
Headers.propTypes = {
|
||||
headers: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
export default class LiveResponse extends React.Component {
|
||||
static propTypes = {
|
||||
response: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
let { request, response, getComponent } = this.props
|
||||
const Curl = getComponent("curl")
|
||||
let body = response.get("text")
|
||||
let status = response.get("status")
|
||||
let url = response.get("url")
|
||||
let originalHeaders = response.get("headers")
|
||||
let headers = originalHeaders && originalHeaders.toJS()
|
||||
let headersKeys = Object.keys(headers)
|
||||
let returnObject = headersKeys.map(key => {
|
||||
return <span className="headerline" key={key}> {key}: {headers[key]} </span>
|
||||
})
|
||||
let notDocumented = response.get("notDocumented")
|
||||
let ResponseBody = getComponent("responseBody")
|
||||
let contentType = headers && headers["content-type"]
|
||||
let isError = response.get("error")
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ request && <Curl request={ request }/> }
|
||||
<h4>Server response</h4>
|
||||
<table className="responses-table">
|
||||
<thead>
|
||||
<tr className="responses-header">
|
||||
<td className="col col_header response-col_status">Code</td>
|
||||
<td className="col col_header response-col_description">Details</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="response">
|
||||
<td className="col response-col_status">
|
||||
{ status }
|
||||
{
|
||||
!notDocumented ? null :
|
||||
<div className="response-undocumented">
|
||||
<i> Undocumented </i>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td className="col response-col_description">
|
||||
{
|
||||
!isError ? null : <span>
|
||||
{`${response.get("name")}: ${response.get("message")}`}
|
||||
</span>
|
||||
}
|
||||
{
|
||||
!body || isError ? null
|
||||
: <ResponseBody content={ body }
|
||||
contentType={ contentType }
|
||||
url={ url }
|
||||
headers={ headers }
|
||||
getComponent={ getComponent }/>
|
||||
}
|
||||
{
|
||||
!headers ? null : <Headers headers={ returnObject }/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
request: ImPropTypes.map,
|
||||
response: ImPropTypes.map
|
||||
}
|
||||
}
|
||||
58
src/core/components/model-example.jsx
Normal file
58
src/core/components/model-example.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
|
||||
export default class ModelExample extends React.Component {
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
example: PropTypes.any.isRequired,
|
||||
isExecute: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
activeTab: "example"
|
||||
}
|
||||
}
|
||||
|
||||
activeTab =( e ) => {
|
||||
let { target : { dataset : { name } } } = e
|
||||
|
||||
this.setState({
|
||||
activeTab: name
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, specSelectors, schema, example, isExecute } = this.props
|
||||
const Model = getComponent("model")
|
||||
|
||||
return <div>
|
||||
<ul className="tab">
|
||||
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
|
||||
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
|
||||
</li>
|
||||
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
|
||||
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
{
|
||||
(isExecute || this.state.activeTab === "example") && example
|
||||
}
|
||||
{
|
||||
!isExecute && this.state.activeTab === "model" && <Model schema={ schema }
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
expandDepth={ 1 } />
|
||||
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
300
src/core/components/model.jsx
Normal file
300
src/core/components/model.jsx
Normal file
@@ -0,0 +1,300 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import isObject from "lodash/isObject"
|
||||
import { List } from "immutable"
|
||||
const braceOpen = "{"
|
||||
const braceClose = "}"
|
||||
|
||||
const EnumModel = ({ value }) => {
|
||||
let collapsedContent = <span>Array [ { value.count() } ]</span>
|
||||
return <span className="prop-enum">
|
||||
Enum:<br />
|
||||
<Collapse collapsedContent={ collapsedContent }>
|
||||
[ { value.join(", ") } ]
|
||||
</Collapse>
|
||||
</span>
|
||||
}
|
||||
|
||||
EnumModel.propTypes = {
|
||||
value: ImPropTypes.iterable
|
||||
}
|
||||
|
||||
class ObjectModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, name, isRef, getComponent, depth, ...props } = this.props
|
||||
let { expandDepth } = this.props
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
let description = schema.get("description")
|
||||
let properties = schema.get("properties")
|
||||
let additionalProperties = schema.get("additionalProperties")
|
||||
let title = schema.get("title") || name
|
||||
let required = schema.get("required")
|
||||
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
|
||||
let collapsedContent = (<span>
|
||||
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
|
||||
{
|
||||
isRef ? <JumpToPathSection name={ name }/> : ""
|
||||
}
|
||||
</span>)
|
||||
|
||||
return <span className="model">
|
||||
{
|
||||
title && <span className="model-title">
|
||||
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
}
|
||||
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
|
||||
<span className="brace-open object">{ braceOpen }</span>
|
||||
{
|
||||
!isRef ? null : <JumpToPathSection name={ name }/>
|
||||
}
|
||||
<span className="inner-object">
|
||||
{
|
||||
<table className="model" style={{ marginLeft: "2em" }}><tbody>
|
||||
{
|
||||
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
|
||||
<td>description:</td>
|
||||
<td>{ description }</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!(properties && properties.size) ? null : properties.entrySeq().map(
|
||||
([key, value]) => {
|
||||
let isRequired = List.isList(required) && required.contains(key)
|
||||
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
|
||||
if ( isRequired ) {
|
||||
propertyStyle.fontWeight = "bold"
|
||||
}
|
||||
|
||||
return (<tr key={key}>
|
||||
<td style={ propertyStyle }>{ key }:</td>
|
||||
<td style={{ verticalAlign: "top" }}>
|
||||
<Model key={ `object-${name}-${key}_${value}` } { ...props }
|
||||
required={ isRequired }
|
||||
getComponent={ getComponent }
|
||||
schema={ value }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
{
|
||||
!additionalProperties || !additionalProperties.size ? null
|
||||
: <tr>
|
||||
<td>{ "< * >:" }</td>
|
||||
<td>
|
||||
<Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ additionalProperties }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
}
|
||||
</span>
|
||||
<span className="brace-close">{ braceClose }</span>
|
||||
</Collapse>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class Primitive extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
required: PropTypes.bool
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, required } = this.props
|
||||
|
||||
if(!schema || !schema.get) {
|
||||
// don't render if schema isn't correctly formed
|
||||
return <div></div>
|
||||
}
|
||||
|
||||
let type = schema.get("type")
|
||||
let format = schema.get("format")
|
||||
let xml = schema.get("xml")
|
||||
let enumArray = schema.get("enum")
|
||||
let description = schema.get("description")
|
||||
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "$$ref"].indexOf(key) === -1 )
|
||||
let style = required ? { fontWeight: "bold" } : {}
|
||||
let propStyle = { color: "#999", fontStyle: "italic" }
|
||||
|
||||
return <span className="prop">
|
||||
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
|
||||
{ format && <span className="prop-format">(${format})</span>}
|
||||
{
|
||||
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
|
||||
<br />{ key !== "description" && key + ": " }{ String(v) }</span>)
|
||||
: null
|
||||
}
|
||||
{
|
||||
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
|
||||
{
|
||||
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/> {key}: { String(v) }</span>).toArray()
|
||||
}
|
||||
</span>): null
|
||||
}
|
||||
{
|
||||
enumArray && <EnumModel value={ enumArray } />
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { required, schema, depth, expandDepth } = this.props
|
||||
let items = schema.get("items")
|
||||
|
||||
return <span>
|
||||
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
|
||||
[
|
||||
<span><Model { ...this.props } schema={ items } required={ false }/></span>
|
||||
]
|
||||
</Collapse>
|
||||
{ required && <span style={{ color: "red" }}>*</span>}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Model extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
getModelName =( ref )=> {
|
||||
if ( ref.indexOf("#/definitions/") !== -1 ) {
|
||||
return ref.replace(/^.*#\/definitions\//, "")
|
||||
}
|
||||
}
|
||||
|
||||
getRefSchema =( model )=> {
|
||||
let { specSelectors } = this.props
|
||||
|
||||
return specSelectors.findDefinition(model)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { schema, required, name, isRef } = this.props
|
||||
let $$ref = schema && schema.get("$$ref")
|
||||
let modelName = $$ref && this.getModelName( $$ref )
|
||||
let modelSchema, type
|
||||
|
||||
if ( schema && (schema.get("type") || schema.get("properties")) ) {
|
||||
modelSchema = schema
|
||||
} else if ( $$ref ) {
|
||||
modelSchema = this.getRefSchema( modelName )
|
||||
}
|
||||
|
||||
type = modelSchema && modelSchema.get("type")
|
||||
if ( !type && modelSchema && modelSchema.get("properties") ) {
|
||||
type = "object"
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case "object":
|
||||
return <ObjectModel className="object" { ...this.props } schema={ modelSchema }
|
||||
name={ modelName || name }
|
||||
isRef={ isRef!== undefined ? isRef : !!$$ref }/>
|
||||
case "array":
|
||||
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } />
|
||||
case "string":
|
||||
case "number":
|
||||
case "integer":
|
||||
case "boolean":
|
||||
default:
|
||||
return <Primitive schema={ modelSchema } required={ required }/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class ModelComponent extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
expandDepth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { name, schema } = this.props
|
||||
let title = schema.get("title") || name
|
||||
|
||||
return <div className="model-box">
|
||||
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class Collapse extends Component {
|
||||
static propTypes = {
|
||||
collapsedContent: PropTypes.any,
|
||||
collapsed: PropTypes.bool,
|
||||
children: PropTypes.any
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
collapsedContent: "{...}",
|
||||
collapsed: true,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let { collapsed, collapsedContent } = this.props
|
||||
|
||||
this.state = {
|
||||
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
|
||||
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
|
||||
}
|
||||
}
|
||||
|
||||
toggleCollapsed=()=>{
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (<span>
|
||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
|
||||
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
|
||||
</span>
|
||||
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
|
||||
</span>)
|
||||
}
|
||||
}
|
||||
42
src/core/components/models.jsx
Normal file
42
src/core/components/models.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
|
||||
|
||||
export default class Models extends Component {
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func,
|
||||
specSelectors: PropTypes.object
|
||||
}
|
||||
|
||||
render(){
|
||||
let { specSelectors, getComponent, layoutSelectors, layoutActions } = this.props
|
||||
let definitions = specSelectors.definitions()
|
||||
let showModels = layoutSelectors.isShown('models', true)
|
||||
|
||||
const Model = getComponent("model")
|
||||
const Collapse = getComponent("Collapse")
|
||||
|
||||
if (!definitions.size) return null
|
||||
|
||||
return <section className={ showModels ? "models is-open" : "models"}>
|
||||
<h4 onClick={() => layoutActions.show('models', !showModels)}>
|
||||
<span>Models</span>
|
||||
<svg width="20" height="20">
|
||||
<use xlinkHref="#large-arrow" />
|
||||
</svg>
|
||||
</h4>
|
||||
<Collapse isOpened={showModels} animated>
|
||||
{
|
||||
definitions.entrySeq().map( ( [ name, model ])=>{
|
||||
return <div className="model-container" key={ `models-section-${name}` }>
|
||||
<Model name={ name }
|
||||
schema={ model }
|
||||
isRef={ true }
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }/>
|
||||
</div>
|
||||
}).toArray()
|
||||
}
|
||||
</Collapse>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
40
src/core/components/online-validator-badge.jsx
Normal file
40
src/core/components/online-validator-badge.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react"
|
||||
|
||||
export default class OnlineValidatorBadge extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
let { specSelectors, getConfigs } = props
|
||||
let { validatorUrl } = getConfigs()
|
||||
this.state = {
|
||||
url: specSelectors.url(),
|
||||
validatorUrl: validatorUrl
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let { specSelectors, getConfigs } = nextProps
|
||||
let { validatorUrl } = getConfigs()
|
||||
|
||||
this.setState({
|
||||
url: specSelectors.url(),
|
||||
validatorUrl: validatorUrl
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getConfigs } = this.props
|
||||
let { spec } = getConfigs()
|
||||
|
||||
if ( typeof spec === "object" && Object.keys(spec).length) return null
|
||||
|
||||
if (!this.state.url) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (<span style={{ float: "right"}}>
|
||||
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}>
|
||||
<img alt="Online validator badge" src={`${ this.state.validatorUrl }?url=${ this.state.url }`} />
|
||||
</a>
|
||||
</span>)
|
||||
}
|
||||
}
|
||||
258
src/core/components/operation.jsx
Normal file
258
src/core/components/operation.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { Map, fromJS } from "immutable"
|
||||
import shallowCompare from "react-addons-shallow-compare"
|
||||
import { getList } from "core/utils"
|
||||
import * as CustomPropTypes from "core/proptypes"
|
||||
|
||||
//import "less/opblock"
|
||||
|
||||
export default class Operation extends React.Component {
|
||||
static propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
operation: PropTypes.object.isRequired,
|
||||
showSummary: PropTypes.bool,
|
||||
|
||||
isShownKey: CustomPropTypes.arrayOrString.isRequired,
|
||||
jumpToKey: CustomPropTypes.arrayOrString.isRequired,
|
||||
|
||||
allowTryItOut: PropTypes.bool,
|
||||
|
||||
response: PropTypes.object,
|
||||
request: PropTypes.object,
|
||||
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
authActions: PropTypes.object,
|
||||
authSelectors: PropTypes.object,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
showSummary: true,
|
||||
response: null,
|
||||
allowTryItOut: true,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
tryItOutEnabled: false
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const defaultContentType = "application/json"
|
||||
let { specActions, path, method, operation } = nextProps
|
||||
let producesValue = operation.get("produces_value")
|
||||
let produces = operation.get("produces")
|
||||
let consumes = operation.get("consumes")
|
||||
let consumesValue = operation.get("consumes_value")
|
||||
|
||||
if(nextProps.response !== this.props.response) {
|
||||
this.setState({ executeInProgress: false })
|
||||
}
|
||||
|
||||
if (producesValue === undefined) {
|
||||
producesValue = produces && produces.size ? produces.first() : defaultContentType
|
||||
specActions.changeProducesValue([path, method], producesValue)
|
||||
}
|
||||
|
||||
if (consumesValue === undefined) {
|
||||
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
|
||||
specActions.changeConsumesValue([path, method], consumesValue)
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return shallowCompare(this, props, state)
|
||||
}
|
||||
|
||||
toggleShown =() => {
|
||||
let { layoutActions, isShownKey } = this.props
|
||||
layoutActions.show(isShownKey, !this.isShown())
|
||||
}
|
||||
|
||||
isShown =() => {
|
||||
let { layoutSelectors, isShownKey } = this.props
|
||||
return layoutSelectors.isShown(isShownKey, false ) // Here is where we set the default
|
||||
}
|
||||
|
||||
onTryoutClick =() => {
|
||||
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
|
||||
}
|
||||
|
||||
onCancelClick =() => {
|
||||
let { specActions, path, method } = this.props
|
||||
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
|
||||
specActions.clearValidateParams([path, method])
|
||||
}
|
||||
|
||||
onExecute = () => {
|
||||
this.setState({ executeInProgress: true })
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
isShownKey,
|
||||
jumpToKey,
|
||||
path,
|
||||
method,
|
||||
operation,
|
||||
showSummary,
|
||||
response,
|
||||
request,
|
||||
allowTryItOut,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
specActions,
|
||||
specSelectors,
|
||||
authActions,
|
||||
authSelectors,
|
||||
layoutSelectors,
|
||||
layoutActions,
|
||||
} = this.props
|
||||
|
||||
let summary = operation.get("summary")
|
||||
let description = operation.get("description")
|
||||
let deprecated = operation.get("deprecated")
|
||||
let externalDocs = operation.get("externalDocs")
|
||||
let responses = operation.get("responses")
|
||||
let security = operation.get("security") || specSelectors.security()
|
||||
let produces = operation.get("produces")
|
||||
let schemes = operation.get("schemes")
|
||||
let parameters = getList(operation, ["parameters"])
|
||||
|
||||
const Responses = getComponent("responses")
|
||||
const Parameters = getComponent( "parameters" )
|
||||
const Execute = getComponent( "execute" )
|
||||
const Clear = getComponent( "clear" )
|
||||
const AuthorizeOperationBtn = getComponent( "authorizeOperationBtn" )
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const Collapse = getComponent( "Collapse" )
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
const Schemes = getComponent( "schemes" )
|
||||
|
||||
// Merge in Live Response
|
||||
if(response && response.size > 0) {
|
||||
let notDocumented = !responses.get(String(response.get("status")))
|
||||
response = response.set("notDocumented", notDocumented)
|
||||
}
|
||||
|
||||
let { tryItOutEnabled } = this.state
|
||||
let shown = this.isShown()
|
||||
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
|
||||
|
||||
return (
|
||||
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
|
||||
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
|
||||
<span className="opblock-summary-method">{method.toUpperCase()}</span>
|
||||
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
|
||||
<span>{path}</span>
|
||||
<JumpToPath path={jumpToKey} />
|
||||
</span>
|
||||
|
||||
{ !showSummary ? null :
|
||||
<div className="opblock-summary-description">
|
||||
{ summary }
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
(!security || !security.count()) ? null :
|
||||
<AuthorizeOperationBtn authActions={ authActions }
|
||||
security={ security }
|
||||
authSelectors={ authSelectors }/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<Collapse isOpened={shown} animated>
|
||||
<div className="opblock-body">
|
||||
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
|
||||
{ description &&
|
||||
<div className="opblock-description-wrapper">
|
||||
<div className="opblock-description">
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
externalDocs && externalDocs.get("url") ?
|
||||
<div className="opblock-external-docs-wrapper">
|
||||
<h4 className="opblock-title_normal">Find more details</h4>
|
||||
<div className="opblock-external-docs">
|
||||
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
|
||||
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
|
||||
</div>
|
||||
</div> : null
|
||||
}
|
||||
<Parameters
|
||||
parameters={parameters}
|
||||
onChangeKey={onChangeKey}
|
||||
onTryoutClick = { this.onTryoutClick }
|
||||
onCancelClick = { this.onCancelClick }
|
||||
tryItOutEnabled = { tryItOutEnabled }
|
||||
allowTryItOut={allowTryItOut}
|
||||
|
||||
fn={fn}
|
||||
getComponent={ getComponent }
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ [path, method] }
|
||||
/>
|
||||
|
||||
{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <Schemes schemes={ schemes }
|
||||
path={ path }
|
||||
method={ method }
|
||||
specActions={ specActions }/>
|
||||
: null
|
||||
}
|
||||
|
||||
<div className={(!tryItOutEnabled || !response || !allowTryItOut) ? "execute-wrapper" : "btn-group"}>
|
||||
{ !tryItOutEnabled || !allowTryItOut ? null :
|
||||
|
||||
<Execute
|
||||
getComponent={getComponent}
|
||||
operation={ operation }
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
path={ path }
|
||||
method={ method }
|
||||
onExecute={ this.onExecute } />
|
||||
}
|
||||
|
||||
{ (!tryItOutEnabled || !response || !allowTryItOut) ? null :
|
||||
<Clear
|
||||
onClick={ this.onClearClick }
|
||||
specActions={ specActions }
|
||||
path={ path }
|
||||
method={ method }/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{this.state.executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null}
|
||||
|
||||
{ !responses ? null :
|
||||
<Responses
|
||||
responses={ responses }
|
||||
request={ request }
|
||||
tryItOutResponse={ response }
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
specActions={ specActions }
|
||||
produces={ produces }
|
||||
producesValue={ operation.get("produces_value") }
|
||||
pathMethod={ [path, method] }
|
||||
fn={fn} />
|
||||
}
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
133
src/core/components/operations.jsx
Normal file
133
src/core/components/operations.jsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import {presets} from "react-motion"
|
||||
|
||||
export default class Operations extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
authActions: PropTypes.object.isRequired,
|
||||
authSelectors: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
specSelectors,
|
||||
specActions,
|
||||
getComponent,
|
||||
layoutSelectors,
|
||||
layoutActions,
|
||||
authActions,
|
||||
authSelectors,
|
||||
fn
|
||||
} = this.props
|
||||
|
||||
let taggedOps = specSelectors.taggedOperations()
|
||||
|
||||
const Operation = getComponent("operation")
|
||||
const Collapse = getComponent("Collapse")
|
||||
const Schemes = getComponent("schemes")
|
||||
|
||||
let showSummary = layoutSelectors.showSummary()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
taggedOps.map( (tagObj, tag) => {
|
||||
let operations = tagObj.get("operations")
|
||||
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
|
||||
|
||||
let isShownKey = ["operations-tag", tag]
|
||||
let showTag = layoutSelectors.isShown(isShownKey, true)
|
||||
|
||||
return (
|
||||
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
|
||||
|
||||
<h4 className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
|
||||
<span onClick={() => layoutActions.show(isShownKey, !showTag)}>{tag}</span>
|
||||
|
||||
{ !tagDescription ? null :
|
||||
<small onClick={() => layoutActions.show(isShownKey, !showTag)} >
|
||||
{ tagDescription }
|
||||
</small>
|
||||
}
|
||||
|
||||
<button className="expand-methods" title="Expand all methods">
|
||||
<svg className="expand" width="20" height="20">
|
||||
<use xlinkHref="#expand" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
|
||||
<svg className="arrow" width="20" height="20">
|
||||
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
|
||||
</svg>
|
||||
</button>
|
||||
</h4>
|
||||
|
||||
<Collapse isOpened={showTag}>
|
||||
{
|
||||
operations.map( op => {
|
||||
|
||||
const isShownKey = ["operations", op.get("id"), tag]
|
||||
const path = op.get("path", "")
|
||||
const method = op.get("method", "")
|
||||
const jumpToKey = `paths.${path}.${method}`
|
||||
|
||||
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
|
||||
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
|
||||
const request = specSelectors.requestFor(op.get("path"), op.get("method"))
|
||||
|
||||
return <Operation
|
||||
{...op.toObject()}
|
||||
|
||||
isShownKey={isShownKey}
|
||||
jumpToKey={jumpToKey}
|
||||
showSummary={showSummary}
|
||||
key={isShownKey}
|
||||
response={ response }
|
||||
request={ request }
|
||||
allowTryItOut={allowTryItOut}
|
||||
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
|
||||
layoutActions={ layoutActions }
|
||||
layoutSelectors={ layoutSelectors }
|
||||
|
||||
authActions={ authActions }
|
||||
authSelectors={ authSelectors }
|
||||
|
||||
getComponent={ getComponent }
|
||||
fn={fn}
|
||||
/>
|
||||
}).toArray()
|
||||
}
|
||||
</Collapse>
|
||||
</div>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
{ taggedOps.size < 1 ? <h3> No operations defined in spec! </h3> : null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Operations.propTypes = {
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
}
|
||||
119
src/core/components/overview.jsx
Normal file
119
src/core/components/overview.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { Link } from "core/components/layout-utils"
|
||||
|
||||
export default class Overview extends React.Component {
|
||||
|
||||
constructor(...args) {
|
||||
super(...args)
|
||||
this.setTagShown = this._setTagShown.bind(this)
|
||||
}
|
||||
|
||||
_setTagShown(showTagId, shown) {
|
||||
this.props.layoutActions.show(showTagId, shown)
|
||||
}
|
||||
|
||||
showOp(key, shown) {
|
||||
let { layoutActions } = this.props
|
||||
layoutActions.show(key, shown)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { specSelectors, layoutSelectors, layoutActions, getComponent } = this.props
|
||||
let taggedOps = specSelectors.taggedOperations()
|
||||
|
||||
const Collapse = getComponent("Collapse")
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="overview-title">Overview</h4>
|
||||
|
||||
{
|
||||
taggedOps.map( (tagObj, tag) => {
|
||||
let operations = tagObj.get("operations")
|
||||
let tagDetails = tagObj.get("tagDetails")
|
||||
|
||||
let showTagId = ["overview-tags", tag]
|
||||
let showTag = layoutSelectors.isShown(showTagId, true)
|
||||
let toggleShow = ()=> layoutActions.show(showTagId, !showTag)
|
||||
|
||||
return (
|
||||
<div key={"overview-"+tag}>
|
||||
|
||||
|
||||
<h4 onClick={toggleShow} className="link overview-tag"> {showTag ? "-" : "+"}{tag}</h4>
|
||||
|
||||
<Collapse isOpened={showTag} animated>
|
||||
{
|
||||
operations.map( op => {
|
||||
let { path, method, operation, id } = op.toObject() // toObject is shallow
|
||||
let showOpIdPrefix = "operations"
|
||||
let showOpId = id
|
||||
let shown = layoutSelectors.isShown([showOpIdPrefix, showOpId])
|
||||
return <OperationLink key={id}
|
||||
path={path}
|
||||
method={method}
|
||||
id={path + "-" + method}
|
||||
shown={shown}
|
||||
showOpId={showOpId}
|
||||
showOpIdPrefix={showOpIdPrefix}
|
||||
href={`#operation-${showOpId}`}
|
||||
onClick={layoutActions.show} />
|
||||
}).toArray()
|
||||
}
|
||||
</Collapse>
|
||||
|
||||
</div>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
{ taggedOps.size < 1 && <h3> No operations defined in spec! </h3> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Overview.propTypes = {
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export class OperationLink extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.onClick = this._onClick.bind(this)
|
||||
}
|
||||
|
||||
_onClick() {
|
||||
let { showOpId, showOpIdPrefix, onClick, shown } = this.props
|
||||
onClick([showOpIdPrefix, showOpId], !shown)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { id, method, shown, href } = this.props
|
||||
|
||||
return (
|
||||
<Link href={ href } style={{fontWeight: shown ? "bold" : "normal"}} onClick={this.onClick} className="block opblock-link">
|
||||
<div>
|
||||
<small className={`bold-label-${method}`}>{method.toUpperCase()}</small>
|
||||
<span className="bold-label" >{id}</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OperationLink.propTypes = {
|
||||
href: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
id: PropTypes.string.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
shown: PropTypes.bool.isRequired,
|
||||
showOpId: PropTypes.string.isRequired,
|
||||
showOpIdPrefix: PropTypes.string.isRequired
|
||||
}
|
||||
141
src/core/components/param-body.jsx
Normal file
141
src/core/components/param-body.jsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import shallowCompare from "react-addons-shallow-compare"
|
||||
import { Set, fromJS, List } from "immutable"
|
||||
import { getSampleSchema } from "core/utils"
|
||||
|
||||
const NOOP = Function.prototype
|
||||
|
||||
export default class ParamBody extends Component {
|
||||
|
||||
static propTypes = {
|
||||
param: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
onChangeConsumes: PropTypes.func,
|
||||
consumes: PropTypes.object,
|
||||
consumesValue: PropTypes.string,
|
||||
fn: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
static defaultProp = {
|
||||
consumes: fromJS(["application/json"]),
|
||||
param: fromJS({}),
|
||||
onChange: NOOP,
|
||||
onChangeConsumes: NOOP,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
isEditBox: false,
|
||||
value: ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateValues.call(this, this.props)
|
||||
}
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return shallowCompare(this, props, state)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.updateValues.call(this, nextProps)
|
||||
}
|
||||
|
||||
updateValues = (props) => {
|
||||
let { specSelectors, pathMethod, param, isExecute, consumesValue="", onChangeConsumes } = props
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
|
||||
let isXml = /xml/i.test(consumesValue)
|
||||
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
|
||||
|
||||
if ( paramValue ) {
|
||||
this.setState({ value: paramValue })
|
||||
this.onChange(paramValue, {isXml: isXml, isEditBox: isExecute})
|
||||
} else {
|
||||
if (isXml) {
|
||||
this.onChange(this.sample("xml"), {isXml: isXml, isEditBox: isExecute})
|
||||
} else {
|
||||
this.onChange(this.sample(), {isEditBox: isExecute})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sample = (xml) => {
|
||||
let { param, fn:{inferSchema} } = this.props
|
||||
let schema = inferSchema(param.toJS())
|
||||
|
||||
return getSampleSchema(schema, xml)
|
||||
}
|
||||
|
||||
onChange = (value, { isEditBox, isXml }) => {
|
||||
this.setState({value, isEditBox})
|
||||
this._onChange(value, isXml)
|
||||
}
|
||||
|
||||
_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }
|
||||
|
||||
handleOnChange = e => {
|
||||
let {consumesValue} = this.props
|
||||
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)})
|
||||
}
|
||||
|
||||
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
|
||||
|
||||
render() {
|
||||
let {
|
||||
onChangeConsumes,
|
||||
param,
|
||||
isExecute,
|
||||
specSelectors,
|
||||
pathMethod,
|
||||
|
||||
getComponent,
|
||||
} = this.props
|
||||
|
||||
const Button = getComponent("Button")
|
||||
const TextArea = getComponent("TextArea")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const ContentType = getComponent("contentType")
|
||||
// for domains where specSelectors not passed
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param
|
||||
let errors = parameter.get("errors", List())
|
||||
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
|
||||
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes
|
||||
|
||||
let { value, isEditBox } = this.state
|
||||
|
||||
return (
|
||||
<div className="body-param">
|
||||
{
|
||||
isEditBox && isExecute
|
||||
? <TextArea className={ "body-param__text" + ( errors.count() ? " invalid" : "")} value={value} onChange={ this.handleOnChange }/>
|
||||
: (value && <HighlightCode className="body-param__example"
|
||||
value={ value }/>)
|
||||
}
|
||||
<div className="body-param-options">
|
||||
{
|
||||
!isExecute ? null
|
||||
: <div className="body-param-edit">
|
||||
<Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
|
||||
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<label htmlFor="">
|
||||
<span>Parameter content type</span>
|
||||
<ContentType value={ consumesValue } contentTypes={ consumes } onChange={onChangeConsumes} className="body-param-content-type" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
119
src/core/components/parameter-row.jsx
Normal file
119
src/core/components/parameter-row.jsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import win from "core/window"
|
||||
|
||||
|
||||
export default class ParameterRow extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
param: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
onChangeConsumes: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let { specSelectors, pathMethod, param } = props
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
if ( defaultValue !== undefined && value === undefined ) {
|
||||
this.onChangeWrapper(defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
let { specSelectors, pathMethod, param } = props
|
||||
let defaultValue = param.get("default")
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
if ( defaultValue !== undefined && value === undefined ) {
|
||||
this.onChangeWrapper(defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
onChangeWrapper = (value) => {
|
||||
let { onChange, param } = this.props
|
||||
return onChange(param, value)
|
||||
}
|
||||
|
||||
render() {
|
||||
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
|
||||
|
||||
// const onChangeWrapper = (value) => onChange(param, value)
|
||||
const JsonSchemaForm = getComponent("JsonSchemaForm")
|
||||
const ParamBody = getComponent("ParamBody")
|
||||
let inType = param.get("in")
|
||||
let bodyParam = inType !== "body" ? null
|
||||
: <ParamBody getComponent={getComponent}
|
||||
fn={fn}
|
||||
param={param}
|
||||
consumes={ specSelectors.operationConsumes(pathMethod) }
|
||||
consumesValue={ specSelectors.contentTypeValues(pathMethod).get("requestContentType") }
|
||||
onChange={onChange}
|
||||
onChangeConsumes={onChangeConsumes}
|
||||
isExecute={ isExecute }
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ pathMethod }
|
||||
/>
|
||||
|
||||
const ModelExample = getComponent("modelExample")
|
||||
const Markdown = getComponent("Markdown")
|
||||
|
||||
let schema = param.get("schema")
|
||||
|
||||
let isFormData = inType === "formData"
|
||||
let isFormDataSupported = "FormData" in win
|
||||
let required = param.get("required")
|
||||
let itemType = param.getIn(["items", "type"])
|
||||
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
|
||||
let value = parameter ? parameter.get("value") : ""
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className="col parameters-col_name">
|
||||
<div className={required ? "parameter__name required" : "parameter__name"}>
|
||||
{ param.get("name") }
|
||||
{ !required ? null : <span style={{color: "red"}}> *</span> }
|
||||
</div>
|
||||
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
|
||||
<div className="parameter__in">({ param.get("in") })</div>
|
||||
</td>
|
||||
|
||||
<td className="col parameters-col_description">
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={ param.get("description") }/>
|
||||
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
|
||||
|
||||
{ bodyParam || !isExecute ? null
|
||||
: <JsonSchemaForm fn={fn}
|
||||
getComponent={getComponent}
|
||||
value={ value }
|
||||
required={ required }
|
||||
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
|
||||
onChange={ this.onChangeWrapper }
|
||||
schema={ param }/>
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
bodyParam && schema ? <ModelExample getComponent={ getComponent }
|
||||
isExecute={ isExecute }
|
||||
specSelectors={ specSelectors }
|
||||
schema={ schema }
|
||||
example={ bodyParam }/>
|
||||
: null
|
||||
}
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
109
src/core/components/parameters.jsx
Normal file
109
src/core/components/parameters.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import Im, { fromJS } from "immutable"
|
||||
|
||||
// More readable, just iterate over maps, only
|
||||
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
|
||||
|
||||
export default class Parameters extends Component {
|
||||
|
||||
static propTypes = {
|
||||
parameters: ImPropTypes.list.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
tryItOutEnabled: PropTypes.bool,
|
||||
allowTryItOut: PropTypes.bool,
|
||||
onTryoutClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
onChangeKey: PropTypes.array,
|
||||
pathMethod: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
|
||||
static defaultProps = {
|
||||
onTryoutClick: Function.prototype,
|
||||
onCancelClick: Function.prototype,
|
||||
tryItOutEnabled: false,
|
||||
allowTryItOut: true,
|
||||
onChangeKey: [],
|
||||
}
|
||||
|
||||
onChange = ( param, value, isXml ) => {
|
||||
let {
|
||||
specActions: { changeParam },
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParam( onChangeKey, param.get("name"), value, isXml)
|
||||
}
|
||||
|
||||
onChangeConsumesWrapper = ( val ) => {
|
||||
let {
|
||||
specActions: { changeConsumesValue },
|
||||
onChangeKey
|
||||
} = this.props
|
||||
|
||||
changeConsumesValue(onChangeKey, val)
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
let {
|
||||
onTryoutClick,
|
||||
onCancelClick,
|
||||
parameters,
|
||||
allowTryItOut,
|
||||
tryItOutEnabled,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
specSelectors,
|
||||
pathMethod
|
||||
} = this.props
|
||||
|
||||
const ParameterRow = getComponent("parameterRow")
|
||||
const TryItOutButton = getComponent("TryItOutButton")
|
||||
|
||||
const isExecute = tryItOutEnabled && allowTryItOut
|
||||
|
||||
return (
|
||||
<div className="opblock-section">
|
||||
<div className="opblock-section-header">
|
||||
<h4 className="opblock-title">Parameters</h4>
|
||||
{ allowTryItOut ? (
|
||||
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
|
||||
) : null }
|
||||
</div>
|
||||
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
|
||||
<div className="table-container">
|
||||
<table className="parameters">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col col_header parameters-col_name">Name</th>
|
||||
<th className="col col_header parameters-col_description">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
eachMap(parameters, (parameter, k) => (
|
||||
<ParameterRow fn={ fn }
|
||||
getComponent={ getComponent }
|
||||
param={ parameter }
|
||||
key={ parameter.get( "name" ) }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ pathMethod }
|
||||
isExecute={ isExecute }/>
|
||||
)).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
95
src/core/components/response-body.jsx
Normal file
95
src/core/components/response-body.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { formatXml } from "core/utils"
|
||||
import lowerCase from "lodash/lowerCase"
|
||||
|
||||
export default class ResponseBody extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
content: PropTypes.any.isRequired,
|
||||
contentType: PropTypes.string.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
headers: PropTypes.object,
|
||||
url: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
let { content, contentType, url, headers={}, getComponent } = this.props
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
let body, bodyEl
|
||||
url = url || ""
|
||||
|
||||
// JSON
|
||||
if (/json/i.test(contentType)) {
|
||||
try {
|
||||
body = JSON.stringify(JSON.parse(content), null, " ")
|
||||
} catch (error) {
|
||||
body = "can't parse JSON. Raw result:\n\n" + content
|
||||
}
|
||||
|
||||
bodyEl = <HighlightCode value={ body } />
|
||||
|
||||
// XML
|
||||
} else if (/xml/i.test(contentType)) {
|
||||
body = formatXml(content)
|
||||
bodyEl = <HighlightCode value={ body } />
|
||||
|
||||
// HTML or Plain Text
|
||||
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
|
||||
bodyEl = <HighlightCode value={ content } />
|
||||
|
||||
// Image
|
||||
} else if (/^image\//i.test(contentType)) {
|
||||
bodyEl = <img src={ url } />
|
||||
|
||||
// Audio
|
||||
} else if (/^audio\//i.test(contentType)) {
|
||||
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
|
||||
|
||||
// Download
|
||||
} else if (
|
||||
/^application\/octet-stream/i.test(contentType) ||
|
||||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) ||
|
||||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) ||
|
||||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
|
||||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {
|
||||
|
||||
let contentLength = headers["content-length"] || headers["Content-Length"]
|
||||
if ( !(+contentLength) ) return null
|
||||
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||
|
||||
if (!isSafari && "Blob" in window) {
|
||||
let type = contentType || "text/html"
|
||||
let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})
|
||||
let href = window.URL.createObjectURL(blob)
|
||||
let fileName = url.substr(url.lastIndexOf("/") + 1)
|
||||
let download = [type, fileName, href].join(":")
|
||||
|
||||
// Use filename from response header
|
||||
let disposition = headers["content-disposition"] || headers["Content-Disposition"]
|
||||
if (typeof disposition !== "undefined") {
|
||||
let responseFilename = /filename=([^;]*);?/i.exec(disposition)
|
||||
if (responseFilename !== null && responseFilename.length > 1) {
|
||||
download = responseFilename[1]
|
||||
}
|
||||
}
|
||||
|
||||
bodyEl = <div><a href={ href } download={ download }>{ "Download file" }</a></div>
|
||||
} else {
|
||||
bodyEl = <pre>Download headers detected but your browser does not support downloading binary via XHR (Blob).</pre>
|
||||
}
|
||||
|
||||
// Anything else (CORS)
|
||||
} else if (typeof content === "string") {
|
||||
bodyEl = <HighlightCode value={ content } />
|
||||
} else {
|
||||
bodyEl = <div>Unknown response type</div>
|
||||
}
|
||||
|
||||
return ( !bodyEl ? null : <div>
|
||||
<h5>Response body</h5>
|
||||
{ bodyEl }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
91
src/core/components/response.jsx
Normal file
91
src/core/components/response.jsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { fromJS } from 'immutable'
|
||||
import { getSampleSchema } from "core/utils"
|
||||
|
||||
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
|
||||
if ( examples && examples.size ) {
|
||||
return examples.entrySeq().map( ([ key, example ]) => {
|
||||
return (<div key={ key }>
|
||||
<h5>{ key }</h5>
|
||||
<HighlightCode className="example" value={ example } />
|
||||
</div>)
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
if ( sampleResponse ) { return <div>
|
||||
<HighlightCode className="example" value={ sampleResponse } />
|
||||
</div>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default class Response extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
code: PropTypes.string.isRequired,
|
||||
response: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
response: fromJS({}),
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
code,
|
||||
response,
|
||||
className,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
specSelectors,
|
||||
contentType
|
||||
} = this.props
|
||||
|
||||
let { inferSchema } = fn
|
||||
|
||||
let schema = inferSchema(response.toJS())
|
||||
let headers = response.get("headers")
|
||||
let examples = response.get("examples")
|
||||
const Headers = getComponent("headers")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const ModelExample = getComponent("modelExample")
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
|
||||
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
|
||||
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
|
||||
|
||||
return (
|
||||
<tr className={ "response " + ( className || "") }>
|
||||
<td className="col response-col_status">
|
||||
{ code }
|
||||
</td>
|
||||
<td className="col response-col_description">
|
||||
|
||||
<div className="response-col_description__inner">
|
||||
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ response.get( "description" ) } />
|
||||
</div>
|
||||
|
||||
{ example ? (
|
||||
<ModelExample
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
schema={ fromJS(schema) }
|
||||
example={ example }/>
|
||||
) : null}
|
||||
|
||||
{ headers ? (
|
||||
<Headers headers={ headers }/>
|
||||
) : null}
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
93
src/core/components/responses.jsx
Normal file
93
src/core/components/responses.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import { fromJS } from "immutable"
|
||||
import { defaultStatusCode } from "core/utils"
|
||||
|
||||
export default class Responses extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
request: PropTypes.object,
|
||||
tryItOutResponse: PropTypes.object,
|
||||
responses: PropTypes.object.isRequired,
|
||||
produces: PropTypes.object,
|
||||
producesValue: PropTypes.any,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
request: null,
|
||||
tryItOutResponse: null,
|
||||
produces: fromJS(["application/json"])
|
||||
}
|
||||
|
||||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)
|
||||
|
||||
render() {
|
||||
let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue } = this.props
|
||||
let defaultCode = defaultStatusCode( responses )
|
||||
|
||||
const ContentType = getComponent( "contentType" )
|
||||
const LiveResponse = getComponent( "liveResponse" )
|
||||
const Response = getComponent( "response" )
|
||||
|
||||
let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces
|
||||
|
||||
return (
|
||||
<div className="responses-wrapper">
|
||||
<div className="opblock-section-header">
|
||||
<h4>Responses</h4>
|
||||
<label>
|
||||
<span>Response content type</span>
|
||||
<ContentType value={producesValue}
|
||||
onChange={this.onChangeProducesWrapper}
|
||||
contentTypes={produces}
|
||||
className="execute-content-type"/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="responses-inner">
|
||||
{
|
||||
!tryItOutResponse ? null
|
||||
: <div>
|
||||
<LiveResponse request={ request }
|
||||
response={ tryItOutResponse }
|
||||
getComponent={ getComponent } />
|
||||
<h4>Responses</h4>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
<table className="responses-table">
|
||||
<thead>
|
||||
<tr className="responses-header">
|
||||
<td className="col col_header response-col_status">Code</td>
|
||||
<td className="col col_header response-col_description">Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
responses.entrySeq().map( ([code, response]) => {
|
||||
|
||||
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
|
||||
return (
|
||||
<Response key={ code }
|
||||
isDefault={defaultCode === code}
|
||||
fn={fn}
|
||||
className={ className }
|
||||
code={ code }
|
||||
response={ response }
|
||||
specSelectors={ specSelectors }
|
||||
contentType={ producesValue }
|
||||
getComponent={ getComponent }/>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
45
src/core/components/schemes.jsx
Normal file
45
src/core/components/schemes.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class Schemes extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
schemes: PropTypes.object.isRequired,
|
||||
path: PropTypes.string,
|
||||
method: PropTypes.string
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let { schemes } = this.props
|
||||
|
||||
//fire 'change' event to set default 'value' of select
|
||||
this.setScheme(schemes.first())
|
||||
}
|
||||
|
||||
onChange =( e ) => {
|
||||
let { path, method, specActions } = this.props
|
||||
|
||||
this.setScheme( e.target.value )
|
||||
}
|
||||
|
||||
setScheme =( value ) => {
|
||||
let { path, method, specActions } = this.props
|
||||
|
||||
specActions.setScheme( value, path, method )
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schemes } = this.props
|
||||
|
||||
return (
|
||||
<label htmlFor="schemes">
|
||||
<span>Schemes</span>
|
||||
<select onChange={ this.onChange }>
|
||||
{ schemes.valueSeq().map(
|
||||
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
|
||||
).toArray()}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
}
|
||||
0
src/core/components/system-wrapper.jsx
Normal file
0
src/core/components/system-wrapper.jsx
Normal file
27
src/core/components/try-it-out-button.jsx
Normal file
27
src/core/components/try-it-out-button.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default class TryItOutButton extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
onTryoutClick: PropTypes.func,
|
||||
enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onTryoutClick: Function.prototype,
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onTryoutClick, onCancelClick, enabled } = this.props
|
||||
|
||||
return (
|
||||
<div className="try-out">
|
||||
{
|
||||
enabled ? <button className="btn try-out__btn cancel" onClick={ onTryoutClick }>Cancel</button>
|
||||
: <button className="btn try-out__btn" onClick={ onCancelClick }>Try it out </button>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
34
src/core/curlify.js
Normal file
34
src/core/curlify.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export default function curl( request ){
|
||||
let curlified = []
|
||||
let type = ""
|
||||
let headers = request.get("headers")
|
||||
curlified.push( "curl" )
|
||||
curlified.push( "-X", request.get("method") )
|
||||
curlified.push( request.get("url") )
|
||||
|
||||
if ( headers && headers.size ) {
|
||||
for( let p of request.get("headers").entries() ){
|
||||
let [ h,v ] = p
|
||||
type = v
|
||||
curlified.push( "-H " )
|
||||
curlified.push( `"${h}: ${v}"` )
|
||||
}
|
||||
}
|
||||
|
||||
if ( request.get("body") ){
|
||||
|
||||
if(type === "multipart/form-data" && request.get("method") === "POST") {
|
||||
let formDataBody = request.get("body").split("&")
|
||||
|
||||
for(var data in formDataBody) {
|
||||
curlified.push( "-F" )
|
||||
curlified.push(formDataBody[data])
|
||||
}
|
||||
} else {
|
||||
curlified.push( "-d" )
|
||||
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "") )
|
||||
}
|
||||
}
|
||||
|
||||
return curlified.join( " " )
|
||||
}
|
||||
119
src/core/index.js
Normal file
119
src/core/index.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import deepExtend from "deep-extend"
|
||||
|
||||
import System from "core/system"
|
||||
import ApisPreset from "core/presets/apis"
|
||||
import * as AllPlugins from "core/plugins/all"
|
||||
import { filterConfigs } from "plugins/configs"
|
||||
|
||||
module.exports = function SwaggerUI(opts) {
|
||||
|
||||
const defaults = {
|
||||
// Some general settings, that we floated to the top
|
||||
dom_id: null,
|
||||
spec: {},
|
||||
url: "",
|
||||
layout: "Layout",
|
||||
configs: {
|
||||
validatorUrl: "https://online.swagger.io/validator"
|
||||
},
|
||||
|
||||
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
|
||||
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
|
||||
presets: [
|
||||
],
|
||||
|
||||
// Plugins; ( loaded after presets )
|
||||
plugins: [
|
||||
],
|
||||
|
||||
// Inline Plugin
|
||||
fn: { },
|
||||
components: { },
|
||||
state: { },
|
||||
|
||||
// Override some core configs... at your own risk
|
||||
store: { },
|
||||
}
|
||||
|
||||
const config = deepExtend({}, defaults, opts)
|
||||
|
||||
const storeConfigs = deepExtend({}, config.store, {
|
||||
system: {
|
||||
configs: config.configs
|
||||
},
|
||||
plugins: config.presets,
|
||||
state: {
|
||||
layout: {
|
||||
layout: config.layout
|
||||
},
|
||||
spec: {
|
||||
spec: "",
|
||||
url: config.url
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let inlinePlugin = ()=> {
|
||||
return {
|
||||
fn: config.fn,
|
||||
components: config.components,
|
||||
state: config.state,
|
||||
}
|
||||
}
|
||||
|
||||
var store = new System(storeConfigs)
|
||||
store.register([config.plugins, inlinePlugin])
|
||||
|
||||
var system = store.getSystem()
|
||||
|
||||
const downloadSpec = (configs) => {
|
||||
if(typeof config !== "object") {
|
||||
return system
|
||||
}
|
||||
|
||||
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
|
||||
let mergedConfig = deepExtend({}, config, configs, localConfig)
|
||||
store.setConfigs(filterConfigs(mergedConfig))
|
||||
|
||||
if(typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
|
||||
system.specActions.updateUrl("")
|
||||
system.specActions.updateLoadingStatus("success");
|
||||
system.specActions.updateSpec(JSON.stringify(mergedConfig.spec))
|
||||
} else if(mergedConfig.url) {
|
||||
system.specActions.updateUrl(mergedConfig.url)
|
||||
system.specActions.download(mergedConfig.url)
|
||||
}
|
||||
|
||||
if(mergedConfig.dom_id)
|
||||
system.render(mergedConfig.dom_id, "App")
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
if (system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(downloadSpec)) {
|
||||
return downloadSpec(config)
|
||||
}
|
||||
|
||||
if (system.specActions.download && config.url) {
|
||||
system.specActions.download(config.url)
|
||||
}
|
||||
|
||||
if(config.spec && typeof config.spec === "string")
|
||||
system.specActions.updateSpec(config.spec)
|
||||
|
||||
if(config.dom_id) {
|
||||
system.render(config.dom_id, "App")
|
||||
} else {
|
||||
console.error("Skipped rendering: no `dom_id` was specified")
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
// Add presets
|
||||
module.exports.presets = {
|
||||
apis: ApisPreset,
|
||||
}
|
||||
|
||||
// All Plugins
|
||||
module.exports.plugins = AllPlugins
|
||||
187
src/core/json-schema-components.js
Normal file
187
src/core/json-schema-components.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import React, { PropTypes, Component } from "react"
|
||||
import { arrayify } from "core/utils"
|
||||
import shallowCompare from "react-addons-shallow-compare"
|
||||
import { List, fromJS } from "immutable"
|
||||
import assign from "object-assign"
|
||||
//import "less/json-schema-form"
|
||||
|
||||
const noop = ()=> {}
|
||||
const JsonSchemaPropShape = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
keyName: PropTypes.any,
|
||||
fn: PropTypes.object.isRequired,
|
||||
schema: PropTypes.object,
|
||||
required: PropTypes.bool,
|
||||
description: PropTypes.any
|
||||
}
|
||||
|
||||
const JsonSchemaDefaultProps = {
|
||||
value: "",
|
||||
onChange: noop,
|
||||
schema: {},
|
||||
keyName: "",
|
||||
required: false
|
||||
}
|
||||
|
||||
export class JsonSchemaForm extends Component {
|
||||
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
render() {
|
||||
let { schema, value, onChange, getComponent, fn } = this.props
|
||||
|
||||
if(schema.toJS)
|
||||
schema = schema.toJS()
|
||||
|
||||
let { type, format="" } = schema
|
||||
|
||||
let Comp = getComponent(`JsonSchema_${type}_${format}`) || getComponent(`JsonSchema_${type}`) || getComponent("JsonSchema_string")
|
||||
return <Comp { ...this.props } fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class JsonSchema_string extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
onChange = (e) => {
|
||||
const value = this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value
|
||||
this.props.onChange(value, this.props.keyName)
|
||||
}
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, schema, fn, required, description } = this.props
|
||||
let enumValue = schema["enum"]
|
||||
let errors = schema.errors || []
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select allowedValues={ enumValue }
|
||||
value={ value }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
const isDisabled = schema["in"] === "formData" && !("FormData" in window)
|
||||
const Input = getComponent("Input")
|
||||
if (schema["type"] === "file") {
|
||||
return <Input type="file" className={ errors.length ? "invalid" : ""} onChange={ this.onChange } disabled={isDisabled}/>
|
||||
}
|
||||
else {
|
||||
return <Input type="text" className={ errors.length ? "invalid" : ""} value={value} placeholder={description} onChange={ this.onChange } disabled={isDisabled}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchema_array extends Component {
|
||||
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {value: props.value}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if(props.value !== this.state.value)
|
||||
this.setState({value: props.value})
|
||||
}
|
||||
|
||||
shouldComponentUpdate(props, state) {
|
||||
return shallowCompare(this, props, state)
|
||||
}
|
||||
|
||||
onChange = () => this.props.onChange(this.state.value)
|
||||
|
||||
onItemChange = (itemVal, i) => {
|
||||
this.setState(state => ({
|
||||
value: state.value.set(i, itemVal)
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
removeItem = (i) => {
|
||||
this.setState(state => ({
|
||||
value: state.value.remove(i)
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
addItem = () => {
|
||||
this.setState(state => {
|
||||
state.value = state.value || List()
|
||||
return {
|
||||
value: state.value.push("")
|
||||
}
|
||||
}, this.onChange)
|
||||
}
|
||||
|
||||
onEnumChange = (value) => {
|
||||
this.setState(state => ({
|
||||
value: value
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, onChange, required, schema, fn } = this.props
|
||||
|
||||
let itemSchema = fn.inferSchema(schema.items)
|
||||
|
||||
const JsonSchemaForm = getComponent("JsonSchemaForm")
|
||||
const Button = getComponent("Button")
|
||||
|
||||
let enumValue = itemSchema["enum"]
|
||||
let value = this.state.value
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select multiple={ true }
|
||||
value={ value }
|
||||
allowedValues={ enumValue }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
let errors = schema.errors || []
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ !value || value.count() < 1 ?
|
||||
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
|
||||
value.map( (item,i) => {
|
||||
let schema = Object.assign({}, itemSchema)
|
||||
let err = errors.filter((err) => err.index === i)
|
||||
if ( err.length ) {
|
||||
schema.errors = [ err[0].error + i ]
|
||||
}
|
||||
return (
|
||||
<div key={i} className="json-schema-form-item">
|
||||
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
|
||||
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
|
||||
</div>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchema_boolean extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, required, value } = this.props
|
||||
const Select = getComponent("Select")
|
||||
|
||||
return (<Select value={ String(value) }
|
||||
allowedValues={ fromJS(["true", "false"]) }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
}
|
||||
47
src/core/oauth2-authorize.js
Normal file
47
src/core/oauth2-authorize.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import win from "core/window"
|
||||
|
||||
export default function authorize ( auth, authActions, errActions, configs ) {
|
||||
let { schema, scopes, name, clientId } = auth
|
||||
|
||||
let redirectUrl = configs.oauth2RedirectUrl
|
||||
let scopeSeparator = " "
|
||||
let state = name
|
||||
let flow = schema.get("flow")
|
||||
let url
|
||||
|
||||
if (flow === "password") {
|
||||
authActions.authorizePassword(auth)
|
||||
return
|
||||
}
|
||||
|
||||
// todo move to parser
|
||||
if ( !redirectUrl ) {
|
||||
errActions.newAuthErr( {
|
||||
authId: name,
|
||||
source: "validation",
|
||||
level: "error",
|
||||
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed."
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (flow === "implicit" || flow === "accessCode") {
|
||||
url = schema.get("authorizationUrl") + "?response_type=" + (flow === "implicit" ? "token" : "code")
|
||||
}
|
||||
|
||||
url += "&redirect_uri=" + encodeURIComponent(redirectUrl)
|
||||
+ "&scope=" + encodeURIComponent(scopes.join(scopeSeparator))
|
||||
+ "&state=" + encodeURIComponent(state)
|
||||
+ "&client_id=" + encodeURIComponent(clientId)
|
||||
|
||||
// pass action authorizeOauth2 and authentication data through window
|
||||
// to authorize with oauth2
|
||||
win.swaggerUIRedirectOauth2 = {
|
||||
auth: auth,
|
||||
state: state,
|
||||
callback: authActions.preAuthorizeOauth2,
|
||||
errCb: errActions.newAuthErr
|
||||
}
|
||||
|
||||
win.open(url)
|
||||
}
|
||||
67
src/core/path-translator.js
Normal file
67
src/core/path-translator.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import get from "lodash/get"
|
||||
|
||||
export function transformPathToArray(property, jsSpec) {
|
||||
if(property.slice(0,9) === "instance.") {
|
||||
var str = property.slice(9)
|
||||
} else { // eslint-disable-next-line no-redeclare
|
||||
var str = property
|
||||
}
|
||||
|
||||
var pathArr = []
|
||||
|
||||
str
|
||||
.split(".")
|
||||
.map(item => {
|
||||
// "key[0]" becomes ["key", "0"]
|
||||
if(item.includes("[")) {
|
||||
let index = parseInt(item.match(/\[(.*)\]/)[1])
|
||||
let keyName = item.slice(0, item.indexOf("["))
|
||||
return [keyName, index.toString()]
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
})
|
||||
.reduce(function(a, b) {
|
||||
// flatten!
|
||||
return a.concat(b)
|
||||
}, [])
|
||||
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
|
||||
.reduce((buffer, curr, i, arr) => {
|
||||
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
|
||||
|
||||
if(get(obj, makeAccessArray(buffer, curr))) {
|
||||
if(buffer.length) {
|
||||
pathArr.push(buffer)
|
||||
}
|
||||
if(curr.length) {
|
||||
pathArr.push(curr)
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
// attach key to buffer
|
||||
return `${buffer}${buffer.length ? "." : ""}${curr}`
|
||||
}
|
||||
}, "")
|
||||
|
||||
if(typeof get(jsSpec, pathArr) !== "undefined") {
|
||||
return pathArr
|
||||
} else {
|
||||
// if our path is not correct (there is no value at the path),
|
||||
// return null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function makeAccessArray(buffer, curr) {
|
||||
let arr = []
|
||||
|
||||
if(buffer.length) {
|
||||
arr.push(buffer)
|
||||
}
|
||||
|
||||
if(curr.length) {
|
||||
arr.push(curr)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
17
src/core/plugins/all.js
Normal file
17
src/core/plugins/all.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { pascalCaseFilename } from "core/utils"
|
||||
|
||||
const request = require.context(".", true, /\.jsx?$/)
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./index.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
// if( key.slice(2).indexOf("/") > -1) {
|
||||
// // skip files in subdirs
|
||||
// return
|
||||
// }
|
||||
|
||||
let mod = request(key)
|
||||
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
|
||||
})
|
||||
36
src/core/plugins/allow-try-it-out-if-host.js
Normal file
36
src/core/plugins/allow-try-it-out-if-host.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { PropTypes } from "react"
|
||||
|
||||
export default function (system) {
|
||||
return {
|
||||
components: {
|
||||
NoHostWarning,
|
||||
},
|
||||
statePlugins: {
|
||||
spec: {
|
||||
selectors: {
|
||||
allowTryItOutFor,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a quick style. How do we improve this?
|
||||
const style = {
|
||||
backgroundColor: "#e7f0f7",
|
||||
padding: "1rem",
|
||||
borderRadius: "3px",
|
||||
}
|
||||
|
||||
function NoHostWarning() {
|
||||
return (
|
||||
<div style={style}>Note: The interactive forms are disabled, as no `host` property was found in the specification. Please see: <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object" target="_blank">OAI 2.0/#swagger-object</a></div>
|
||||
)
|
||||
}
|
||||
|
||||
// Only allow if, there is a host field
|
||||
function allowTryItOutFor(state) {
|
||||
return ({specSelectors}) => {
|
||||
return specSelectors.hasHost(state)
|
||||
}
|
||||
}
|
||||
284
src/core/plugins/ast/ast.js
Normal file
284
src/core/plugins/ast/ast.js
Normal file
@@ -0,0 +1,284 @@
|
||||
import YAML from "yaml-js"
|
||||
import isArray from "lodash/isArray"
|
||||
import lodashFind from "lodash/find"
|
||||
import { memoize } from "core/utils"
|
||||
|
||||
let cachedCompose = memoize(YAML.compose) // TODO: build a custom cache based on content
|
||||
|
||||
var MAP_TAG = "tag:yaml.org,2002:map"
|
||||
var SEQ_TAG = "tag:yaml.org,2002:seq"
|
||||
|
||||
export function getLineNumberForPath(yaml, path) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (!isArray(path)) {
|
||||
throw new TypeError("path should be an array of strings")
|
||||
}
|
||||
|
||||
var i = 0
|
||||
|
||||
let ast = cachedCompose(yaml)
|
||||
|
||||
// simply walks the tree using current path recursively to the point that
|
||||
// path is empty
|
||||
|
||||
return find(ast, path)
|
||||
|
||||
function find(current, path, last) {
|
||||
if(!current) {
|
||||
// something has gone quite wrong
|
||||
// return the last start_mark as a best-effort
|
||||
if(last && last.start_mark)
|
||||
return last.start_mark.line
|
||||
return 0
|
||||
}
|
||||
|
||||
if (path.length && current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (key.value === path[0]) {
|
||||
return find(value, path.slice(1), current)
|
||||
}
|
||||
|
||||
if (key.value === path[0].replace(/\[.*/, "")) {
|
||||
// access the array at the index in the path (example: grab the 2 in "tags[2]")
|
||||
var index = parseInt(path[0].match(/\[(.*)\]/)[1])
|
||||
if(value.value.length === 1 && index !== 0 && !!index) {
|
||||
var nextVal = lodashFind(value.value[0], { value: index.toString() })
|
||||
} else { // eslint-disable-next-line no-redeclare
|
||||
var nextVal = value.value[index]
|
||||
}
|
||||
return find(nextVal, path.slice(1), value.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path.length && current.tag === SEQ_TAG) {
|
||||
var item = current.value[path[0]]
|
||||
|
||||
if (item && item.tag) {
|
||||
return find(item, path.slice(1), current.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === MAP_TAG && !Array.isArray(last)) {
|
||||
return current.start_mark.line
|
||||
} else {
|
||||
return current.start_mark.line + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a position object with given
|
||||
* @param {string} yaml
|
||||
* YAML or JSON string
|
||||
* @param {array} path
|
||||
* an array of stings that constructs a
|
||||
* JSON Path similiar to JSON Pointers(RFC 6901). The difference is, each
|
||||
* component of path is an item of the array intead of beinf seperated with
|
||||
* slash(/) in a string
|
||||
*/
|
||||
export function positionRangeForPath(yaml, path) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (!isArray(path)) {
|
||||
throw new TypeError("path should be an array of strings")
|
||||
}
|
||||
|
||||
var invalidRange = {
|
||||
start: {line: -1, column: -1},
|
||||
end: {line: -1, column: -1}
|
||||
}
|
||||
var i = 0
|
||||
|
||||
let ast = cachedCompose(yaml)
|
||||
|
||||
// simply walks the tree using current path recursively to the point that
|
||||
// path is empty.
|
||||
return find(ast)
|
||||
|
||||
function find(current) {
|
||||
if (current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (key.value === path[0]) {
|
||||
path.shift()
|
||||
return find(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === SEQ_TAG) {
|
||||
var item = current.value[path[0]]
|
||||
|
||||
if (item && item.tag) {
|
||||
path.shift()
|
||||
return find(item)
|
||||
}
|
||||
}
|
||||
|
||||
// if path is still not empty we were not able to find the node
|
||||
if (path.length) {
|
||||
return invalidRange
|
||||
}
|
||||
|
||||
return {
|
||||
/* jshint camelcase: false */
|
||||
start: {
|
||||
line: current.start_mark.line,
|
||||
column: current.start_mark.column
|
||||
},
|
||||
end: {
|
||||
line: current.end_mark.line,
|
||||
column: current.end_mark.column
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON Path for position object in the spec
|
||||
* @param {string} yaml
|
||||
* YAML or JSON string
|
||||
* @param {object} position
|
||||
* position in the YAML or JSON string with `line` and `column` properties.
|
||||
* `line` and `column` number are zero indexed
|
||||
*/
|
||||
export function pathForPosition(yaml, position) {
|
||||
|
||||
// Type check
|
||||
if (typeof yaml !== "string") {
|
||||
throw new TypeError("yaml should be a string")
|
||||
}
|
||||
if (typeof position !== "object" || typeof position.line !== "number" ||
|
||||
typeof position.column !== "number") {
|
||||
throw new TypeError("position should be an object with line and column" +
|
||||
" properties")
|
||||
}
|
||||
|
||||
try {
|
||||
var ast = cachedCompose(yaml)
|
||||
} catch (e) {
|
||||
console.error("Error composing AST", e)
|
||||
console.error(`Problem area:\n`, yaml.split("\n").slice(position.line - 5, position.line + 5).join("\n"))
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
var path = []
|
||||
|
||||
return find(ast)
|
||||
|
||||
/**
|
||||
* recursive find function that finds the node matching the position
|
||||
* @param {object} current - AST object to serach into
|
||||
*/
|
||||
function find(current) {
|
||||
|
||||
// algorythm:
|
||||
// is current a promitive?
|
||||
// // finish recursion without modifying the path
|
||||
// is current a hash?
|
||||
// // find a key or value that position is in their range
|
||||
// // if key is in range, terminate recursion with exisiting path
|
||||
// // if a value is in range push the corresponding key to the path
|
||||
// // andcontinue recursion
|
||||
// is current an array
|
||||
// // find the item that position is in it"s range and push the index
|
||||
// // of the item to the path and continue recursion with that item.
|
||||
|
||||
var i = 0
|
||||
|
||||
if (!current || [MAP_TAG, SEQ_TAG].indexOf(current.tag) === -1) {
|
||||
return path
|
||||
}
|
||||
|
||||
if (current.tag === MAP_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var pair = current.value[i]
|
||||
var key = pair[0]
|
||||
var value = pair[1]
|
||||
|
||||
if (isInRange(key)) {
|
||||
return path
|
||||
} else if (isInRange(value)) {
|
||||
path.push(key.value)
|
||||
return find(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.tag === SEQ_TAG) {
|
||||
for (i = 0; i < current.value.length; i++) {
|
||||
var item = current.value[i]
|
||||
|
||||
if (isInRange(item)) {
|
||||
path.push(i.toString())
|
||||
return find(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path
|
||||
|
||||
/**
|
||||
* Determines if position is in node"s range
|
||||
* @param {object} node - AST node
|
||||
* @return {Boolean} true if position is in node"s range
|
||||
*/
|
||||
function isInRange(node) {
|
||||
/* jshint camelcase: false */
|
||||
|
||||
// if node is in a single line
|
||||
if (node.start_mark.line === node.end_mark.line) {
|
||||
|
||||
return (position.line === node.start_mark.line) &&
|
||||
(node.start_mark.column <= position.column) &&
|
||||
(node.end_mark.column >= position.column)
|
||||
}
|
||||
|
||||
// if position is in the same line as start_mark
|
||||
if (position.line === node.start_mark.line) {
|
||||
return position.column >= node.start_mark.column
|
||||
}
|
||||
|
||||
// if position is in the same line as end_mark
|
||||
if (position.line === node.end_mark.line) {
|
||||
return position.column <= node.end_mark.column
|
||||
}
|
||||
|
||||
// if position is between start and end lines return true, otherwise
|
||||
// return false.
|
||||
return (node.start_mark.line < position.line) &&
|
||||
(node.end_mark.line > position.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// utility fns
|
||||
|
||||
|
||||
export let pathForPositionAsync = promisifySyncFn(pathForPosition)
|
||||
export let positionRangeForPathAsync = promisifySyncFn(positionRangeForPath)
|
||||
export let getLineNumberForPathAsync = promisifySyncFn(getLineNumberForPath)
|
||||
|
||||
function promisifySyncFn(fn) {
|
||||
return function(...args) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
resolve(fn(...args))
|
||||
})
|
||||
}
|
||||
}
|
||||
9
src/core/plugins/ast/index.js
Normal file
9
src/core/plugins/ast/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as AST from "./ast"
|
||||
import JumpToPath from "./jump-to-path"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
fn: { AST },
|
||||
components: { JumpToPath }
|
||||
}
|
||||
}
|
||||
9
src/core/plugins/ast/jump-to-path.jsx
Normal file
9
src/core/plugins/ast/jump-to-path.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
|
||||
// Nothing by default- component can be overriden by another plugin.
|
||||
|
||||
export default class JumpToPath extends React.Component {
|
||||
render() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
118
src/core/plugins/auth/actions.js
Normal file
118
src/core/plugins/auth/actions.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import win from "core/window"
|
||||
import btoa from "btoa"
|
||||
|
||||
export const SHOW_AUTH_POPUP = "show_popup"
|
||||
export const AUTHORIZE = "authorize"
|
||||
export const LOGOUT = "logout"
|
||||
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
|
||||
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
|
||||
export const VALIDATE = "validate"
|
||||
|
||||
export function showDefinitions(payload) {
|
||||
return {
|
||||
type: SHOW_AUTH_POPUP,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export function authorize(payload) {
|
||||
return {
|
||||
type: AUTHORIZE,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export function logout(payload) {
|
||||
return {
|
||||
type: LOGOUT,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export const preAuthorizeOauth2 = (payload) => ( { authActions, errActions } ) => {
|
||||
let { auth , token, isValid } = payload
|
||||
let { schema, name } = auth
|
||||
let flow = schema.get("flow")
|
||||
|
||||
// remove oauth2 property from window after redirect from authentication
|
||||
delete win.swaggerUIRedirectOauth2
|
||||
|
||||
if ( flow !== "accessCode" && !isValid ) {
|
||||
errActions.newAuthErr( {
|
||||
authId: name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
})
|
||||
}
|
||||
|
||||
if ( token.error ) {
|
||||
errActions.newAuthErr({
|
||||
authId: name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: JSON.stringify(token)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authActions.authorizeOauth2({ auth, token })
|
||||
}
|
||||
|
||||
export function authorizeOauth2(payload) {
|
||||
return {
|
||||
type: AUTHORIZE_OAUTH2,
|
||||
payload: payload
|
||||
}
|
||||
}
|
||||
|
||||
export const authorizePassword = ( auth ) => ( { fn, authActions, errActions } ) => {
|
||||
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
|
||||
let req = {
|
||||
url: schema.get("tokenUrl"),
|
||||
method: "post",
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
query: {
|
||||
grant_type: "password",
|
||||
username,
|
||||
password
|
||||
}
|
||||
}
|
||||
|
||||
if ( passwordType === "basic") {
|
||||
req.headers.authorization = "Basic " + btoa(clientId + ":" + clientSecret)
|
||||
} else if ( passwordType === "request") {
|
||||
req.query = Object.assign(req.query, { client_id: clientId, client_secret: clientSecret })
|
||||
}
|
||||
return fn.fetch(req)
|
||||
.then(( response ) => {
|
||||
let token = JSON.parse(response.data)
|
||||
let error = token && ( token.error || "" )
|
||||
let parseError = token && ( token.parseError || "" )
|
||||
|
||||
if ( !response.ok ) {
|
||||
errActions.newAuthErr( {
|
||||
authId: name,
|
||||
level: "error",
|
||||
source: "auth",
|
||||
message: response.statusText
|
||||
} )
|
||||
return
|
||||
}
|
||||
|
||||
if ( error || parseError ) {
|
||||
errActions.newAuthErr({
|
||||
authId: name,
|
||||
level: "error",
|
||||
source: "auth",
|
||||
message: JSON.stringify(token)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
authActions.authorizeOauth2({ auth, token })
|
||||
})
|
||||
.catch(err => { errActions.newAuthErr( err ) })
|
||||
}
|
||||
19
src/core/plugins/auth/index.js
Normal file
19
src/core/plugins/auth/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
import * as specWrapActionReplacements from "./spec-wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
auth: {
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
},
|
||||
spec: {
|
||||
wrapActions: specWrapActionReplacements
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/core/plugins/auth/reducers.js
Normal file
63
src/core/plugins/auth/reducers.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { fromJS, Map } from "immutable"
|
||||
import btoa from "btoa"
|
||||
|
||||
import {
|
||||
SHOW_AUTH_POPUP,
|
||||
AUTHORIZE,
|
||||
PRE_AUTHORIZE_OAUTH2,
|
||||
AUTHORIZE_OAUTH2,
|
||||
LOGOUT
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
[SHOW_AUTH_POPUP]: (state, { payload } ) =>{
|
||||
return state.set( "showDefinitions", payload )
|
||||
},
|
||||
|
||||
[AUTHORIZE]: (state, { payload } ) =>{
|
||||
let securities = fromJS(payload)
|
||||
let map = state.get("authorized") || Map()
|
||||
|
||||
// refactor withMutations
|
||||
securities.entrySeq().forEach( ([ key, security ]) => {
|
||||
let type = security.getIn(["schema", "type"])
|
||||
let name = security.get("name")
|
||||
|
||||
if ( type === "apiKey" ) {
|
||||
map = map.set(key, security)
|
||||
} else if ( type === "basic" ) {
|
||||
let username = security.getIn(["value", "username"])
|
||||
let password = security.getIn(["value", "password"])
|
||||
|
||||
map = map.setIn([key, "value"], {
|
||||
username: username,
|
||||
header: "Basic " + btoa(username + ":" + password)
|
||||
})
|
||||
|
||||
map = map.setIn([key, "schema"], security.get("schema"))
|
||||
}
|
||||
})
|
||||
|
||||
return state.set( "authorized", map )
|
||||
},
|
||||
|
||||
[AUTHORIZE_OAUTH2]: (state, { payload } ) =>{
|
||||
let { auth, token } = payload
|
||||
let parsedAuth
|
||||
|
||||
auth.token = token
|
||||
parsedAuth = fromJS(auth)
|
||||
|
||||
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
|
||||
},
|
||||
|
||||
[LOGOUT]: (state, { payload } ) =>{
|
||||
let result = state.get("authorized").withMutations((authorized) => {
|
||||
payload.forEach((auth) => {
|
||||
authorized.delete(auth)
|
||||
})
|
||||
})
|
||||
|
||||
return state.set("authorized", result)
|
||||
}
|
||||
}
|
||||
78
src/core/plugins/auth/selectors.js
Normal file
78
src/core/plugins/auth/selectors.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { List, Map } from "immutable"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const shownDefinitions = createSelector(
|
||||
state,
|
||||
auth => auth.get( "showDefinitions" )
|
||||
)
|
||||
|
||||
export const definitionsToAuthorize = createSelector(
|
||||
state,
|
||||
auth =>( { specSelectors } ) => {
|
||||
let definitions = specSelectors.securityDefinitions()
|
||||
let list = List()
|
||||
|
||||
//todo refactor
|
||||
definitions.entrySeq().forEach( ([ key, val ]) => {
|
||||
let map = Map()
|
||||
|
||||
map = map.set(key, val)
|
||||
list = list.push(map)
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export const getDefinitionsByNames = ( state, securities ) =>( { specSelectors } ) => {
|
||||
let securityDefinitions = specSelectors.securityDefinitions()
|
||||
let result = List()
|
||||
|
||||
securities.valueSeq().forEach( (names) => {
|
||||
let map = Map()
|
||||
names.entrySeq().forEach( ([name, scopes]) => {
|
||||
let definition = securityDefinitions.get(name)
|
||||
let allowedScopes
|
||||
|
||||
if ( definition.get("type") === "oauth2" && scopes.size ) {
|
||||
allowedScopes = definition.get("scopes")
|
||||
|
||||
allowedScopes.keySeq().forEach( (key) => {
|
||||
if ( !scopes.contains(key) ) {
|
||||
allowedScopes = allowedScopes.delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
definition = definition.set("allowedScopes", allowedScopes)
|
||||
}
|
||||
|
||||
map = map.set(name, definition)
|
||||
})
|
||||
|
||||
result = result.push(map)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const authorized = createSelector(
|
||||
state,
|
||||
auth => auth.get("authorized") || Map()
|
||||
)
|
||||
|
||||
|
||||
export const isAuthorized = ( state, securities ) =>( { authSelectors } ) => {
|
||||
let authorized = authSelectors.authorized()
|
||||
let isAuth = false
|
||||
|
||||
return !!securities.toJS().filter( ( security ) => {
|
||||
let isAuthorized = true
|
||||
|
||||
return Object.keys(security).map((key) => {
|
||||
return !isAuthorized || !!authorized.get(key)
|
||||
}).indexOf(false) === -1
|
||||
}).length
|
||||
}
|
||||
13
src/core/plugins/auth/spec-wrap-actions.js
Normal file
13
src/core/plugins/auth/spec-wrap-actions.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Map } from "immutable"
|
||||
|
||||
// Add security to the final `execute` call ( via `extras` )
|
||||
export const execute = ( oriAction, { authSelectors, specSelectors }) => ({ path, method, operation, extras }) => {
|
||||
let securities = {
|
||||
authorized: authSelectors.authorized() && authSelectors.authorized().toJS(),
|
||||
definitions: specSelectors.securityDefinitions() && specSelectors.securityDefinitions().toJS(),
|
||||
specSecurity: specSelectors.security() && specSelectors.security().toJS()
|
||||
}
|
||||
|
||||
return oriAction({ path, method, operation, securities, ...extras })
|
||||
}
|
||||
|
||||
67
src/core/plugins/download-url.js
Normal file
67
src/core/plugins/download-url.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/* global Promise */
|
||||
|
||||
import { createSelector } from "reselect"
|
||||
import { Map } from "immutable"
|
||||
|
||||
export default function downloadUrlPlugin (toolbox) {
|
||||
let { fn, Im } = toolbox
|
||||
|
||||
const actions = {
|
||||
download: (url)=> ({ errActions, specSelectors, specActions }) => {
|
||||
let { fetch } = fn
|
||||
url = url || specSelectors.url()
|
||||
specActions.updateLoadingStatus("loading")
|
||||
fetch(url, {
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
}).then(next,next)
|
||||
|
||||
function next(res) {
|
||||
if(res instanceof Error || res.status >= 400) {
|
||||
specActions.updateLoadingStatus("failed")
|
||||
return errActions.newThrownErr( new Error(res.statusText + " " + url) )
|
||||
}
|
||||
specActions.updateLoadingStatus("success")
|
||||
specActions.updateSpec(res.text)
|
||||
specActions.updateUrl(url)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
updateLoadingStatus: (status) => {
|
||||
let enums = [null, "loading", "failed", "success"]
|
||||
if(enums.indexOf(status) === -1) {
|
||||
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
|
||||
}
|
||||
|
||||
return {
|
||||
type: "spec_update_loading_status",
|
||||
payload: status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reducers = {
|
||||
"spec_update_loading_status": (state, action) => {
|
||||
return (typeof action.payload === "string")
|
||||
? state.set("loadingStatus", action.payload)
|
||||
: state
|
||||
}
|
||||
}
|
||||
|
||||
let selectors = {
|
||||
loadingStatus: createSelector(
|
||||
state => {
|
||||
return state || Map()
|
||||
},
|
||||
spec => spec.get("loadingStatus") || null
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: { actions, reducers, selectors }
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/core/plugins/err/actions.js
Normal file
43
src/core/plugins/err/actions.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import serializeError from "serialize-error"
|
||||
|
||||
export const NEW_THROWN_ERR = "err_new_thrown_err"
|
||||
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
|
||||
export const NEW_SPEC_ERR = "err_new_spec_err"
|
||||
export const NEW_AUTH_ERR = "err_new_auth_err"
|
||||
export const CLEAR = "err_clear"
|
||||
|
||||
export function newThrownErr(err, action) {
|
||||
return {
|
||||
type: NEW_THROWN_ERR,
|
||||
payload: { action, error: serializeError(err) }
|
||||
}
|
||||
}
|
||||
|
||||
export function newThrownErrBatch(errors) {
|
||||
return {
|
||||
type: NEW_THROWN_ERR_BATCH,
|
||||
payload: errors
|
||||
}
|
||||
}
|
||||
|
||||
export function newSpecErr(err, action) {
|
||||
return {
|
||||
type: NEW_SPEC_ERR,
|
||||
payload: err
|
||||
}
|
||||
}
|
||||
|
||||
export function newAuthErr(err, action) {
|
||||
return {
|
||||
type: NEW_AUTH_ERR,
|
||||
payload: err
|
||||
}
|
||||
}
|
||||
|
||||
export function clear(filter = {}) {
|
||||
// filter looks like: {type: 'spec'}, {source: 'parser'}
|
||||
return {
|
||||
type: CLEAR,
|
||||
payload: filter
|
||||
}
|
||||
}
|
||||
31
src/core/plugins/err/error-transformers/README.md
Normal file
31
src/core/plugins/err/error-transformers/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Error transformers
|
||||
|
||||
Error transformers provide a standard interface for making generated error messages more useful to end users.
|
||||
|
||||
### Inputs & outputs
|
||||
|
||||
Each transformer's `transform` function is given an Immutable List of Immutable Maps as its first argument.
|
||||
|
||||
It is expected that each `transform` function returns a List of similarly-formed Maps.
|
||||
|
||||
These errors originate from the Redux error actions that add errors to state. Errors are transformed before being passed into the reducer.
|
||||
|
||||
It's important that all the keys present in each error (specifically, `line`, `level`, `message`, `source`, and `type`) are present when the transformer is finished.
|
||||
|
||||
##### Deleting an error
|
||||
|
||||
If you want to delete an error completely, you can overwrite it with `null`. The null value will be filtered out of the transformed error array before the errors are returned.
|
||||
å
|
||||
|
||||
### Example transformer
|
||||
|
||||
This transformer will increase all your line numbers by 10.
|
||||
|
||||
```
|
||||
export function transform(errors) {
|
||||
return errors.map(err => {
|
||||
err.line += 10
|
||||
return err
|
||||
})
|
||||
}
|
||||
```
|
||||
57
src/core/plugins/err/error-transformers/hook.js
Normal file
57
src/core/plugins/err/error-transformers/hook.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import concat from "lodash/concat"
|
||||
import reduce from "lodash/reduce"
|
||||
let request = require.context("./transformers/", true, /\.js$/)
|
||||
let errorTransformers = []
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./hook.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
if( !key.match(/js$/) ) {
|
||||
return
|
||||
}
|
||||
|
||||
if( key.slice(2).indexOf("/") > -1) {
|
||||
// skip files in subdirs
|
||||
return
|
||||
}
|
||||
|
||||
errorTransformers.push({
|
||||
name: toTitleCase(key).replace(".js", "").replace("./", ""),
|
||||
transform: request(key).transform
|
||||
})
|
||||
})
|
||||
|
||||
export default function transformErrors (errors, system) {
|
||||
let inputs = {
|
||||
jsSpec: system.specSelectors.specJson().toJS()
|
||||
}
|
||||
|
||||
let transformedErrors = reduce(errorTransformers, (result, transformer) => {
|
||||
try {
|
||||
let newlyTransformedErrors = transformer.transform(result, inputs)
|
||||
return newlyTransformedErrors.filter(err => !!err) // filter removed errors
|
||||
} catch(e) {
|
||||
console.error("Transformer error:", e)
|
||||
return result
|
||||
}
|
||||
}, errors)
|
||||
|
||||
return transformedErrors
|
||||
.filter(err => !!err) // filter removed errors
|
||||
.map(err => {
|
||||
if(!err.get("line") && err.get("path")) {
|
||||
// TODO: re-resolve line number if we've transformed it away
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function toTitleCase(str) {
|
||||
return str
|
||||
.split("-")
|
||||
.map(substr => substr[0].toUpperCase() + substr.slice(1))
|
||||
.join("")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export function transform(errors) {
|
||||
// JSONSchema refers to the current object being validated
|
||||
// as 'instance'. This isn't helpful to users, so we remove it.
|
||||
return errors
|
||||
.map(err => {
|
||||
let seekStr = "is not of a type(s)"
|
||||
let i = err.get("message").indexOf(seekStr)
|
||||
if(i > -1) {
|
||||
let types = err.get("message").slice(i + seekStr.length).split(",")
|
||||
return err.set("message", err.get("message").slice(0, i) + makeNewMessage(types))
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeNewMessage(types) {
|
||||
return types.reduce((p, c, i, arr) => {
|
||||
if(i === arr.length - 1 && arr.length > 1) {
|
||||
return p + "or " + c
|
||||
} else if(arr[i+1] && arr.length > 2) {
|
||||
return p + c + ", "
|
||||
} else if(arr[i+1]) {
|
||||
return p + c + " "
|
||||
} else {
|
||||
return p + c
|
||||
}
|
||||
}, "should be a")
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import get from "lodash/get"
|
||||
import last from "lodash/get"
|
||||
import { fromJS, List } from "immutable"
|
||||
|
||||
export function transform(errors, { jsSpec }) {
|
||||
// LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED 😃
|
||||
// TODO: finish implementing, fix flattening problem
|
||||
/* eslint-disable no-unreachable */
|
||||
return errors
|
||||
|
||||
|
||||
// JSONSchema gives us very little to go on
|
||||
let searchStr = "is not exactly one from <#/definitions/parameter>,<#/definitions/jsonReference>"
|
||||
return errors
|
||||
.map(err => {
|
||||
let message = err.get("message")
|
||||
let isParameterOneOfError = message.indexOf(searchStr) > -1
|
||||
if(isParameterOneOfError) {
|
||||
// try to find what's wrong
|
||||
return createTailoredParameterError(err, jsSpec)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
})
|
||||
.flatten(true) // shallow Immutable flatten
|
||||
}
|
||||
|
||||
const VALID_IN_VALUES = ["path", "query", "header", "body", "formData"]
|
||||
const VALID_COLLECTIONFORMAT_VALUES = ["csv", "ssv", "tsv", "pipes", "multi"]
|
||||
|
||||
function createTailoredParameterError(err, jsSpec) {
|
||||
let newErrs = []
|
||||
let parameter = get(jsSpec, err.get("path"))
|
||||
|
||||
// find addressable cases
|
||||
if(parameter.in && VALID_IN_VALUES.indexOf(parameter.in) === -1) {
|
||||
let message = `Wrong value for the "in" keyword. Expected one of: ${VALID_IN_VALUES.join(", ")}.`
|
||||
newErrs.push({
|
||||
message,
|
||||
path: err.get("path") + ".in",
|
||||
type: "spec",
|
||||
source: "schema",
|
||||
level: "error"
|
||||
})
|
||||
}
|
||||
|
||||
if(parameter.collectionFormat && VALID_COLLECTIONFORMAT_VALUES.indexOf(parameter.collectionFormat) === -1) {
|
||||
let message = `Wrong value for the "collectionFormat" keyword. Expected one of: ${VALID_COLLECTIONFORMAT_VALUES.join(", ")}.`
|
||||
newErrs.push({
|
||||
message,
|
||||
path: err.get("path") + ".collectionFormat",
|
||||
type: "spec",
|
||||
source: "schema",
|
||||
level: "error"
|
||||
})
|
||||
}
|
||||
|
||||
return newErrs.length ? fromJS(newErrs) : err // fall back to making no changes
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export function transform(errors) {
|
||||
return errors
|
||||
.map(err => {
|
||||
return err.set("message", removeSubstring(err.get("message"), "instance."))
|
||||
})
|
||||
}
|
||||
|
||||
function removeSubstring(str, substr) {
|
||||
return str.replace(new RegExp(substr, "g"), "")
|
||||
}
|
||||
15
src/core/plugins/err/index.js
Normal file
15
src/core/plugins/err/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import makeReducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
|
||||
export default function(system) {
|
||||
return {
|
||||
statePlugins: {
|
||||
err: {
|
||||
reducers: makeReducers(system),
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/core/plugins/err/reducers.js
Normal file
68
src/core/plugins/err/reducers.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
NEW_THROWN_ERR,
|
||||
NEW_THROWN_ERR_BATCH,
|
||||
NEW_SPEC_ERR,
|
||||
NEW_AUTH_ERR,
|
||||
CLEAR
|
||||
} from "./actions"
|
||||
|
||||
import reject from "lodash/reject"
|
||||
|
||||
import Im, { fromJS, List } from "immutable"
|
||||
|
||||
import transformErrors from "./error-transformers/hook"
|
||||
|
||||
let DEFAULT_ERROR_STRUCTURE = {
|
||||
// defaults
|
||||
line: 0,
|
||||
level: "error",
|
||||
message: "Unknown error"
|
||||
}
|
||||
|
||||
export default function(system) {
|
||||
return {
|
||||
[NEW_THROWN_ERR]: (state, { payload }) => {
|
||||
let error = Object.assign(DEFAULT_ERROR_STRUCTURE, payload, {type: "thrown"})
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS( error )) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_THROWN_ERR_BATCH]: (state, { payload }) => {
|
||||
payload = payload.map(err => {
|
||||
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "thrown" }))
|
||||
})
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_SPEC_ERR]: (state, { payload }) => {
|
||||
let error = fromJS(payload)
|
||||
error = error.set("type", "spec")
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS(error)).sortBy(err => err.get("line")) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[NEW_AUTH_ERR]: (state, { payload }) => {
|
||||
let error = fromJS(Object.assign({}, payload))
|
||||
|
||||
error = error.set("type", "auth")
|
||||
return state
|
||||
.update("errors", errors => (errors || List()).push( fromJS(error)) )
|
||||
.update("errors", errors => transformErrors(errors, system.getSystem()))
|
||||
},
|
||||
|
||||
[CLEAR]: (state, { payload }) => {
|
||||
if(!payload) {
|
||||
return
|
||||
}
|
||||
// TODO: Rework, to use immutable only, no need for lodash
|
||||
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
|
||||
return state.merge({
|
||||
errors: newErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/core/plugins/err/selectors.js
Normal file
15
src/core/plugins/err/selectors.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { List } from "immutable"
|
||||
import { createSelector } from "reselect"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const allErrors = createSelector(
|
||||
state,
|
||||
err => err.get("errors", List())
|
||||
)
|
||||
|
||||
export const lastError = createSelector(
|
||||
allErrors,
|
||||
all => all.last()
|
||||
)
|
||||
|
||||
45
src/core/plugins/layout/actions.js
Normal file
45
src/core/plugins/layout/actions.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { normalizeArray } from "core/utils"
|
||||
|
||||
export const UPDATE_LAYOUT = "layout_update_layout"
|
||||
export const UPDATE_MODE = "layout_update_mode"
|
||||
export const SHOW = "layout_show"
|
||||
|
||||
// export const ONLY_SHOW = "layout_only_show"
|
||||
|
||||
export function updateLayout(layout) {
|
||||
return {
|
||||
type: UPDATE_LAYOUT,
|
||||
payload: layout
|
||||
}
|
||||
}
|
||||
|
||||
export function show(thing, shown=true) {
|
||||
thing = normalizeArray(thing)
|
||||
return {
|
||||
type: SHOW,
|
||||
payload: {thing, shown}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple string key-store, used for
|
||||
export function changeMode(thing, mode="") {
|
||||
thing = normalizeArray(thing)
|
||||
return {
|
||||
type: UPDATE_MODE,
|
||||
payload: {thing, mode}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// export function onlyShow(thing, shown=true) {
|
||||
// thing = normalizeArray(thing)
|
||||
// if(thing.length < 2)
|
||||
// throw new Error("layoutActions.onlyShow only works, when `thing` is an array with length > 1")
|
||||
// return {
|
||||
// type: ONLY_SHOW,
|
||||
// payload: {thing, shown}
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
15
src/core/plugins/layout/index.js
Normal file
15
src/core/plugins/layout/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
layout: {
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/core/plugins/layout/reducers.js
Normal file
24
src/core/plugins/layout/reducers.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
UPDATE_LAYOUT,
|
||||
UPDATE_MODE,
|
||||
SHOW
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
|
||||
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload),
|
||||
|
||||
[SHOW]: (state, action) => {
|
||||
let thing = action.payload.thing
|
||||
let shown = action.payload.shown
|
||||
return state.setIn(["shown"].concat(thing), shown)
|
||||
},
|
||||
|
||||
[UPDATE_MODE]: (state, action) => {
|
||||
let thing = action.payload.thing
|
||||
let mode = action.payload.mode
|
||||
return state.setIn(["modes"].concat(thing), (mode || "") + "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
22
src/core/plugins/layout/selectors.js
Normal file
22
src/core/plugins/layout/selectors.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { normalizeArray } from "core/utils"
|
||||
|
||||
const state = state => state
|
||||
|
||||
export const current = state => state.get("layout")
|
||||
|
||||
export const isShown = (state, thing, def) => {
|
||||
thing = normalizeArray(thing)
|
||||
return Boolean(state.getIn(["shown", ...thing], def))
|
||||
}
|
||||
|
||||
export const whatMode = (state, thing, def="") => {
|
||||
thing = normalizeArray(thing)
|
||||
return state.getIn(["modes", ...thing], def)
|
||||
}
|
||||
|
||||
export const showSummary = createSelector(
|
||||
state,
|
||||
state => !isShown(state, "editor")
|
||||
)
|
||||
|
||||
28
src/core/plugins/logs/index.js
Normal file
28
src/core/plugins/logs/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export default function ({configs}) {
|
||||
|
||||
const levels = {
|
||||
"debug": 0,
|
||||
"info": 1,
|
||||
"log": 2,
|
||||
"warn": 3,
|
||||
"error": 4
|
||||
}
|
||||
|
||||
const getLevel = (level) => levels[level] || -1
|
||||
|
||||
let { logLevel } = configs
|
||||
let logLevelInt = getLevel(logLevel)
|
||||
|
||||
function log(level, ...args) {
|
||||
if(getLevel(level) >= logLevelInt)
|
||||
// eslint-disable-next-line no-console
|
||||
console[level](...args)
|
||||
}
|
||||
|
||||
log.warn = log.bind(null, "warn")
|
||||
log.error = log.bind(null, "error")
|
||||
log.info = log.bind(null, "info")
|
||||
log.debug = log.bind(null, "debug")
|
||||
|
||||
return { rootInjects: { log } }
|
||||
}
|
||||
223
src/core/plugins/samples/fn.js
Normal file
223
src/core/plugins/samples/fn.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import { objectify, isFunc, normalizeArray } from "core/utils"
|
||||
import XML from "xml"
|
||||
import memoizee from "memoizee"
|
||||
|
||||
const primitives = {
|
||||
"string": () => "string",
|
||||
"string_email": () => "user@example.com",
|
||||
"string_date-time": () => new Date().toISOString(),
|
||||
"number": () => 0,
|
||||
"number_float": () => 0.0,
|
||||
"integer": () => 0,
|
||||
"boolean": () => true
|
||||
}
|
||||
|
||||
const primitive = (schema) => {
|
||||
schema = objectify(schema)
|
||||
let { type, format } = schema
|
||||
|
||||
let fn = primitives[`${type}_${format}`] || primitives[type]
|
||||
|
||||
if(isFunc(fn))
|
||||
return fn(schema)
|
||||
|
||||
return "Unknown Type: " + schema.type
|
||||
}
|
||||
|
||||
|
||||
export const sampleFromSchema = (schema, config={}) => {
|
||||
let { type, example, properties, additionalProperties, items } = objectify(schema)
|
||||
let { includeReadOnly } = config
|
||||
|
||||
if(example !== undefined)
|
||||
return example
|
||||
|
||||
if(!type) {
|
||||
if(properties) {
|
||||
type = "object"
|
||||
} else if(items) {
|
||||
type = "array"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if(type === "object") {
|
||||
let props = objectify(properties)
|
||||
let obj = {}
|
||||
for (var name in props) {
|
||||
if ( !props[name].readOnly || includeReadOnly ) {
|
||||
obj[name] = sampleFromSchema(props[name])
|
||||
}
|
||||
}
|
||||
|
||||
if ( additionalProperties === true ) {
|
||||
obj.additionalProp1 = {}
|
||||
} else if ( additionalProperties ) {
|
||||
let additionalProps = objectify(additionalProperties)
|
||||
let additionalPropVal = sampleFromSchema(additionalProps)
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
obj["additionalProp" + i] = additionalPropVal
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
if(type === "array") {
|
||||
return [ sampleFromSchema(items) ]
|
||||
}
|
||||
|
||||
if(schema["enum"]) {
|
||||
if(schema["default"])
|
||||
return schema["default"]
|
||||
return normalizeArray(schema["enum"])[0]
|
||||
}
|
||||
|
||||
return primitive(schema)
|
||||
}
|
||||
|
||||
export const inferSchema = (thing) => {
|
||||
if(thing.schema)
|
||||
thing = thing.schema
|
||||
|
||||
if(thing.properties) {
|
||||
thing.type = "object"
|
||||
}
|
||||
|
||||
return thing // Hopefully this will have something schema like in it... `type` for example
|
||||
}
|
||||
|
||||
|
||||
export const sampleXmlFromSchema = (schema, config={}) => {
|
||||
let objectifySchema = objectify(schema)
|
||||
let { type, properties, additionalProperties, items, example } = objectifySchema
|
||||
let { includeReadOnly } = config
|
||||
let defaultValue = objectifySchema.default
|
||||
let res = {}
|
||||
let _attr = {}
|
||||
let { xml } = schema
|
||||
let { name, prefix, namespace } = xml
|
||||
let enumValue = objectifySchema.enum
|
||||
let displayName, value
|
||||
|
||||
if(!type) {
|
||||
if(properties || additionalProperties) {
|
||||
type = "object"
|
||||
} else if(items) {
|
||||
type = "array"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
name = name || "notagname"
|
||||
// add prefix to name if exists
|
||||
displayName = (prefix ? prefix + ":" : "") + name
|
||||
if ( namespace ) {
|
||||
//add prefix to namespace if exists
|
||||
let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns"
|
||||
_attr[namespacePrefix] = namespace
|
||||
}
|
||||
|
||||
if (type === "array") {
|
||||
if (items) {
|
||||
items.xml = items.xml || xml || {}
|
||||
items.xml.name = items.xml.name || xml.name
|
||||
|
||||
if (xml.wrapped) {
|
||||
res[displayName] = []
|
||||
if (Array.isArray(defaultValue)) {
|
||||
|
||||
defaultValue.forEach((v)=>{
|
||||
items.default = v
|
||||
res[displayName].push(sampleXmlFromSchema(items, config))
|
||||
})
|
||||
} else {
|
||||
res[displayName] = [sampleXmlFromSchema(items, config)]
|
||||
}
|
||||
|
||||
if (_attr) {
|
||||
res[displayName].push({_attr: _attr})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let _res = []
|
||||
|
||||
if (Array.isArray(defaultValue)) {
|
||||
defaultValue.forEach((v)=>{
|
||||
items.default = v
|
||||
_res.push(sampleXmlFromSchema(items, config))
|
||||
})
|
||||
return _res
|
||||
|
||||
}
|
||||
return sampleXmlFromSchema(items, config)
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "object") {
|
||||
let props = objectify(properties)
|
||||
res[displayName] = []
|
||||
example = example || {}
|
||||
|
||||
for (let propName in props) {
|
||||
if ( !props[propName].readOnly || includeReadOnly ) {
|
||||
props[propName].xml = props[propName].xml || {}
|
||||
|
||||
if (props[propName].xml.attribute) {
|
||||
let enumAttrVal = Array.isArray(props[propName].enum) && props[propName].enum[0]
|
||||
let attrExample = props[propName].example
|
||||
let attrDefault = props[propName].default
|
||||
_attr[props[propName].xml.name || propName] = attrExample!== undefined && attrExample
|
||||
|| example[propName] !== undefined && example[propName] || attrDefault !== undefined && attrDefault
|
||||
|| enumAttrVal || primitive(props[propName])
|
||||
} else {
|
||||
props[propName].xml.name = props[propName].xml.name || propName
|
||||
props[propName].example = props[propName].example !== undefined ? props[propName].example : example[propName]
|
||||
res[displayName].push(sampleXmlFromSchema(props[propName]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalProperties === true) {
|
||||
res[displayName].push({additionalProp: "Anything can be here"})
|
||||
} else if (additionalProperties) {
|
||||
res[displayName].push({additionalProp: primitive(additionalProperties)})
|
||||
}
|
||||
|
||||
if (_attr) {
|
||||
res[displayName].push({_attr: _attr})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if (example !== undefined) {
|
||||
value = example
|
||||
} else if (defaultValue !== undefined) {
|
||||
//display example if exists
|
||||
value = defaultValue
|
||||
} else if (Array.isArray(enumValue)) {
|
||||
//display enum first value
|
||||
value = enumValue[0]
|
||||
} else {
|
||||
//set default value
|
||||
value = primitive(schema)
|
||||
}
|
||||
|
||||
res[displayName] = _attr ? [{_attr: _attr}, value] : value
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export function createXMLExample(schema, config) {
|
||||
let json = sampleXmlFromSchema(schema, config)
|
||||
if (!json) { return }
|
||||
|
||||
return XML(json, { declaration: true, indent: "\t" })
|
||||
}
|
||||
|
||||
export const memoizedCreateXMLExample = memoizee(createXMLExample)
|
||||
|
||||
export const memoizedSampleFromSchema = memoizee(sampleFromSchema)
|
||||
5
src/core/plugins/samples/index.js
Normal file
5
src/core/plugins/samples/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as fn from "./fn"
|
||||
|
||||
export default function () {
|
||||
return { fn }
|
||||
}
|
||||
234
src/core/plugins/spec/actions.js
Normal file
234
src/core/plugins/spec/actions.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import YAML from "js-yaml"
|
||||
import serializeError from "serialize-error"
|
||||
|
||||
// Actions conform to FSA (flux-standard-actions)
|
||||
// {type: string,payload: Any|Error, meta: obj, error: bool}
|
||||
|
||||
export const UPDATE_SPEC = "spec_update_spec"
|
||||
export const UPDATE_URL = "spec_update_url"
|
||||
export const UPDATE_JSON = "spec_update_json"
|
||||
export const UPDATE_PARAM = "spec_update_param"
|
||||
export const VALIDATE_PARAMS = "spec_validate_param"
|
||||
export const SET_RESPONSE = "spec_set_response"
|
||||
export const SET_REQUEST = "spec_set_request"
|
||||
export const LOG_REQUEST = "spec_log_request"
|
||||
export const CLEAR_RESPONSE = "spec_clear_response"
|
||||
export const CLEAR_REQUEST = "spec_clear_request"
|
||||
export const ClEAR_VALIDATE_PARAMS = "spec_clear_validate_param"
|
||||
export const UPDATE_OPERATION_VALUE = "spec_update_operation_value"
|
||||
export const UPDATE_RESOLVED = "spec_update_resolved"
|
||||
export const SET_SCHEME = "set_scheme"
|
||||
|
||||
export function updateSpec(spec) {
|
||||
if(spec instanceof Error) {
|
||||
return {type: UPDATE_SPEC, error: true, payload: spec}
|
||||
}
|
||||
|
||||
if(typeof spec === "string") {
|
||||
return {
|
||||
type: UPDATE_SPEC,
|
||||
payload: spec.replace(/\t/g, " ") || ""
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: UPDATE_SPEC,
|
||||
payload: ""
|
||||
}
|
||||
}
|
||||
|
||||
export function updateResolved(spec) {
|
||||
return {
|
||||
type: UPDATE_RESOLVED,
|
||||
payload: spec
|
||||
}
|
||||
}
|
||||
|
||||
export function updateUrl(url) {
|
||||
return {type: UPDATE_URL, payload: url}
|
||||
}
|
||||
|
||||
export function updateJsonSpec(json) {
|
||||
if(!json || typeof json !== "object") {
|
||||
throw new Error("updateJson must only accept a simple JSON object")
|
||||
}
|
||||
return {type: UPDATE_JSON, payload: json}
|
||||
}
|
||||
|
||||
export const parseToJson = (str) => ({specActions, specSelectors, errActions}) => {
|
||||
let { specStr } = specSelectors
|
||||
|
||||
let json = null
|
||||
try {
|
||||
str = str || specStr()
|
||||
errActions.clear({ source: "parser" })
|
||||
json = YAML.safeLoad(str)
|
||||
} catch(e) {
|
||||
// TODO: push error to state
|
||||
console.error(e)
|
||||
return errActions.newSpecErr({
|
||||
source: "parser",
|
||||
level: "error",
|
||||
message: e.reason,
|
||||
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined
|
||||
})
|
||||
}
|
||||
return specActions.updateJsonSpec(json)
|
||||
}
|
||||
|
||||
export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }}) => {
|
||||
if(typeof(json) === "undefined") {
|
||||
json = specSelectors.specJson()
|
||||
}
|
||||
if(typeof(url) === "undefined") {
|
||||
url = specSelectors.url()
|
||||
}
|
||||
|
||||
let { getLineNumberForPath } = AST
|
||||
|
||||
let specStr = specSelectors.specStr()
|
||||
|
||||
return resolve({fetch, spec: json, baseDoc: url})
|
||||
.then( ({spec, errors}) => {
|
||||
errActions.clear({
|
||||
type: "thrown"
|
||||
})
|
||||
|
||||
if(errors.length > 0) {
|
||||
let preparedErrors = errors
|
||||
.map(err => {
|
||||
console.error(err)
|
||||
err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null
|
||||
err.path = err.fullPath ? err.fullPath.join(".") : null
|
||||
err.level = "error"
|
||||
err.type = "thrown"
|
||||
err.source = "resolver"
|
||||
Object.defineProperty(err, "message", { enumerable: true, value: err.message })
|
||||
return err
|
||||
})
|
||||
errActions.newThrownErrBatch(preparedErrors)
|
||||
}
|
||||
|
||||
return specActions.updateResolved(spec)
|
||||
})
|
||||
}
|
||||
|
||||
export const formatIntoYaml = () => ({specActions, specSelectors}) => {
|
||||
let { specStr } = specSelectors
|
||||
let { updateSpec } = specActions
|
||||
|
||||
try {
|
||||
let yaml = YAML.safeDump(YAML.safeLoad(specStr()), {indent: 2})
|
||||
updateSpec(yaml)
|
||||
} catch(e) {
|
||||
updateSpec(e)
|
||||
}
|
||||
}
|
||||
|
||||
export function changeParam( path, paramName, value, isXml ){
|
||||
return {
|
||||
type: UPDATE_PARAM,
|
||||
payload:{ path, value, paramName, isXml }
|
||||
}
|
||||
}
|
||||
|
||||
export function validateParams( payload ){
|
||||
return {
|
||||
type: VALIDATE_PARAMS,
|
||||
payload:{ pathMethod: payload }
|
||||
}
|
||||
}
|
||||
|
||||
export function clearValidateParams( payload ){
|
||||
return {
|
||||
type: ClEAR_VALIDATE_PARAMS,
|
||||
payload:{ pathMethod: payload }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeConsumesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
payload:{ path, value, key: "consumes_value" }
|
||||
}
|
||||
}
|
||||
|
||||
export function changeProducesValue(path, value) {
|
||||
return {
|
||||
type: UPDATE_OPERATION_VALUE,
|
||||
payload:{ path, value, key: "produces_value" }
|
||||
}
|
||||
}
|
||||
|
||||
export const setResponse = ( path, method, res ) => {
|
||||
return {
|
||||
payload: { path, method, res },
|
||||
type: SET_RESPONSE
|
||||
}
|
||||
}
|
||||
|
||||
export const setRequest = ( path, method, req ) => {
|
||||
return {
|
||||
payload: { path, method, req },
|
||||
type: SET_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// This is for debugging, remove this comment if you depend on this action
|
||||
export const logRequest = (req) => {
|
||||
return {
|
||||
payload: req,
|
||||
type: LOG_REQUEST
|
||||
}
|
||||
}
|
||||
|
||||
// Actually fire the request via fn.execute
|
||||
// (For debugging) and ease of testing
|
||||
export const executeRequest = (req) => ({fn, specActions, errActions}) => {
|
||||
let { pathName, method } = req
|
||||
let parsedRequest = Object.assign({}, req)
|
||||
if ( pathName && method ) {
|
||||
parsedRequest.operationId = method.toLowerCase() + "-" + pathName
|
||||
}
|
||||
parsedRequest = fn.buildRequest(parsedRequest)
|
||||
specActions.setRequest(req.pathName, req.method, parsedRequest)
|
||||
|
||||
return fn.execute(req)
|
||||
.then( fn.serializeRes )
|
||||
.then( res => specActions.setResponse(req.pathName, req.method, res))
|
||||
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) )
|
||||
}
|
||||
|
||||
|
||||
// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat
|
||||
export const execute = ( { path, method, ...extras }={} ) => (system) => {
|
||||
let { fn:{fetch}, specSelectors, specActions } = system
|
||||
let spec = specSelectors.spec().toJS()
|
||||
let scheme = specSelectors.operationScheme(path, method)
|
||||
let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS()
|
||||
let isXml = /xml/i.test(requestContentType)
|
||||
let parameters = specSelectors.parameterValues([path, method], isXml).toJS()
|
||||
|
||||
return specActions.executeRequest({fetch, spec, pathName: path, method, parameters, requestContentType, scheme, responseContentType, ...extras })
|
||||
}
|
||||
|
||||
export function clearResponse (path, method) {
|
||||
return {
|
||||
type: CLEAR_RESPONSE,
|
||||
payload:{ path, method }
|
||||
}
|
||||
}
|
||||
|
||||
export function clearRequest (path, method) {
|
||||
return {
|
||||
type: CLEAR_REQUEST,
|
||||
payload:{ path, method }
|
||||
}
|
||||
}
|
||||
|
||||
export function setScheme (scheme, path, method) {
|
||||
return {
|
||||
type: SET_SCHEME,
|
||||
payload: { scheme, path, method }
|
||||
}
|
||||
}
|
||||
17
src/core/plugins/spec/index.js
Normal file
17
src/core/plugins/spec/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import reducers from "./reducers"
|
||||
import * as actions from "./actions"
|
||||
import * as selectors from "./selectors"
|
||||
import * as wrapActions from "./wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: {
|
||||
wrapActions,
|
||||
reducers,
|
||||
actions,
|
||||
selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
src/core/plugins/spec/reducers.js
Normal file
123
src/core/plugins/spec/reducers.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { fromJS } from "immutable"
|
||||
import { fromJSOrdered, validateParam } from "core/utils"
|
||||
import win from "../../window"
|
||||
|
||||
import {
|
||||
UPDATE_SPEC,
|
||||
UPDATE_URL,
|
||||
UPDATE_JSON,
|
||||
UPDATE_PARAM,
|
||||
VALIDATE_PARAMS,
|
||||
SET_RESPONSE,
|
||||
SET_REQUEST,
|
||||
UPDATE_RESOLVED,
|
||||
UPDATE_OPERATION_VALUE,
|
||||
CLEAR_RESPONSE,
|
||||
CLEAR_REQUEST,
|
||||
ClEAR_VALIDATE_PARAMS,
|
||||
SET_SCHEME
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
|
||||
[UPDATE_SPEC]: (state, action) => {
|
||||
return (typeof action.payload === "string")
|
||||
? state.set("spec", action.payload)
|
||||
: state
|
||||
},
|
||||
|
||||
[UPDATE_URL]: (state, action) => {
|
||||
return state.set("url", action.payload+"")
|
||||
},
|
||||
|
||||
[UPDATE_JSON]: (state, action) => {
|
||||
return state.set("json", fromJSOrdered(action.payload))
|
||||
},
|
||||
|
||||
[UPDATE_RESOLVED]: (state, action) => {
|
||||
return state.setIn(["resolved"], fromJSOrdered(action.payload))
|
||||
},
|
||||
|
||||
[UPDATE_PARAM]: ( state, {payload} ) => {
|
||||
let { path, paramName, value, isXml } = payload
|
||||
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
|
||||
let index = parameters.findIndex( p => p.get( "name" ) === paramName )
|
||||
if (!(value instanceof win.File)) {
|
||||
value = fromJSOrdered( value )
|
||||
}
|
||||
return parameters.setIn( [ index, isXml ? "value_xml" : "value" ], value)
|
||||
})
|
||||
},
|
||||
|
||||
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
|
||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
|
||||
let parameters = operation.get("parameters")
|
||||
let isXml = /xml/i.test(operation.get("consumes_value"))
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
|
||||
return parameters.withMutations( parameters => {
|
||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
|
||||
let errors = validateParam(parameters.get(i), isXml)
|
||||
parameters.setIn([i, "errors"], fromJS(errors))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
[ClEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
|
||||
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
|
||||
let parameters = operation.get("parameters")
|
||||
|
||||
return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
|
||||
return parameters.withMutations( parameters => {
|
||||
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
|
||||
parameters.setIn([i, "errors"], fromJS({}))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
[SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{
|
||||
let result
|
||||
if ( res.error ) {
|
||||
result = Object.assign({error: true}, res.err)
|
||||
} else {
|
||||
result = res
|
||||
}
|
||||
|
||||
let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) )
|
||||
|
||||
// ImmutableJS messes up Blob. Needs to reset its value.
|
||||
if (res.data instanceof win.Blob) {
|
||||
newState = newState.setIn( [ "responses", path, method, "text" ], res.data)
|
||||
}
|
||||
return newState
|
||||
},
|
||||
|
||||
[SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{
|
||||
return state.setIn( [ "requests", path, method ], fromJSOrdered(req))
|
||||
},
|
||||
|
||||
[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
|
||||
return state.setIn(["resolved", "paths", ...path, key], fromJS(value))
|
||||
},
|
||||
|
||||
[CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{
|
||||
return state.deleteIn( [ "responses", path, method ])
|
||||
},
|
||||
|
||||
[CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{
|
||||
return state.deleteIn( [ "requests", path, method ])
|
||||
},
|
||||
|
||||
[SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{
|
||||
if ( path && method ) {
|
||||
return state.setIn( [ "scheme", path, method ], scheme)
|
||||
}
|
||||
|
||||
if (!path && !method) {
|
||||
return state.setIn( [ "scheme", "_defaultScheme" ], scheme)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
318
src/core/plugins/spec/selectors.js
Normal file
318
src/core/plugins/spec/selectors.js
Normal file
@@ -0,0 +1,318 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { fromJS, Set, Map, List } from "immutable"
|
||||
|
||||
const DEFAULT_TAG = "default"
|
||||
|
||||
const OPERATION_METHODS = ["get", "put", "post", "delete", "options", "head", "patch"]
|
||||
|
||||
const state = state => {
|
||||
return state || Map()
|
||||
}
|
||||
|
||||
export const lastError = createSelector(
|
||||
state,
|
||||
spec => spec.get("lastError")
|
||||
)
|
||||
|
||||
export const url = createSelector(
|
||||
state,
|
||||
spec => spec.get("url")
|
||||
)
|
||||
|
||||
export const specStr = createSelector(
|
||||
state,
|
||||
spec => spec.get("spec") || ""
|
||||
)
|
||||
|
||||
export const specSource = createSelector(
|
||||
state,
|
||||
spec => spec.get("specSource") || "not-editor"
|
||||
)
|
||||
|
||||
export const specJson = createSelector(
|
||||
state,
|
||||
spec => spec.get("json", Map())
|
||||
)
|
||||
|
||||
export const specResolved = createSelector(
|
||||
state,
|
||||
spec => spec.get("resolved", Map())
|
||||
)
|
||||
|
||||
// Default Spec ( as an object )
|
||||
export const spec = state => {
|
||||
let res = specResolved(state)
|
||||
if(res.count() < 1)
|
||||
res = specJson(state)
|
||||
return res
|
||||
}
|
||||
|
||||
export const info = createSelector(
|
||||
spec,
|
||||
spec => returnSelfOrNewMap(spec && spec.get("info"))
|
||||
)
|
||||
|
||||
export const externalDocs = createSelector(
|
||||
spec,
|
||||
spec => returnSelfOrNewMap(spec && spec.get("externalDocs"))
|
||||
)
|
||||
|
||||
export const version = createSelector(
|
||||
info,
|
||||
info => info && info.get("version")
|
||||
)
|
||||
|
||||
export const semver = createSelector(
|
||||
version,
|
||||
version => /v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(version).slice(1)
|
||||
)
|
||||
|
||||
export const paths = createSelector(
|
||||
spec,
|
||||
spec => spec.get("paths")
|
||||
)
|
||||
|
||||
export const operations = createSelector(
|
||||
paths,
|
||||
paths => {
|
||||
if(!paths || paths.size < 1)
|
||||
return List()
|
||||
|
||||
let list = List()
|
||||
|
||||
if(!paths || !paths.forEach) {
|
||||
return List()
|
||||
}
|
||||
|
||||
paths.forEach((path, pathName) => {
|
||||
if(!path || !path.forEach) {
|
||||
return {}
|
||||
}
|
||||
path.forEach((operation, method) => {
|
||||
if(OPERATION_METHODS.indexOf(method) === -1) {
|
||||
return
|
||||
}
|
||||
list = list.push(fromJS({
|
||||
path: pathName,
|
||||
method,
|
||||
operation,
|
||||
id: `${method}-${pathName}`
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
)
|
||||
|
||||
export const consumes = createSelector(
|
||||
spec,
|
||||
spec => Set(spec.get("consumes"))
|
||||
)
|
||||
|
||||
export const produces = createSelector(
|
||||
spec,
|
||||
spec => Set(spec.get("produces"))
|
||||
)
|
||||
|
||||
export const security = createSelector(
|
||||
spec,
|
||||
spec => spec.get("security", List())
|
||||
)
|
||||
|
||||
export const securityDefinitions = createSelector(
|
||||
spec,
|
||||
spec => spec.get("securityDefinitions")
|
||||
)
|
||||
|
||||
|
||||
export const findDefinition = ( state, name ) => {
|
||||
return specResolved(state).getIn(["definitions", name], null)
|
||||
}
|
||||
|
||||
export const definitions = createSelector(
|
||||
spec,
|
||||
spec => spec.get("definitions") || Map()
|
||||
)
|
||||
|
||||
export const basePath = createSelector(
|
||||
spec,
|
||||
spec => spec.get("basePath")
|
||||
)
|
||||
|
||||
export const host = createSelector(
|
||||
spec,
|
||||
spec => spec.get("host")
|
||||
)
|
||||
|
||||
export const schemes = createSelector(
|
||||
spec,
|
||||
spec => spec.get("schemes", Map())
|
||||
)
|
||||
|
||||
export const operationsWithRootInherited = createSelector(
|
||||
operations,
|
||||
consumes,
|
||||
produces,
|
||||
(operations, consumes, produces) => {
|
||||
return operations.map( ops => ops.update("operation", op => {
|
||||
if(op) {
|
||||
if(!Map.isMap(op)) { return }
|
||||
return op.withMutations( op => {
|
||||
if ( !op.get("consumes") ) {
|
||||
op.update("consumes", a => Set(a).merge(consumes))
|
||||
}
|
||||
if ( !op.get("produces") ) {
|
||||
op.update("produces", a => Set(a).merge(produces))
|
||||
}
|
||||
return op
|
||||
})
|
||||
} else {
|
||||
// return something with Immutable methods
|
||||
return Map()
|
||||
}
|
||||
|
||||
}))
|
||||
}
|
||||
)
|
||||
|
||||
export const tags = createSelector(
|
||||
spec,
|
||||
json => json.get("tags", List())
|
||||
)
|
||||
|
||||
export const tagDetails = (state, tag) => {
|
||||
let currentTags = tags(state) || List()
|
||||
return currentTags.filter(Map.isMap).find(t => t.get("name") === tag, Map())
|
||||
}
|
||||
|
||||
export const operationsWithTags = createSelector(
|
||||
operationsWithRootInherited,
|
||||
operations => {
|
||||
return operations.reduce( (taggedMap, op) => {
|
||||
let tags = Set(op.getIn(["operation","tags"]))
|
||||
if(tags.count() < 1)
|
||||
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))
|
||||
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )
|
||||
}, Map())
|
||||
}
|
||||
)
|
||||
|
||||
export const taggedOperations = createSelector(
|
||||
state,
|
||||
operationsWithTags,
|
||||
(state, tagMap) => {
|
||||
return tagMap.map((ops, tag) => Map({tagDetails: tagDetails(state, tag), operations: ops}))
|
||||
}
|
||||
)
|
||||
|
||||
export const responses = createSelector(
|
||||
state,
|
||||
state => state.get( "responses", Map() )
|
||||
)
|
||||
|
||||
export const requests = createSelector(
|
||||
state,
|
||||
state => state.get( "requests", Map() )
|
||||
)
|
||||
|
||||
export const responseFor = (state, path, method) => {
|
||||
return responses(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const requestFor = (state, path, method) => {
|
||||
return requests(state).getIn([path, method], null)
|
||||
}
|
||||
|
||||
export const allowTryItOutFor = (state, path, method ) => {
|
||||
// This is just a hook for now.
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the parameter value by parameter name
|
||||
export function getParameter(state, pathMethod, name) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.filter( (p) => {
|
||||
return Map.isMap(p) && p.get("name") === name
|
||||
}).first()
|
||||
}
|
||||
|
||||
export const hasHost = createSelector(
|
||||
spec,
|
||||
spec => {
|
||||
const host = spec.get("host")
|
||||
return typeof host === "string" && host.length > 0 && host[0] !== "/"
|
||||
}
|
||||
)
|
||||
|
||||
// Get the parameter values, that the user filled out
|
||||
export function parameterValues(state, pathMethod, isXml) {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
return params.reduce( (hash, p) => {
|
||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
|
||||
return hash.set(p.get("name"), value)
|
||||
}, fromJS({}))
|
||||
}
|
||||
|
||||
// True if any parameter includes `in: ?`
|
||||
export function parametersIncludeIn(parameters, inValue="") {
|
||||
if(List.isList(parameters)) {
|
||||
return parameters.some( p => Map.isMap(p) && p.get("in") === inValue )
|
||||
}
|
||||
}
|
||||
|
||||
// True if any parameter includes `type: ?`
|
||||
export function parametersIncludeType(parameters, typeValue="") {
|
||||
if(List.isList(parameters)) {
|
||||
return parameters.some( p => Map.isMap(p) && p.get("type") === typeValue )
|
||||
}
|
||||
}
|
||||
|
||||
// Get the consumes/produces value that the user selected
|
||||
export function contentTypeValues(state, pathMethod) {
|
||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
|
||||
const parameters = op.get("parameters") || new List()
|
||||
const requestContentType = (
|
||||
parametersIncludeType(parameters, "file") ? "multipart/form-data"
|
||||
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded"
|
||||
: op.get("consumes_value")
|
||||
)
|
||||
|
||||
|
||||
return fromJS({
|
||||
requestContentType,
|
||||
responseContentType: op.get("produces_value")
|
||||
})
|
||||
}
|
||||
|
||||
// Get the consumes/produces by path
|
||||
export function operationConsumes(state, pathMethod) {
|
||||
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
|
||||
}
|
||||
|
||||
export const operationScheme = ( state, path, method ) => {
|
||||
return state.getIn(["scheme", path, method]) || state.getIn(["scheme", "_defaultScheme"]) || "http"
|
||||
}
|
||||
|
||||
export const canExecuteScheme = ( state, path, method ) => {
|
||||
return ["http", "https"].indexOf(operationScheme(state, path, method)) > -1
|
||||
}
|
||||
|
||||
export const validateBeforeExecute = ( state, pathMethod ) => {
|
||||
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let isValid = true
|
||||
|
||||
params.forEach( (p) => {
|
||||
let errors = p.get("errors")
|
||||
if ( errors && errors.count() ) {
|
||||
isValid = false
|
||||
}
|
||||
})
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
function returnSelfOrNewMap(obj) {
|
||||
// returns obj if obj is an Immutable map, else returns a new Map
|
||||
return Map.isMap(obj) ? obj : new Map()
|
||||
}
|
||||
15
src/core/plugins/spec/wrap-actions.js
Normal file
15
src/core/plugins/spec/wrap-actions.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const updateSpec = (ori, {specActions}) => (...args) => {
|
||||
ori(...args)
|
||||
specActions.parseToJson(...args)
|
||||
}
|
||||
|
||||
export const updateJsonSpec = (ori, {specActions}) => (...args) => {
|
||||
ori(...args)
|
||||
specActions.resolveSpec(...args)
|
||||
}
|
||||
|
||||
// Log the request ( just for debugging, shouldn't affect prod )
|
||||
export const executeRequest = (ori, { specActions }) => (req) => {
|
||||
specActions.logRequest(req)
|
||||
return ori(req)
|
||||
}
|
||||
17
src/core/plugins/split-pane-mode/components/index.js
Normal file
17
src/core/plugins/split-pane-mode/components/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { pascalCaseFilename } from "core/utils"
|
||||
|
||||
const request = require.context(".", true, /\.jsx?$/)
|
||||
|
||||
request.keys().forEach( function( key ){
|
||||
if( key === "./index.js" ) {
|
||||
return
|
||||
}
|
||||
|
||||
// if( key.slice(2).indexOf("/") > -1) {
|
||||
// // skip files in subdirs
|
||||
// return
|
||||
// }
|
||||
|
||||
let mod = request(key)
|
||||
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import React, { PropTypes } from "react"
|
||||
import SplitPane from "react-split-pane"
|
||||
import "./split-pane-mode.less"
|
||||
|
||||
const MODE_KEY = ["split-pane-mode"]
|
||||
const MODE_LEFT = "left"
|
||||
const MODE_RIGHT = "right"
|
||||
const MODE_BOTH = "both" // or anything other than left/right
|
||||
|
||||
export default class SplitPaneMode extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
threshold: PropTypes.number,
|
||||
|
||||
children: PropTypes.array,
|
||||
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
layoutActions: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
threshold: 100, // in pixels
|
||||
children: [],
|
||||
};
|
||||
|
||||
onDragFinished = () => {
|
||||
let { threshold, layoutActions } = this.props
|
||||
let { position, draggedSize } = this.refs.splitPane.state
|
||||
this.draggedSize = draggedSize
|
||||
|
||||
let nearLeftEdge = position <= threshold
|
||||
let nearRightEdge = draggedSize <= threshold
|
||||
|
||||
layoutActions
|
||||
.changeMode(MODE_KEY, (
|
||||
nearLeftEdge
|
||||
? MODE_RIGHT : nearRightEdge
|
||||
? MODE_LEFT : MODE_BOTH
|
||||
))
|
||||
}
|
||||
|
||||
sizeFromMode = (mode, defaultSize) => {
|
||||
if(mode === MODE_LEFT) {
|
||||
this.draggedSize = null
|
||||
return "0px"
|
||||
} else if (mode === MODE_RIGHT) {
|
||||
this.draggedSize = null
|
||||
return "100%"
|
||||
}
|
||||
// mode === "both"
|
||||
return this.draggedSize || defaultSize
|
||||
}
|
||||
|
||||
render() {
|
||||
let { children, layoutSelectors } = this.props
|
||||
|
||||
const mode = layoutSelectors.whatMode(MODE_KEY)
|
||||
const left = mode === MODE_RIGHT ? <noscript/> : children[0]
|
||||
const right = mode === MODE_LEFT ? <noscript/> : children[1]
|
||||
const size = this.sizeFromMode(mode, '50%')
|
||||
|
||||
return (
|
||||
<SplitPane
|
||||
disabledClass={''}
|
||||
ref={'splitPane'}
|
||||
split='vertical'
|
||||
defaultSize={'50%'}
|
||||
primary="second"
|
||||
minSize={0}
|
||||
size={size}
|
||||
onDragFinished={this.onDragFinished}
|
||||
allowResize={mode !== MODE_LEFT && mode !== MODE_RIGHT }
|
||||
resizerStyle={{"flex": "0 0 auto", "position": "relative"}}
|
||||
>
|
||||
{ left }
|
||||
{ right }
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
.swagger-ui {
|
||||
.Resizer.vertical.disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user