Commit 0cf244c5 authored by 郭勇志's avatar 郭勇志

token验证

parent 65bc1e6b
...@@ -49,7 +49,7 @@ return [ ...@@ -49,7 +49,7 @@ return [
'class'=>'yii\rest\UrlRule', 'class'=>'yii\rest\UrlRule',
'controller'=>['swagger','v1/shop/branch/branch','v1/user/user'], 'controller'=>['swagger','v1/shop/branch/branch','v1/user/user'],
'extraPatterns'=>[ 'extraPatterns'=>[
'GET test'=>'test', 'GET,OPTIONS test'=>'test',
'GET swagger'=>'swagger', 'GET swagger'=>'swagger',
'POST login'=>'login', 'POST login'=>'login',
], ],
......
...@@ -6,6 +6,8 @@ use yii\web\Response; ...@@ -6,6 +6,8 @@ use yii\web\Response;
use yii\rest\ActiveController; use yii\rest\ActiveController;
use yii\filters\auth\CompositeAuth; use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBearerAuth; use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
use yii\filters\Cors;
/** /**
* Base controller * Base controller
* 基类 * 基类
...@@ -19,19 +21,45 @@ use yii\filters\auth\HttpBearerAuth; ...@@ -19,19 +21,45 @@ use yii\filters\auth\HttpBearerAuth;
* ) * )
* ) * )
*/ */
/**
* @OA\SecurityScheme(
* securityScheme="Authorization",
* type="apiKey",
* in="header",
* name="Authorization"
* )
*/
class BaseController extends ActiveController class BaseController extends ActiveController
{ {
public function behaviors() public function behaviors()
{ {
$behaviors = parent::behaviors(); $behaviors = parent::behaviors();
// 跨域请求配置
$behaviors = array_merge(
[
'cors'=>[
'class' => Cors::className(),
'cors' => [
'Origin' => ['http://localhost:8080','http://192.168.188.207:8080'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true,
'Access-Control-Max-Age' => 86400,
'Access-Control-Expose-Headers' => ['*'],
],
]
],$behaviors);
// 格式化输出请求
$behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_JSON; $behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_JSON;
$behaviors['authenticator'] = [ // 配置认证方式(两种都行)
'class' => CompositeAuth::className(), $behaviors['HttpBearerAuth']=[
'class' => HttpBearerAuth::className(), 'class' => HttpBearerAuth::className(),
'optional' => [ 'optional' => ['login'],
'login',
],
]; ];
// $behaviors['QueryParamAuth']=[
// 'class' => QueryParamAuth::className(),
// 'optional' => ['login']
// ];
return $behaviors; return $behaviors;
} }
} }
...@@ -20,7 +20,6 @@ class BranchController extends BaseController ...@@ -20,7 +20,6 @@ class BranchController extends BaseController
* summary="swagger事例", * summary="swagger事例",
* operationId="returnGetParam", * operationId="returnGetParam",
* @OA\Parameter(name="param",in="query",required=true,@OA\Schema(type="string")), * @OA\Parameter(name="param",in="query",required=true,@OA\Schema(type="string")),
* @OA\Parameter(name="Authorization",in="header",required=true,@OA\Schema(type="string")),
* @OA\Response(response="200",description="OK。一切正常"), * @OA\Response(response="200",description="OK。一切正常"),
* @OA\Response(response="201",description="响应 POST 请求时成功创建一个资源"), * @OA\Response(response="201",description="响应 POST 请求时成功创建一个资源"),
* @OA\Response(response="204",description="该请求被成功处理,响应不包含正文内容 (类似 DELETE 请求)"), * @OA\Response(response="204",description="该请求被成功处理,响应不包含正文内容 (类似 DELETE 请求)"),
...@@ -34,10 +33,10 @@ class BranchController extends BaseController ...@@ -34,10 +33,10 @@ class BranchController extends BaseController
* @OA\Response(response="422",description="数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息"), * @OA\Response(response="422",description="数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息"),
* @OA\Response(response="429",description="请求过多。 由于限速请求被拒绝"), * @OA\Response(response="429",description="请求过多。 由于限速请求被拒绝"),
* @OA\Response(response="500",description="内部服务器错误。 这可能是由于内部程序错误引起的"), * @OA\Response(response="500",description="内部服务器错误。 这可能是由于内部程序错误引起的"),
* security={{"Authorization": {}}}
* *
* ) * )
*/ */
public function actionTest() public function actionTest()
{ {
$get=Yii::$app->request->get(); $get=Yii::$app->request->get();
......
...@@ -15,28 +15,20 @@ class UserController extends BaseController ...@@ -15,28 +15,20 @@ class UserController extends BaseController
/** /**
* @OA\Post( * @OA\Post(
* path="/guoyongzhi/weiShopNew/backend/web/v1/user/users/login", * path="/guoyongzhi/weiShopNew/backend/web/v1/user/users/login",
* tags={"登陆接口"}, * tags={"用户&员工接口"},
* summary="登陆接口", * summary="后台登陆接口",
* description="后台登陆接口",
* @OA\RequestBody( * @OA\RequestBody(
* @OA\MediaType( * @OA\MediaType(
* mediaType="application/json", * mediaType="application/json",
* @OA\Schema( * @OA\Schema(
* @OA\Property( * @OA\Property(property="CODE",type="string"),
* property="CODE", * @OA\Property(property="PASSWORD",type="string"),
* type="string"
* ),
* @OA\Property(
* property="PASSWORD",
* type="string"
* ),
* example={"CODE": 1234567890, "PASSWORD": "123"} * example={"CODE": 1234567890, "PASSWORD": "123"}
* ) * )
* ) * )
* ), * ),
* @OA\Response( * @OA\Response(response=200,description="成功时返回access-token")
* response=200,
* description="OK"
* )
* ) * )
*/ */
public function actionLogin() public function actionLogin()
......
...@@ -49,11 +49,19 @@ ...@@ -49,11 +49,19 @@
plugins: [ plugins: [
SwaggerUIBundle.plugins.DownloadUrl SwaggerUIBundle.plugins.DownloadUrl
], ],
layout: "StandaloneLayout" layout: "StandaloneLayout",
// requestInterceptor: (req) => {
// if (! req.loadSpec) {
// console.log(req);
// req.headers.Authorization = "Bearer " + '8svS-f3OWrI-pmBy9SNNsmrEK2oTG8X3';
// }
// return req;
// }
}) })
// End Swagger UI call region // End Swagger UI call region
window.ui = ui window.ui = ui
} }
</script> </script>
</body> </body>
......
{
"presets": [
[
"@babel/env",
{
"targets": {
"browsers": [
/* benefit of C/S/FF/Edge only? */
"> 1%",
"last 2 versions",
"Firefox ESR",
"not dead",
]
},
"useBuiltIns": "entry",
"corejs": "2"
}
],
"@babel/preset-react"
],
"plugins": [
["@babel/plugin-transform-runtime", {
"corejs": "2"
}],
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-optional-chaining",
["transform-react-remove-prop-types", {
"additionalLibraries": ["react-immutable-proptypes"]
}],
[
"babel-plugin-module-resolver",
{
"alias": {
"root": ".",
"components": "./src/core/components",
"containers": "./src/core/containers",
"core": "./src/core",
"plugins": "./src/plugins",
"img": "./src/img",
"corePlugins": "./src/core/plugins",
"less": "./src/less",
}
}
]
]
}
/.git
/.github
/dev-helpers
/docs
/src
/swagger-ui-dist-package
/test
/node_modules
\ No newline at end of file
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
parser: babel-eslint
env:
browser: true
node: true
es6: true
parserOptions:
ecmaFeatures:
jsx: true
extends:
- eslint:recommended
- plugin:react/recommended
plugins:
- react
- mocha
- import
settings:
react:
pragma: React
version: '15.0'
rules:
semi: [2, never]
strict: 0
quotes: [2, double, { allowTemplateLiterals: true }]
no-unused-vars: 2
no-multi-spaces: 1
camelcase: 1
no-use-before-define: [2, nofunc]
no-underscore-dangle: 0
no-unused-expressions: 1
comma-dangle: 0
no-console: [2, { allow: [warn, error] }]
react/jsx-no-bind: 1
react/jsx-no-target-blank: 2
react/display-name: 0
mocha/no-exclusive-tests: 2
import/no-extraneous-dependencies: 2
react/jsx-filename-extension: 2
\ No newline at end of file
---
name: Bug report
about: Report an issue you're experiencing
---
<!---
Thanks for filing a bug report! 😄
Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and
contact us at security@swagger.io instead!
Search open/closed issues before submitting!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try
to offer support here for your use case, but we can't offer help with projects
that use Swagger-UI indirectly, like Springfox or swagger-node.
Likewise, we can't accept bugs in the Swagger/OpenAPI specifications
themselves, or anything that violates the specifications.
-->
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!--
Provide us with a way to see what you're seeing,
so that we can fix your issue.
-->
Example Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Describe the bug you're encountering
<!-- A clear and concise description of what the bug is. -->
### To reproduce...
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Additional context or thoughts
<!-- Add any other context about the problem here. -->
---
name: Feature request
about: Suggest an new feature or enhancement for this project
---
### Content & configuration
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Is your feature request related to a problem?
<!--
Please provide a clear and concise description of what the problem is.
"I'm always frustrated when..."
-->
### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
### Describe alternatives you've considered
<!--
A clear and concise description of any alternative solutions or features
you've considered.
-->
### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
---
name: Support
about: Ask a question or request help with your implementation.
---
<!--
We can only offer support for Swagger-UI itself.
If you're having a problem with a library that uses Swagger-UI
(for example, Springfox or swagger-node), please open an issue
in that project's repository instead.
-->
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!-- Provide us with a way to see what you're seeing, so that we can help. -->
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Screenshots
<!-- If applicable, add screenshots to help give context to your problem. -->
### How can we help?
<!-- Your question or problem goes here! -->
daysUntilLock: 365
skipCreatedBefore: 2017-03-29 # initial release of Swagger UI 3.0.0
exemptLabels: []
lockLabel: "locked-by: lock-bot"
setLockReason: false
only: issues
lockComment: false
# lockComment: |
# Locking due to inactivity.
# This is done to avoid resurrecting old issues and bumping long threads with new, possibly unrelated content.
# If you think you're experiencing something similar to what you've found here: please [open a new issue](https://github.com/swagger-api/swagger-ui/issues/new/choose), follow the template, and reference this issue in your report.
# Thanks!
<!--- Provide a general summary of your changes in the Title above -->
### Description
<!--- Describe your changes in detail -->
### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- Use the magic "Fixes #1234" format, so the issues are -->
<!--- automatically closed when this PR is merged. -->
### How Has This Been Tested?
<!--- Please describe in detail how you manually tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
### Screenshots (if appropriate):
## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
### My PR contains...
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] No code changes (`src/` is unmodified: changes to documentation, CI, metadata, etc.)
- [ ] Dependency changes (any modification to dependencies in `package.json`)
- [ ] Bug fixes (non-breaking change which fixes an issue)
- [ ] Improvements (misc. changes to existing features)
- [ ] Features (non-breaking change which adds functionality)
### My changes...
- [ ] are breaking changes to a public API (config options, System API, major UI change, etc).
- [ ] are breaking changes to a private API (Redux, component props, utility functions, etc.).
- [ ] are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
- [ ] are not breaking changes.
### Documentation
- [ ] My changes do not require a change to the project documentation.
- [ ] My changes require a change to the project documentation.
- [ ] If yes to above: I have updated the documentation accordingly.
### Automated tests
- [ ] My changes can not or do not need to be tested.
- [ ] My changes can and should be tested by unit and/or integration tests.
- [ ] If yes to above: I have added tests to cover my changes.
- [ ] If yes to above: I have taken care to cover edge cases in my tests.
- [ ] All new and existing tests passed.
node_modules
.idea
.vscode
.deps_check
.DS_Store
.nyc_output
npm-debug.log*
.eslintcache
*.iml
selenium-debug.log
test/e2e/db.json
docs/_book
flavors/**/dist/*
# Cypress
test/e2e-cypress/screenshots
test/e2e-cypress/videos
\ No newline at end of file
*
*/
!README.md
!package.json
!dist/swagger-ui.js
!dist/swagger-ui.js.map
!dist/swagger-ui-standalone-preset.js
!dist/swagger-ui-standalone-preset.js.map
!dist/swagger-ui.css
!dist/swagger-ui.css.map
!dist/oauth2-redirect.html
semi: false
trailingComma: es5
endOfLine: lf
requirePragma: true
insertPragma: true
# Looking for information on environment variables?
# We don't declare them here — take a look at our docs.
# https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md
FROM nginx:1.17-alpine
RUN apk --no-cache add nodejs
LABEL maintainer="fehguy"
ENV API_KEY "**None**"
ENV SWAGGER_JSON "/app/swagger.json"
ENV PORT 8080
ENV BASE_URL ""
COPY ./docker/nginx.conf ./docker/cors.conf /etc/nginx/
# copy swagger files to the `/js` folder
COPY ./dist/* /usr/share/nginx/html/
COPY ./docker/run.sh /usr/share/nginx/
COPY ./docker/configurator /usr/share/nginx/configurator
RUN chmod +x /usr/share/nginx/run.sh && \
chmod -R a+rw /usr/share/nginx && \
chmod -R a+rw /etc/nginx && \
chmod -R a+rw /var && \
chmod -R a+rw /var/run
EXPOSE 8080
CMD ["sh", "/usr/share/nginx/run.sh"]
Copyright 2019 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.
# <img src="https://raw.githubusercontent.com/swagger-api/swagger.io/wordpress/images/assets/SWU-logo-clr.png" height="80">
[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui)
[![Build Status](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/badge/icon?subject=jenkins%20build)](https://jenkins.swagger.io/view/OSS%20-%20JavaScript/job/oss-swagger-ui-master/)
[![npm audit](https://jenkins.swagger.io/buildStatus/icon?job=oss-swagger-ui-security-audit&subject=npm%20audit)](https://jenkins.swagger.io/job/oss-swagger-ui-security-audit/lastBuild/console)
![total GitHub contributors](https://img.shields.io/github/contributors-anon/swagger-api/swagger-ui.svg)
![monthly npm installs](https://img.shields.io/npm/dm/swagger-ui.svg?label=npm%20downloads)
![total docker pulls](https://img.shields.io/docker/pulls/swaggerapi/swagger-ui.svg)
![monthly packagist installs](https://img.shields.io/packagist/dm/swagger-api/swagger-ui.svg?label=packagist%20installs)
![gzip size](https://img.shields.io/bundlephobia/minzip/swagger-ui.svg?label=gzip%20size)
**👉🏼 Want to score an easy open-source contribution?** Check out our [Good first issue](https://github.com/swagger-api/swagger-ui/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%22) label.
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).
This repository publishes to three different NPM modules:
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
* [swagger-ui-react](https://www.npmjs.com/package/swagger-ui-react) is Swagger UI packaged as a React component for use in React applications.
We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.
## Compatibility
The OpenAPI Specification has undergone 5 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
------------------ | ------------ | -------------------------- | -----
3.18.3 | 2018-08-03 | 2.0, 3.0 | [tag v3.18.3](https://github.com/swagger-api/swagger-ui/tree/v3.18.3)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
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)
## Documentation
#### Usage
- [Installation](docs/usage/installation.md)
- [Configuration](docs/usage/configuration.md)
- [CORS](docs/usage/cors.md)
- [OAuth2](docs/usage/oauth2.md)
- [Deep Linking](docs/usage/deep-linking.md)
- [Limitations](docs/usage/limitations.md)
- [Version detection](docs/usage/version-detection.md)
#### Customization
- [Overview](docs/customization/overview.md)
- [Plugin API](docs/customization/plugin-api.md)
- [Custom layout](docs/customization/custom-layout.md)
#### Development
- [Setting up](docs/development/setting-up.md)
- [Scripts](docs/development/scripts.md)
##### Integration Tests
You will need JDK of version 7 or higher as instructed here
https://nightwatchjs.org/gettingstarted/#selenium-server-setup
Integration tests can be run locally with `npm run e2e` - be sure you aren't running a dev server when testing!
### Browser support
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11.
### Known Issues
To help with the migration, here are the currently known issues with 3.X. This list will update regularly, and will not include features that were not implemented in previous versions.
- Only part of the parameters previously supported are available.
- The JSON Form Editor is not implemented.
- Support for `collectionFormat` is partial.
- l10n (translations) is not implemented.
- Relative path support for external files is not implemented.
## Security contact
Please disclose any security-related issues or vulnerabilities by emailing [security@swagger.io](mailto:security@swagger.io), instead of using the public issue tracker.
## License
Copyright 2019 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](https://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.
# Security Policy
If you believe you've found an exploitable security issue in Swagger UI,
**please don't create a public issue**.
## Supported versions
This is the list of versions of `swagger-ui` which are
currently being supported with security updates.
| Version | Supported | Notes |
| -------- | ------------------ | ---------------------- |
| 3.x | :white_check_mark: | |
| 2.x | :x: | End-of-life as of 2017 |
## Reporting a vulnerability
To report a vulnerability please send an email with the details to [security@swagger.io](mailto:security@swagger.io).
We'll acknowledge receipt of your report ASAP, and set expectations on how we plan to handle it.
{
"name": "swagger-api/swagger-ui",
"description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.",
"keywords": [
"Swagger",
"OpenAPI",
"specification",
"documentation",
"API",
"UI"
],
"homepage": "http://swagger.io",
"license": "Apache-2.0",
"authors": [
{
"name": "Anna Bodnia",
"email": "anna.bodnia@gmail.com"
},
{
"name": "Buu Nguyen",
"email": "buunguyen@gmail.com"
},
{
"name": "Josh Ponelat",
"email": "jponelat@gmail.com"
},
{
"name": "Kyle Shockey",
"email": "kyleshockey1@gmail.com"
},
{
"name": "Robert Barnwell",
"email": "robert@robertismy.name"
},
{
"name": "Sahar Jafari",
"email": "shr.jafari@gmail.com"
}
]
}
{
"fileServerFolder": "test/e2e-cypress/static",
"fixturesFolder": "test/e2e-cypress/fixtures",
"integrationFolder": "test/e2e-cypress/tests",
"pluginsFile": "test/e2e-cypress/plugins/index.js",
"screenshotsFolder": "test/e2e-cypress/screenshots",
"supportFile": "test/e2e-cypress/support/index.js",
"videosFolder": "test/e2e-cypress/videos",
"baseUrl": "http://localhost:3230/",
"video": false
}
<!-- HTML for dev server -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./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>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
window["SwaggerUIBundle"] = window["swagger-ui-bundle"]
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"]
// Build a system
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {},
usePkceWithAuthorizationCodeGrant: false
})
}
</script>
</body>
</html>
<!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 redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !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;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./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>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
<!doctype html>
<html lang="en-US">
<title>Swagger UI: OAuth2 Redirect</title>
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !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;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
module.exports.indent = function indent(str, len, fromLine = 0) {
return str
.split("\n")
.map((line, i) => {
if (i + 1 >= fromLine) {
return `${Array(len + 1).join(" ")}${line}`
} else {
return line
}
})
.join("\n")
}
\ No newline at end of file
const fs = require("fs")
const path = require("path")
const translator = require("./translator")
const oauthBlockBuilder = require("./oauth")
const indent = require("./helpers").indent
const START_MARKER = "// Begin Swagger UI call region"
const END_MARKER = "// End Swagger UI call region"
const targetPath = path.normalize(process.cwd() + "/" + process.argv[2])
const originalHtmlContent = fs.readFileSync(targetPath, "utf8")
const startMarkerIndex = originalHtmlContent.indexOf(START_MARKER)
const endMarkerIndex = originalHtmlContent.indexOf(END_MARKER)
const beforeStartMarkerContent = originalHtmlContent.slice(0, startMarkerIndex)
const afterEndMarkerContent = originalHtmlContent.slice(
endMarkerIndex + END_MARKER.length
)
if (startMarkerIndex < 0 || endMarkerIndex < 0) {
console.error("ERROR: Swagger UI was unable to inject Docker configuration data!")
console.error("! This can happen when you provide custom HTML to Swagger UI.")
console.error("! ")
console.error("! In order to solve this, add the `Begin Swagger UI call region`")
console.error("! and `End Swagger UI call region` markers to your HTML.")
console.error("! See the repository for an example:")
console.error("! https://github.com/swagger-api/swagger-ui/blob/02758b8125dbf38763cfd5d4f91c7c803e9bd0ad/dist/index.html#L40-L54")
console.error("! ")
console.error("! If you're seeing this message and aren't using custom HTML,")
console.error("! this message may be a bug. Please file an issue:")
console.error("! https://github.com/swagger-api/swagger-ui/issues/new/choose")
process.exit(0)
}
fs.writeFileSync(
targetPath,
`${beforeStartMarkerContent}
${START_MARKER}
const ui = SwaggerUIBundle({
${indent(translator(process.env, { injectBaseConfig: true }), 8, 2)}
})
${indent(oauthBlockBuilder(process.env), 6, 2)}
${END_MARKER}
${afterEndMarkerContent}`
)
const translator = require("./translator")
const indent = require("./helpers").indent
const oauthBlockSchema = {
OAUTH_CLIENT_ID: {
type: "string",
name: "clientId"
},
OAUTH_CLIENT_SECRET: {
type: "string",
name: "clientSecret",
onFound: () => console.warn("Swagger UI warning: don't use `OAUTH_CLIENT_SECRET` in production!")
},
OAUTH_REALM: {
type: "string",
name: "realm"
},
OAUTH_APP_NAME: {
type: "string",
name: "appName"
},
OAUTH_SCOPE_SEPARATOR: {
type: "string",
name: "scopeSeparator"
},
OAUTH_ADDITIONAL_PARAMS: {
type: "object",
name: "additionalQueryStringParams"
},
OAUTH_USE_PKCE: {
type: "boolean",
name: "usePkceWithAuthorizationCodeGrant"
}
}
module.exports = function oauthBlockBuilder(env) {
const translatorResult = translator(env, { schema: oauthBlockSchema })
if(translatorResult) {
return (
`ui.initOAuth({
${indent(translatorResult, 2)}
})`)
}
return ``
}
\ No newline at end of file
// Converts an object of environment variables into a Swagger UI config object
const configSchema = require("./variables")
const defaultBaseConfig = {
url: {
value: "https://petstore.swagger.io/v2/swagger.json",
schema: {
type: "string",
base: true
}
},
dom_id: {
value: "#swagger-ui",
schema: {
type: "string",
base: true
}
},
deepLinking: {
value: "true",
schema: {
type: "boolean",
base: true
}
},
presets: {
value: `[\n SwaggerUIBundle.presets.apis,\n SwaggerUIStandalonePreset\n]`,
schema: {
type: "array",
base: true
}
},
plugins: {
value: `[\n SwaggerUIBundle.plugins.DownloadUrl\n]`,
schema: {
type: "array",
base: true
}
},
layout: {
value: "StandaloneLayout",
schema: {
type: "string",
base: true
}
}
}
function objectToKeyValueString(env, { injectBaseConfig = false, schema = configSchema, baseConfig = defaultBaseConfig } = {}) {
let valueStorage = injectBaseConfig ? Object.assign({}, baseConfig) : {}
const keys = Object.keys(env)
// Compute an intermediate representation that holds candidate values and schemas.
//
// This is useful for deduping between multiple env keys that set the same
// config variable.
keys.forEach(key => {
const varSchema = schema[key]
const value = env[key]
if(!varSchema) return
if(varSchema.onFound) {
varSchema.onFound()
}
const storageContents = valueStorage[varSchema.name]
if(storageContents) {
if (varSchema.legacy === true && !storageContents.schema.base) {
// If we're looking at a legacy var, it should lose out to any already-set value
// except for base values
return
}
delete valueStorage[varSchema.name]
}
valueStorage[varSchema.name] = {
value,
schema: varSchema
}
})
// Compute a key:value string based on valueStorage's contents.
let result = ""
Object.keys(valueStorage).forEach(key => {
const value = valueStorage[key]
const escapedName = /[^a-zA-Z0-9]/.test(key) ? `"${key}"` : key
if (value.schema.type === "string") {
result += `${escapedName}: "${value.value}",\n`
} else {
result += `${escapedName}: ${value.value === "" ? `undefined` : value.value},\n`
}
})
return result.trim()
}
module.exports = objectToKeyValueString
\ No newline at end of file
const standardVariables = {
CONFIG_URL: {
type: "string",
name: "configUrl"
},
DOM_ID: {
type: "string",
name: "dom_id"
},
SPEC: {
type: "object",
name: "spec"
},
URL: {
type: "string",
name: "url"
},
URLS: {
type: "array",
name: "urls"
},
URLS_PRIMARY_NAME: {
type: "string",
name: "urls.primaryName"
},
LAYOUT: {
type: "string",
name: "layout"
},
DEEP_LINKING: {
type: "boolean",
name: "deepLinking"
},
DISPLAY_OPERATION_ID: {
type: "boolean",
name: "displayOperationId"
},
DEFAULT_MODELS_EXPAND_DEPTH: {
type: "number",
name: "defaultModelsExpandDepth"
},
DEFAULT_MODEL_EXPAND_DEPTH: {
type: "number",
name: "defaultModelExpandDepth"
},
DEFAULT_MODEL_RENDERING: {
type: "string",
name: "defaultModelRendering"
},
DISPLAY_REQUEST_DURATION: {
type: "boolean",
name: "displayRequestDuration"
},
DOC_EXPANSION: {
type: "string",
name: "docExpansion"
},
FILTER: {
type: "string",
name: "filter"
},
MAX_DISPLAYED_TAGS: {
type: "number",
name: "maxDisplayedTags"
},
SHOW_EXTENSIONS: {
type: "boolean",
name: "showExtensions"
},
SHOW_COMMON_EXTENSIONS: {
type: "boolean",
name: "showCommonExtensions"
},
OAUTH2_REDIRECT_URL: {
type: "string",
name: "oauth2RedirectUrl"
},
SHOW_MUTATED_REQUEST: {
type: "boolean",
name: "showMutatedRequest"
},
SUPPORTED_SUBMIT_METHODS: {
type: "array",
name: "supportedSubmitMethods"
},
VALIDATOR_URL: {
type: "string",
name: "validatorUrl"
},
WITH_CREDENTIALS: {
type: "boolean",
name: "withCredentials",
}
}
const legacyVariables = {
API_URL: {
type: "string",
name: "url",
legacy: true
},
API_URLS: {
type: "array",
name: "urls",
legacy: true
}
}
module.exports = Object.assign({}, standardVariables, legacyVariables)
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' always;
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' $access_control_max_age always;
if ($request_method = OPTIONS) {
return 204;
}
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_static on;
gzip_disable "msie6";
gzip_vary on;
gzip_types text/plain text/css application/javascript;
map $request_method $access_control_max_age {
OPTIONS 1728000; # 20 days
}
server {
listen 8080;
server_name localhost;
index index.html index.htm;
location / {
alias /usr/share/nginx/html/;
expires 1d;
location ~* \.(?:json|yml|yaml)$ {
expires -1;
include cors.conf;
}
include cors.conf;
}
}
}
#! /bin/sh
set -e
BASE_URL=${BASE_URL:-/}
NGINX_ROOT=/usr/share/nginx/html
INDEX_FILE=$NGINX_ROOT/index.html
node /usr/share/nginx/configurator $INDEX_FILE
replace_in_index () {
if [ "$1" != "**None**" ]; then
sed -i "s|/\*||g" $INDEX_FILE
sed -i "s|\*/||g" $INDEX_FILE
sed -i "s|$1|$2|g" $INDEX_FILE
fi
}
replace_or_delete_in_index () {
if [ -z "$2" ]; then
sed -i "/$1/d" $INDEX_FILE
else
replace_in_index $1 $2
fi
}
if [ "${BASE_URL}" ]; then
sed -i "s|location / {|location $BASE_URL {|g" /etc/nginx/nginx.conf
fi
replace_in_index myApiKeyXXXX123456789 $API_KEY
if [[ -f $SWAGGER_JSON ]]; then
cp -s $SWAGGER_JSON $NGINX_ROOT
REL_PATH="./$(basename $SWAGGER_JSON)"
sed -i "s|https://petstore.swagger.io/v2/swagger.json|$REL_PATH|g" $INDEX_FILE
sed -i "s|http://example.com/api|$REL_PATH|g" $INDEX_FILE
fi
# replace the PORT that nginx listens on if PORT is supplied
if [[ -n "${PORT}" ]]; then
sed -i "s|8080|${PORT}|g" /etc/nginx/nginx.conf
fi
find $NGINX_ROOT -type f -regex ".*\.\(html\|js\|css\)" -exec sh -c "gzip < {} > {}.gz" \;
exec nginx -g 'daemon off;'
# Swagger UI
Welcome to the Swagger UI documentation!
A table of contents can be found at `SUMMARY.md`.
#### Usage
- [Installation](usage/installation.md)
- [Configuration](usage/configuration.md)
- [CORS](usage/cors.md)
- [OAuth2](usage/oauth2.md)
- [Deep Linking](usage/deep-linking.md)
- [Limitations](usage/limitations.md)
- [Version detection](usage/version-detection.md)
#### Customization
- [Overview](customization/overview.md)
- [Plugin API](customization/plugin-api.md)
- [Custom layout](customization/custom-layout.md)
#### Development
- [Setting up](development/setting-up.md)
- [Scripts](development/scripts.md)
# Creating a custom layout
**Layouts** are a special type of component that Swagger UI uses as the root component for the entire application. You can define custom layouts in order to have high-level control over what ends up on the page.
By default, Swagger UI uses `BaseLayout`, which is built into the application. You can specify a different layout to be used by passing the layout's name as the `layout` parameter to Swagger UI. Be sure to provide your custom layout as a component to Swagger UI.
<br>
For example, if you wanted to create a custom layout that only displayed operations, you could define an `OperationsLayout`:
```js
import React from "react"
// Create the layout component
class OperationsLayout extends React.Component {
render() {
const {
getComponent
} = this.props
const Operations = getComponent("operations", true)
return (
<div>
<Operations />
</div>
)
}
}
// Create the plugin that provides our layout component
const OperationsLayoutPlugin = () => {
return {
components: {
OperationsLayout: OperationsLayout
}
}
}
// Provide the plugin to Swagger-UI, and select OperationsLayout
// as the layout for Swagger-UI
SwaggerUI({
url: "https://petstore.swagger.io/v2/swagger.json",
plugins: [ OperationsLayoutPlugin ],
layout: "OperationsLayout"
})
```
### Augmenting the default layout
If you'd like to build around the `BaseLayout` instead of replacing it, you can pull the `BaseLayout` into your custom layout and use it:
```js
import React from "react"
// Create the layout component
class AugmentingLayout extends React.Component {
render() {
const {
getComponent
} = this.props
const BaseLayout = getComponent("BaseLayout", true)
return (
<div>
<div className="myCustomHeader">
<h1>I have a custom header above Swagger-UI!</h1>
</div>
<BaseLayout />
</div>
)
}
}
// Create the plugin that provides our layout component
const AugmentingLayoutPlugin = () => {
return {
components: {
AugmentingLayout: AugmentingLayout
}
}
}
// Provide the plugin to Swagger-UI, and select AugmentingLayout
// as the layout for Swagger-UI
SwaggerUI({
url: "https://petstore.swagger.io/v2/swagger.json",
plugins: [ AugmentingLayoutPlugin ],
layout: "AugmentingLayout"
})
```
# Plugin system overview
### Prior art
Swagger UI leans heavily on concepts and patterns found in React and Redux.
If you aren't already familiar, here's some suggested reading:
- [React: Quick Start (reactjs.org)](https://reactjs.org/docs/hello-world.html)
- [Redux README (redux.js.org)](http://redux.js.org/)
In the following documentation, we won't take the time to define the fundamentals covered in the resources above.
> **Note**: Some of the examples in this section contain JSX, which is a syntax extension to JavaScript that is useful for writing React components.
>
> If you don't want to set up a build pipeline capable of translating JSX to JavaScript, take a look at [React without JSX (reactjs.org)](https://reactjs.org/docs/react-without-jsx.html). You can use our `system.React` reference to leverage React without needing to pull a copy into your project.
### The System
The _system_ is the heart of the Swagger UI application. At runtime, it's a JavaScript object that holds many things:
- React components
- Bound Redux actions and reducers
- Bound Reselect state selectors
- System-wide collection of available components
- Built-in helpers like `getComponent`, `makeMappedContainer`, and `getStore`
- References to the React and Immutable.js libraries (`system.React`, `system.Im`)
- User-defined helper functions
The system is built up when Swagger UI is called by iterating through ("compiling") each plugin that Swagger UI has been given, through the `presets` and `plugins` configuration options.
### Presets
Presets are arrays of plugins, which are provided to Swagger UI through the `presets` configuration option. All plugins within presets are compiled before any plugins provided via the `plugins` configuration option. Consider the following example:
```javascript
const MyPreset = [FirstPlugin, SecondPlugin, ThirdPlugin]
SwaggerUI({
presets: [
MyPreset
]
})
```
By default, Swagger UI includes the internal `ApisPreset`, which contains a set of plugins that provide baseline functionality for Swagger UI. If you specify your own `presets` option, you need to add the ApisPreset manually, like so:
```javascript
SwaggerUI({
presets: [
SwaggerUI.presets.apis,
MyAmazingCustomPreset
]
})
```
The need to provide the `apis` preset when adding other presets is an artifact of Swagger UI's original design, and will likely be removed in the next major version.
### getComponent
`getComponent` is a helper function injected into every container component, which is used to get references to components provided by the plugin system.
All components should be loaded through `getComponent`, since it allows other plugins to modify the component. It is preferred over a conventional `import` statement.
Container components in Swagger UI can be loaded by passing `true` as the second argument to `getComponent`, like so:
```javascript
getComponent("ContainerComponentName", true)
```
This will map the current system as props to the component.
# Plug points
Swagger UI exposes most of its internal logic through the plugin system.
Often, it is beneficial to override the core internals to achieve custom behavior.
### Note: Semantic Versioning
Swagger UI's internal APIs are _not_ part of our public contract, which means that they can change without the major version changing.
If your custom plugins wrap, extend, override, or consume any internal core APIs, we recommend specifying a specific minor version of Swagger UI to use in your application, because they will _not_ change between patch versions.
If you're installing Swagger UI via NPM, for example, you can do this by using a tilde:
```js
{
"dependencies": {
"swagger-ui": "~3.11.0"
}
}
```
### `fn.opsFilter`
When using the `filter` option, tag names will be filtered by the user-provided value. If you'd like to customize this behavior, you can override the default `opsFilter` function.
For example, you can implement a multiple-phrase filter:
```js
const MultiplePhraseFilterPlugin = function() {
return {
fn: {
opsFilter: (taggedOps, phrase) => {
const phrases = phrase.split(", ")
return taggedOps.filter((val, key) => {
return phrases.some(item => key.indexOf(item) > -1)
})
}
}
}
}
```
# Helpful scripts
Any of the scripts below can be run by typing `npm run <script name>` in the project's root directory.
### Developing
Script name | Description
--- | ---
`dev` | Spawn a hot-reloading dev server on port 3200.
`deps-check` | Generate a size and licensing report on Swagger UI's dependencies.
`lint` | Report ESLint style errors and warnings.
`lint-errors` | Report ESLint style errors, without warnings.
`lint-fix` | Attempt to fix style errors automatically.
`watch` | Rebuild the core files in `/dist` when the source code changes. Useful for `npm link` with Swagger Editor.
### Building
Script name | Description
--- | ---
`build` | Build a new set of JS and CSS assets, and output them to `/dist`.
`build-bundle` | Build `swagger-ui-bundle.js` only.
`build-core` | Build `swagger-ui.(js\|css)` only.
`build-standalone` | Build `swagger-ui-standalone-preset.js` only.
`build-stylesheets` | Build `swagger-ui.css` only.
### Testing
Script name | Description
--- | ---
`test` | Run unit tests in Node and run ESLint in errors-only mode.
`just-test` | Run unit tests in the browser with Karma.
`just-test-in-node` | Run unit tests in Node.
`e2e` | Run end-to-end tests (requires JDK and Selenium).
# Setting up a dev environment
Swagger UI includes a development server that provides hot module reloading and unminified stack traces, for easier development.
### Prerequisites
- Node.js `6.0.0` or greater
- npm `3.0.0` or greater
- git, any version
### Steps
1. `git clone https://github.com/swagger-api/swagger-ui.git`
2. `cd swagger-ui`
3. `npm install`
4. `npm run dev`
5. Wait a bit
6. Open http://localhost:3200/
## Bonus points
- Swagger UI includes an ESLint rule definition. If you use a graphical editor, consider installing an ESLint plugin, which will point out syntax and style errors for you as you code.
- The linter runs as part of the PR test sequence, so don't think you can get away with not paying attention to it!
# CORS
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 "https://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.
- Use the http://www.test-cors.org website to verify CORS support. 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 UI 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.
# `deepLinking` parameter
Swagger UI allows you to deeply link into tags and operations within a spec. When Swagger UI is provided a URL fragment at runtime, it will automatically expand and scroll to a specified tag or operation.
## Usage
👉🏼 Add `deepLinking: true` to your Swagger UI configuration to enable this functionality. This is demonstrated in [`dist/index.html`](https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html).
When you expand a tag or operation, Swagger UI will automatically update its URL fragment with a deep link to the item.
Conversely, when you collapse a tag or operation, Swagger UI will clear the URL fragment.
You can also right-click a tag name or operation path in order to copy a link to that tag or operation.
#### Fragment format
The fragment is formatted in one of two ways:
- `#/{tagName}`, to trigger the focus of a specific tag
- `#/{tagName}/{operationId}`, to trigger the focus of a specific operation within a tag
`operationId` is the explicit operationId provided in the spec, if one exists.
Otherwise, Swagger UI generates an implicit operationId by combining the operation's path and method, and escaping non-alphanumeric characters.
## FAQ
> I'm using Swagger UI in an application that needs control of the URL fragment. How do I disable deep-linking?
This functionality is disabled by default, but you can pass `deepLinking: false` into Swagger UI as a configuration item to be sure.
> Can I link to multiple tags or operations?
No, this is not supported.
> Can I collapse everything except the operation or tag I'm linking to?
Sure - use `docExpansion: none` to collapse all tags and operations. Your deep link will take precedence over the setting, so only the tag or operation you've specified will be expanded.
# Installation
## Distribution channels
### NPM Registry
We publish two modules to npm: **`swagger-ui`** and **`swagger-ui-dist`**.
**`swagger-ui`** is meant for consumption by JavaScript web projects that include module bundlers, such as Webpack, Browserify, and Rollup. Its main file exports Swagger UI's main function, and the module also includes a namespaced stylesheet at `swagger-ui/dist/swagger-ui.css`. Here's an example:
```javascript
import SwaggerUI from 'swagger-ui'
// or use require, if you prefer
const SwaggerUI = require('swagger-ui')
SwaggerUI({
dom_id: '#myDomId'
})
```
In contrast, **`swagger-ui-dist`** is meant for server-side projects that need assets to serve to clients. The module, when imported, includes an `absolutePath` helper function that returns the absolute filesystem path to where the `swagger-ui-dist` module is installed.
_Note: we suggest using `swagger-ui` when your tooling makes it possible, as `swagger-ui-dist`
will result in more code going across the wire._
The module's contents mirrors the `dist` folder you see in the Git repository. The most useful file is `swagger-ui-bundle.js`, which is a build of Swagger UI that includes all the code it needs to run in one file. The folder also has an `index.html` asset, to make it easy to serve Swagger UI like so:
```javascript
const express = require('express')
const pathToSwaggerUi = require('swagger-ui-dist').absolutePath()
const app = express()
app.use(express.static(pathToSwaggerUi))
app.listen(3000)
```
The module also exports `SwaggerUIBundle` and `SwaggerUIStandalonePreset`, so
if you're in a JavaScript project that can't handle a traditional npm module,
you could do something like this:
```js
var SwaggerUIBundle = require('swagger-ui-dist').SwaggerUIBundle
const ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "StandaloneLayout"
})
```
`SwaggerUIBundle` is equivalent to `SwaggerUI`.
### Docker
You can pull a pre-built docker image of the swagger-ui directly from Docker Hub:
```
docker pull swaggerapi/swagger-ui
docker run -p 80:8080 swaggerapi/swagger-ui
```
Will start nginx with Swagger UI on port 80.
Or you can provide your own swagger.json on your host
```
docker run -p 80:8080 -e SWAGGER_JSON=/foo/swagger.json -v /bar:/foo swaggerapi/swagger-ui
```
The base URL of the web application can be changed by specifying the `BASE_URL` environment variable:
```
docker run -p 80:8080 -e BASE_URL=/swagger -e SWAGGER_JSON=/foo/swagger.json -v /bar:/foo swaggerapi/swagger-ui
```
This will serve Swagger UI at `/swagger` instead of `/`.
For more information on controlling Swagger UI through the Docker image, see the Docker section of the [Configuration documentation](configuration.md#docker).
### unpkg
You can embed Swagger UI's code directly in your HTML by using unpkg's interface:
```html
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<!-- `SwaggerUIBundle` is now available on the page -->
```
See [unpkg's main page](https://unpkg.com/) for more information on how to use unpkg.
# Limitations
### Forbidden header names
Some header names cannot be controlled by web applications, due to security
features built into web browsers.
Forbidden headers include:
> - Accept-Charset
> - Accept-Encoding
> - Access-Control-Request-Headers
> - Access-Control-Request-Method
> - Connection
> - Content-Length
> - Cookie
> - Cookie2
> - Date
> - DNT
> - Expect
> - Host
> - Keep-Alive
> - Origin
> - Proxy-*
> - Sec-*
> - Referer
> - TE
> - Trailer
> - Transfer-Encoding
> - Upgrade
> - Via
>
> _[Forbidden header names (developer.mozilla.org)](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name)_
The biggest impact of this is that OpenAPI 3.0 Cookie parameters cannot be
controlled when running Swagger UI in a browser.
For more context, see [#3956](https://github.com/swagger-api/swagger-ui/issues/3956).
# OAuth 2.0 configuration
You can configure OAuth 2.0 authorization by calling the `initOAuth` method.
Property name | Docker variable | Description
--- | --- | ------
clientId | `OAUTH_CLIENT_ID` | Default clientId. MUST be a string
clientSecret | `OAUTH_CLIENT_SECRET` | **🚨 Never use this parameter in your production environment. It exposes cruicial security information. This feature is intended for dev/test environments only. 🚨** <br>Default clientSecret. MUST be a string
realm | `OAUTH_REALM` |realm query parameter (for oauth1) added to `authorizationUrl` and `tokenUrl`. MUST be a string
appName | `OAUTH_APP_NAME` |application name, displayed in authorization popup. MUST be a string
scopeSeparator | `OAUTH_SCOPE_SEPARATOR` |scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | `OAUTH_ADDITIONAL_PARAMS` |Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | _Unavailable_ |Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encode(client_id + client_secret)`). The default is `false`
usePkceWithAuthorizationCodeGrant | `OAUTH_USE_PKCE` | Only applies to `authorizatonCode` flows. [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636) brings enhanced security for OAuth public clients. The default is `false`
```javascript
const ui = SwaggerUI({...})
// Method can be called in any place after calling constructor SwaggerUIBundle
ui.initOAuth({
clientId: "your-client-id",
clientSecret: "your-client-secret-if-required",
realm: "your-realms",
appName: "your-app-name",
scopeSeparator: " ",
additionalQueryStringParams: {test: "hello"},
usePkceWithAuthorizationCodeGrant: true
})
```
# Detecting your Swagger UI version
At times, you're going to need to know which version of Swagger UI you use.
The first step would be to detect which major version you currently use, as the method of detecting the version has changed. If your Swagger UI has been heavily modified and you cannot detect from the look and feel which major version you use, you'd have to try both methods to get the exact version.
To help you visually detect which version you're using, we've included supporting images.
# Swagger UI 3.x
![Swagger UI 3](/docs/images/swagger-ui3.png)
Some distinct identifiers to Swagger UI 3.x:
- The API version appears as a badge next to its title.
- If there are schemes or authorizations, they'd appear in a bar above the operations.
- Try it out functionality is not enabled by default.
- All the response codes in the operations appear at after the parameters.
- There's a models section after the operations.
If you've determined this is the version you have, to find the exact version:
- Open your browser's web console (changes between browsers)
- Type `JSON.stringify(versions)` in the console and execute the call.
- The result should look similar to `swaggerUi : Object { version: "3.1.6", gitRevision: "g786cd47", gitDirty: true, … }`.
- The version taken from that example would be `3.1.6`.
Note: This functionality was added in 3.0.8. If you're unable to execute it, you're likely to use an older version, and in that case the first step would be to upgrade.
# Swagger UI 2.x and under
![Swagger UI 2](/docs/images/swagger-ui2.png)
Some distinct identifiers to Swagger UI 3.x:
- The API version appears at the bottom of the page.
- Schemes are not rendered.
- Authorization, if rendered, will appear next to the navigation bar.
- Try it out functionality is enabled by default.
- The successful response code would appear above the parameters, the rest below them.
- There's no models section after the operations.
If you've determined this is the version you have, to find the exact version:
- Navigate to the sources of the UI. Either on your disk or via the view page source functionality in your browser.
- Find an open the `swagger-ui.js`
- At the top of the page, there would be a comment containing the exact version of Swagger UI. This example shows version `2.2.9`:
```
/**
* swagger-ui - Swagger UI is a dependency-free collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API
* @version v2.2.9
* @link http://swagger.io
* @license Apache-2.0
*/
```
# `swagger-ui-react`
[![NPM version](https://badge.fury.io/js/swagger-ui-react.svg)](http://badge.fury.io/js/swagger-ui-react)
`swagger-ui-react` is a flavor of Swagger UI suitable for use in React applications.
It has a few differences from the main version of Swagger UI:
* Declares `react` and `react-dom` as peerDependencies instead of production dependencies
* Exports a component instead of a constructor function
Versions of this module mirror the version of Swagger UI included in the distribution.
## Quick start
Install `swagger-ui-react`:
```
$ npm i --save swagger-ui-react
```
Use it in your React application:
```js
import SwaggerUI from "swagger-ui-react"
import "swagger-ui-react/swagger-ui.css"
export default App = () => <SwaggerUI url="https://petstore.swagger.io/v2/swagger.json" />
```
## Props
These props map to [Swagger UI configuration options](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md) of the same name.
#### `spec`: PropTypes.object
An OpenAPI document respresented as a JavaScript object, JSON string, or YAML string for Swagger UI to display.
⚠️ Don't use this in conjunction with `url` - unpredictable behavior may occur.
#### `url`: PropTypes.string
Remote URL to an OpenAPI document that Swagger UI will fetch, parse, and display.
⚠️ Don't use this in conjunction with `spec` - unpredictable behavior may occur.
#### `onComplete`: PropTypes.func
> `(system) => void`
A callback function that is triggered when Swagger-UI finishes rendering an OpenAPI document.
Swagger UI's `system` object is passed as an argument.
#### `requestInterceptor`: PropTypes.func
> `req => req` or `req => Promise<req>`.
A function that accepts a request object, and returns either a request object
or a Promise that resolves to a request object.
#### `responseInterceptor`: PropTypes.func
> `res => res` or `res => Promise<res>`.
A function that accepts a response object, and returns either a response object
or a Promise that resolves to a response object.
#### `docExpansion`: PropTypes.oneOf(['list', 'full', 'none'])
Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default value is 'list'.
⚠️ This prop is currently only applied once, on mount. Changes to this prop's value will not be propagated to the underlying Swagger UI instance. A future version of this module will remove this limitation, and the change will not be considered a breaking change.
## Limitations
* Not all configuration bindings are available.
* Some props are only applied on mount, and cannot be updated reliably.
* OAuth redirection handling is not supported.
* Topbar/Standalone mode is not supported.
* Custom plugins are not supported.
We intend to address these limitations based on user demand, so please open an issue or pull request if you have a specific request.
## Notes
* The `package.json` in the same folder as this README is _not_ the manifest that should be used for releases - another manifest is generated at build-time and can be found in `./dist/`.
---
For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.
import React from "react"
import PropTypes from "prop-types"
import swaggerUIConstructor from "./swagger-ui"
export default class SwaggerUI extends React.Component {
constructor (props) {
super(props)
this.SwaggerUIComponent = null
this.system = null
}
componentDidMount() {
const ui = swaggerUIConstructor({
spec: this.props.spec,
url: this.props.url,
requestInterceptor: this.requestInterceptor,
responseInterceptor: this.responseInterceptor,
onComplete: this.onComplete,
docExpansion: this.props.docExpansion,
})
this.system = ui
this.SwaggerUIComponent = ui.getComponent("App", "root")
this.forceUpdate()
}
render() {
return this.SwaggerUIComponent ? <this.SwaggerUIComponent /> : null
}
componentDidUpdate(prevProps) {
if(this.props.url !== prevProps.url) {
// flush current content
this.system.specActions.updateSpec("")
if(this.props.url) {
// update the internal URL
this.system.specActions.updateUrl(this.props.url)
// trigger remote definition fetch
this.system.specActions.download(this.props.url)
}
}
if(this.props.spec !== prevProps.spec && this.props.spec) {
if(typeof this.props.spec === "object") {
this.system.specActions.updateSpec(JSON.stringify(this.props.spec))
} else {
this.system.specActions.updateSpec(this.props.spec)
}
}
}
requestInterceptor = (req) => {
if (typeof this.props.requestInterceptor === "function") {
return this.props.requestInterceptor(req)
}
return req
}
responseInterceptor = (res) => {
if (typeof this.props.responseInterceptor === "function") {
return this.props.responseInterceptor(res)
}
return res
}
onComplete = () => {
if (typeof this.props.onComplete === "function") {
return this.props.onComplete(this.system)
}
}
}
SwaggerUI.propTypes = {
spec: PropTypes.oneOf([
PropTypes.string,
PropTypes.object,
]),
url: PropTypes.string,
requestInterceptor: PropTypes.func,
responseInterceptor: PropTypes.func,
onComplete: PropTypes.func,
docExpansion: PropTypes.oneOf(['list', 'full', 'none']),
}
var jsonMerger = require("json-merger")
var fs = require("fs")
var result = jsonMerger.mergeFiles(["../../../package.json", "template.json"])
if(process.env.REACT_FLAVOR_VERSION_IDENTIFIER) {
result.version = process.env.REACT_FLAVOR_VERSION_IDENTIFIER
}
process.stdout.write(JSON.stringify(result, null, 2))
# Deploy `swagger-ui-react` to npm.
# https://www.peterbe.com/plog/set-ex
set -ex
# Parameter Expansion: http://stackoverflow.com/questions/6393551/what-is-the-meaning-of-0-in-a-bash-script
cd "${0%/*}"
mkdir -p ../dist
# Copy UI's dist files to our directory
cp ../../../dist/swagger-ui.js ../dist
cp ../../../dist/swagger-ui.css ../dist
# Create a releasable package manifest
node create-manifest.js > ../dist/package.json
# Transpile our top-level component
../../../node_modules/.bin/babel --config-file ../../../.babelrc ../index.js > ../dist/index.js
# Copy our README into the dist folder for npm
cp ../README.md ../dist
# Run the release from the dist folder
cd ../dist
if [ "$PUBLISH_FLAVOR_REACT" = "true" ] ; then
npm publish .
else
npm pack .
fi
{
"dependencies": {
"react": {
"$remove": true
},
"react-dom": {
"$remove": true
}
},
"scripts": {
"$remove": true
},
"devDependencies": {
"$remove": true
},
"bundlesize": {
"$remove": true
},
"nyc": {
"$remove": true
},
"browserslist": {
"$remove": true
},
"config": {
"$remove": true
},
"name": "swagger-ui-react",
"main": "index.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",
"peerDependencies": {
"react": ">=15.6.2",
"react-dom": ">=15.6.2"
}
}
\ No newline at end of file
{
"name": "swagger-ui",
"version": "3.24.0",
"main": "dist/swagger-ui.js",
"homepage": "https://github.com/swagger-api/swagger-ui",
"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 <kyleshockey@gmail.com>",
"Robert Barnwell <robert@robertismy.name>",
"Sahar Jafari <shr.jafari@gmail.com>"
],
"license": "Apache-2.0",
"scripts": {
"automated-release": "release-it --config ./release/.release-it.json",
"build": "run-p --aggregate-output build-core build-bundle build-standalone build-stylesheets",
"build-bundle": "webpack --colors --config webpack/bundle.babel.js",
"build-core": "webpack --colors --config webpack/core.babel.js",
"build-standalone": "webpack --colors --config webpack/standalone.babel.js",
"build-stylesheets": "webpack --colors --config webpack/stylesheets.babel.js",
"predev": "npm install",
"dev": "webpack-dev-server --config webpack/dev.babel.js",
"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/bundle.babel.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
"deps-check": "run-s deps-license deps-size",
"lint": "eslint --cache --ext '.js,.jsx' src test",
"lint-errors": "eslint --cache --quiet --ext '.js,.jsx' src test",
"lint-fix": "eslint --cache --ext '.js,.jsx' src test --fix",
"test": "run-s just-test-in-node e2e-cypress lint-errors",
"test-in-node": "run-s lint-errors just-test-in-node",
"just-test-in-node": "mocha --require test/mocha/setup.js --recursive --compilers js:@babel/register --require source-map-support 'test/mocha/**/*.{js,jsx}'",
"test-e2e-cypress": "cypress run",
"test-e2e-selenium": "sleep 3 && nightwatch test/e2e-selenium/scenarios/ --config test/e2e-selenium/nightwatch.json",
"e2e-initial-render": "nightwatch test/e2e-selenium/scenarios/ --config test/e2e-selenium/nightwatch.json --group initial-render",
"mock-api": "json-server --watch test/e2e-selenium/db.json --port 3204",
"hot-e2e-cypress-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-cypress/static",
"hot-e2e-selenium-server": "webpack-dev-server --config webpack/dev-e2e.babel.js --content-base test/e2e-selenium/static",
"e2e-cypress": "run-p -r hot-e2e-cypress-server mock-api test-e2e-cypress",
"e2e-selenium": "run-p -r hot-e2e-selenium-server mock-api test-e2e-selenium",
"open-static": "node -e 'require(\"open\")(\"http://localhost:3002\")'",
"security-audit": "run-s -sc security-audit:all security-audit:prod",
"security-audit:prod": "npm-audit-ci-wrapper -p -t low",
"security-audit:all": "npm-audit-ci-wrapper -t moderate",
"serve-static": "http-server dist/ -i -a 0.0.0.0 -p 3002",
"start": "npm-run-all --parallel serve-static open-static"
},
"dependencies": {
"@babel/runtime-corejs2": "^7.5.5",
"@braintree/sanitize-url": "^3.0.0",
"@kyleshockey/object-assign-deep": "^0.4.2",
"@kyleshockey/xml": "^1.0.2",
"base64-js": "^1.2.0",
"classnames": "^2.2.6",
"core-js": "^2.5.1",
"css.escape": "1.5.1",
"deep-extend": "0.6.0",
"dompurify": "^2.0.0",
"ieee754": "^1.1.13",
"immutable": "^3.x.x",
"js-file-download": "^0.4.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.15",
"memoizee": "^0.4.12",
"prop-types": "^15.7.2",
"randombytes": "^2.1.0",
"react": "^15.6.2",
"react-debounce-input": "^3.2.0",
"react-dom": "^15.6.2",
"react-immutable-proptypes": "2.1.0",
"react-immutable-pure-component": "^1.1.1",
"react-inspector": "^2.3.0",
"react-motion": "^0.5.2",
"react-redux": "^4.x.x",
"redux": "^3.x.x",
"redux-immutable": "3.1.0",
"remarkable": "^1.7.4",
"reselect": "^2.5.4",
"serialize-error": "^2.1.0",
"sha.js": "^2.4.11",
"swagger-client": "^3.9.5",
"url-parse": "^1.4.7",
"xml-but-prettier": "^1.0.1",
"zenscroll": "^4.0.2"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.4.4",
"@release-it/conventional-changelog": "^1.1.0",
"autoprefixer": "^8.4.1",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.6",
"babel-plugin-module-resolver": "^3.2.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"body-parser": "^1.19.0",
"bundlesize": "^0.18.0",
"chromedriver": "^2.38.3",
"copy-webpack-plugin": "^5.0.4",
"cors": "^2.8.5",
"css-loader": "^3.1.0",
"cypress": "^3.4.1",
"dedent": "^0.7.0",
"deepmerge": "^2.1.0",
"enzyme": "^2.7.1",
"eslint": "^4.1.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.14.3",
"expect": "^1.20.2",
"express": "^4.17.1",
"file-loader": "^4.1.0",
"git-describe": "^4.0.4",
"http-server": "^0.11.1",
"ignore-assets-webpack-plugin": "^2.0.1",
"imports-loader": "^0.8.0",
"jsdom": "^11.10.0",
"json-loader": "^0.5.7",
"json-merger": "^1.1.1",
"json-server": "^0.15.0",
"less": "^3.9.0",
"license-checker": "^19.0.0",
"mini-css-extract-plugin": "^0.8.0",
"mocha": "^5.1.1",
"nightwatch": "^1.1.13",
"node-sass": "^4.12.0",
"npm-audit-ci-wrapper": "^2.3.0",
"npm-run-all": "^4.1.5",
"oauth2-server": "^2.4.1",
"open": "^6.4.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"prettier": "^1.18.2",
"raw-loader": "^3.1.0",
"react-test-renderer": "^15.5.4",
"release-it": "^12.3.4",
"rimraf": "^2.6.3",
"sass-loader": "^7.1.0",
"selenium-server-standalone-jar": "^3.141.5",
"source-map-support": "^0.5.12",
"standard": "^11.0.1",
"tachyons-sass": "^4.9.5",
"terser-webpack-plugin": "^1.4.1",
"url-loader": "^2.1.0",
"webpack": "^4.39.0",
"webpack-bundle-size-analyzer": "^2.5.0",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.7.2"
},
"config": {
"deps_check_dir": ".deps_check"
},
"bundlesize": [
{
"path": "./dist/swagger-ui-bundle.js",
"maxSize": "1 MB",
"compression": "none"
}
]
}
{
"scripts": {
"beforeBump": [
"./release/check-for-breaking-changes.sh ${latestVersion} ${version}",
"npm update swagger-client",
"npm test"
],
"beforeStage": ["npm run build"],
"afterRelease": "export GIT_TAG=v${version} && echo GIT_TAG=v${version} > release/.version"
},
"git": {
"requireUpstream": false,
"changelog": "./release/get-changelog.sh",
"commitMessage": "release: v${version}",
"tagName": "v${version}",
"push": false
},
"github": {
"release": true,
"releaseName": "Swagger UI %s Released!",
"draft": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular"
}
}
}
#!/bin/bash
CURRENT_VERSION=$1
NEXT_VERSION=$2
CURRENT_MAJOR=${CURRENT_VERSION:0:1}
NEXT_MAJOR=${NEXT_VERSION:0:1}
if [ "$CURRENT_MAJOR" -ne "$NEXT_MAJOR" ] ;
then if [ "$BREAKING_OKAY" = "true" ];
then echo "breaking change detected but BREAKING_OKAY is set; continuing." && exit 0;
else echo "breaking change detected and BREAKING_OKAY is not set; aborting." && exit 1;
fi;
fi;
echo "next version is not a breaking change; continuing.";
\ No newline at end of file
echo "_No release summary included._\n\n#### Changelog\n"
PREV_RELEASE_REF=$(git log --pretty=oneline | grep ' release: ' | head -n 2 | tail -n 1 | cut -f 1 -d " ")
git log --pretty=oneline $PREV_RELEASE_REF..HEAD | awk '{ $1=""; print}' | sed -e 's/^[ \t]*//' | sed 's/^feat/0,feat/' | sed 's/^improve/1,improve/' | sed 's/^fix/2,fix/'| sort | sed 's/^[0-2],//' | sed 's/^/* /'
{
"extends": [
"config:base"
],
"packageRules": [
{
"managers": ["npm"],
"commitMessage": "{{{commitMessagePrefix}}} {{{commitMessageTopic}}}{{{commitMessageExtra}}} {{{commitMessageSuffix}}}",
"commitMessageTopic": "{{depName}}",
"commitMessageExtra": "{{#if isMajor}} v{{{newMajor}}}{{else}}{{#if isSingleVersion}}@{{{toVersion}}}{{else}}@{{{newValue}}}{{/if}}{{/if}}"
},
{
"packagePatterns": [
"*"
],
"rangeStrategy": "replace",
"semanticCommitType": "housekeeping"
},
{
"depTypeList": [
"devDependencies"
],
"rangeStrategy": "update-lockfile",
"semanticCommitScope": "dev-deps",
"automerge": true,
"major": {
"automerge": false
}
},
{
"depTypeList": [
"peerDependencies"
],
"rangeStrategy": "widen",
"semanticCommitScope": "peer-deps"
},
{
"packagePatterns": ["^react"],
"enabled": false
}
],
"ignorePaths": ["swagger-ui-dist-package/package.json"],
"prConcurrentLimit": 3
}
name: swagger-ui
version: master
summary: The World's Most Popular API Framework
description: |
Swagger UI is part of the Swagger project. The Swagger project allows you to
produce, visualize and consume your OWN RESTful services. No proxy or 3rd
party services required. Do it your own way.
Swagger UI is a dependency-free collection of HTML, Javascript, and CSS
assets that dynamically generate beautiful documentation and sandbox from a
Swagger-compliant API. Because Swagger UI has no dependencies, you can host
it in any server environment, or on your local machine.
grade: devel
confinement: strict
apps:
swagger-ui:
command: sh -c \"cd $SNAP/lib/node_modules/swagger-ui/dist && http-server -a localhost -p 8080\"
daemon: simple
plugs: [network, network-bind]
parts:
swagger-ui:
source: .
plugin: nodejs
npm-run: [build]
node-packages: [handlebars, http-server]
{
"rules": {
"import/no-extraneous-dependencies": [
2,
{
"devDependencies": false
}
]
}
}
\ No newline at end of file
/* 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"
})
import React from "react"
import PropTypes from "prop-types"
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 &quot;{layoutName}&quot; </h1>
}
render() {
const Layout = this.getLayout()
return (
<Layout/>
)
}
}
App.propTypes = {
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
}
App.defaultProps = {
}
import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
const propStyle = { color: "#999", fontStyle: "italic" }
export default class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
specPath: ImPropTypes.list.isRequired,
depth: PropTypes.number
}
render(){
let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props
let description = schema.get("description")
let items = schema.get("items")
let title = schema.get("title") || displayName || name
let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown")
const ModelCollapse = getComponent("ModelCollapse")
const Model = getComponent("Model")
const Property = getComponent("Property")
const titleEl = title &&
<span className="model-title">
<span className="model-title__text">{ title }</span>
</span>
/*
Note: we set `name={null}` in <Model> below because we don't want
the name of the current Model passed (and displayed) as the name of the array element Model
*/
return <span className="model">
<ModelCollapse title={titleEl} expanded={ depth <= expandDepth } collapsedContent="[...]">
[
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null
}
{
!description ? (properties.size ? <div className="markdown"></div> : null) :
<Markdown source={ description } />
}
<span>
<Model
{ ...this.props }
getConfigs={ getConfigs }
specPath={specPath.push("items")}
name={null}
schema={ items }
required={ false }
depth={ depth + 1 }
/>
</span>
]
</ModelCollapse>
</span>
}
}
import React from "react"
import PropTypes from "prop-types"
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>
<code>{ name || schema.get("name") }</code>&nbsp;
(apiKey)
<JumpToPath path={[ "securityDefinitions", name ]} />
</h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown 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>
{
value ? <code> ****** </code>
: <Col><Input type="text" onChange={ this.onChange }/></Col>
}
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
}
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class Auths extends React.Component {
static propTypes = {
schema: ImPropTypes.orderedMap.isRequired,
name: PropTypes.string.isRequired,
onAuthChange: PropTypes.func.isRequired,
authorized: ImPropTypes.orderedMap.isRequired
}
render() {
let {
schema,
name,
getComponent,
onAuthChange,
authorized,
errSelectors
} = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
let authEl
const type = schema.get("type")
switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}
return (<div key={`${name}-jump`}>
{ authEl }
</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
}
}
import React from "react"
import PropTypes from "prop-types"
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 href="#close" 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,
}
}
import React from "react"
import PropTypes from "prop-types"
export default class AuthorizeBtn extends React.Component {
static propTypes = {
onClick: PropTypes.func,
isAuthorized: PropTypes.bool,
showPopup: PropTypes.bool,
getComponent: PropTypes.func.isRequired
}
render() {
let { isAuthorized, showPopup, onClick, getComponent } = this.props
//must be moved out of button component
const AuthorizationPopup = getComponent("authorizationPopup", true)
return (
<div className="auth-wrapper">
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={onClick}>
<span>Authorize</span>
<svg width="20" height="20">
<use href={ isAuthorized ? "#locked" : "#unlocked" } xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
{ showPopup && <AuthorizationPopup /> }
</div>
)
}
}
import React from "react"
import PropTypes from "prop-types"
export default class AuthorizeOperationBtn extends React.Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired,
onClick: PropTypes.func
}
onClick =(e) => {
e.stopPropagation()
let { onClick } = this.props
if(onClick) {
onClick()
}
}
render() {
let { isAuthorized } = this.props
return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"}
aria-label={isAuthorized ? "authorization button locked" : "authorization button unlocked"}
onClick={this.onClick}>
<svg width="20" height="20">
<use href={ isAuthorized ? "#locked" : "#unlocked" } xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
)
}
}
import React from "react"
import PropTypes from "prop-types"
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)
}
close =(e) => {
e.preventDefault()
let { authActions } = this.props
authActions.showDefinitions(false)
}
render() {
let { definitions, getComponent, authSelectors, errSelectors } = this.props
const AuthItem = getComponent("AuthItem")
const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button")
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) => {
return <AuthItem
key={name}
schema={schema}
name={name}
getComponent={getComponent}
onAuthChange={this.onAuthChange}
authorized={authorized}
errSelectors={errSelectors}
/>
}).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>
}
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Close</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
}
}
import React from "react"
import PropTypes from "prop-types"
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 source={ schema.get("description") } />
</Row>
<Row>
<label>Username:</label>
{
username ? <code> { username } </code>
: <Col><Input type="text" required="required" name="username" onChange={ this.onChange }/></Col>
}
</Row>
<Row>
<label>Password:</label>
{
username ? <code> ****** </code>
: <Col><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
}
}
import React from "react"
import PropTypes from "prop-types"
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>
)
}
}
import React from "react"
import PropTypes from "prop-types"
import oauth2Authorize from "core/oauth2-authorize"
export default class Oauth2 extends React.Component {
static propTypes = {
name: PropTypes.string,
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
getConfigs: PropTypes.any
}
constructor(props, context) {
super(props, context)
let { name, schema, authorized, authSelectors } = this.props
let auth = authorized && authorized.get(name)
let authConfigs = authSelectors.getConfigs() || {}
let username = auth && auth.get("username") || ""
let clientId = auth && auth.get("clientId") || authConfigs.clientId || ""
let clientSecret = auth && auth.get("clientSecret") || authConfigs.clientSecret || ""
let passwordType = auth && auth.get("passwordType") || "basic"
this.state = {
appName: authConfigs.appName,
name: name,
schema: schema,
scopes: [],
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: "",
passwordType: passwordType
}
}
close = (e) => {
e.preventDefault()
let { authActions } = this.props
authActions.showDefinitions(false)
}
authorize =() => {
let { authActions, errActions, getConfigs, authSelectors } = this.props
let configs = getConfigs()
let authConfigs = authSelectors.getConfigs()
errActions.clear({authId: name,type: "auth", source: "auth"})
oauth2Authorize({auth: this.state, authActions, errActions, configs, authConfigs })
}
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, specSelectors
} = 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" )
const InitializedInput = getComponent("InitializedInput")
const { isOAS3 } = specSelectors
// Auth type consts
const IMPLICIT = "implicit"
const PASSWORD = "password"
const ACCESS_CODE = isOAS3() ? "authorizationCode" : "accessCode"
const APPLICATION = isOAS3() ? "clientCredentials" : "application"
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
let description = schema.get("description")
return (
<div>
<h4>{name} (OAuth2, { schema.get("flow") }) <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
{ description && <Markdown 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 ? null
: <Row>
<Row>
<label htmlFor="oauth_username">username:</label>
{
isAuthorized ? <code> { this.state.username } </code>
: <Col tablet={10} desktop={10}>
<input id="oauth_username" type="text" data-name="username" onChange={ this.onInputChange }/>
</Col>
}
</Row>
{
}
<Row>
<label htmlFor="oauth_password">password:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<input id="oauth_password" type="password" data-name="password" onChange={ this.onInputChange }/>
</Col>
}
</Row>
<Row>
<label htmlFor="password_type">Client credentials location:</label>
{
isAuthorized ? <code> { this.state.passwordType } </code>
: <Col tablet={10} desktop={10}>
<select id="password_type" data-name="passwordType" onChange={ this.onInputChange }>
<option value="basic">Authorization header</option>
<option value="request-body">Request body</option>
</select>
</Col>
}
</Row>
</Row>
}
{
( flow === APPLICATION || flow === IMPLICIT || flow === ACCESS_CODE || flow === PASSWORD ) &&
( !isAuthorized || isAuthorized && this.state.clientId) && <Row>
<label htmlFor="client_id">client_id:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<InitializedInput id="client_id"
type="text"
required={ flow === PASSWORD }
initialValue={ this.state.clientId }
data-name="clientId"
onChange={ this.onInputChange }/>
</Col>
}
</Row>
}
{
( (flow === APPLICATION || flow === ACCESS_CODE || flow === PASSWORD) && <Row>
<label htmlFor="client_secret">client_secret:</label>
{
isAuthorized ? <code> ****** </code>
: <Col tablet={10} desktop={10}>
<InitializedInput id="client_secret"
initialValue={ this.state.clientSecret }
type="text"
data-name="clientSecret"
onChange={ this.onInputChange }/>
</Col>
}
</Row>
)}
{
!isAuthorized && 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}-${flow}-checkbox-${this.state.name}`}
disabled={ isAuthorized }
type="checkbox"
onChange={ this.onScopeChange }/>
<label htmlFor={`${name}-${flow}-checkbox-${this.state.name}`}>
<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 &&
( 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>
)
}
<Button className="btn modal-btn auth btn-done" onClick={ this.close }>Close</Button>
</div>
</div>
)
}
}
import React, { Component } from "react"
import PropTypes from "prop-types"
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,
}
}
import React from "react"
import PropTypes from "prop-types"
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, ImPropTypes.seq]),
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
if(this.props.contentTypes) {
this.props.onChange(this.props.contentTypes.first())
}
}
componentWillReceiveProps(nextProps) {
if(!nextProps.contentTypes || !nextProps.contentTypes.size) {
return
}
if(!nextProps.contentTypes.includes(nextProps.value)) {
nextProps.onChange(nextProps.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>
)
}
}
import React from "react"
import PropTypes from "prop-types"
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} readOnly={true} className="curl" style={{ whiteSpace: "normal" }} value={curl}></textarea>
</div>
</div>
)
}
}
import React from "react"
import PropTypes from "prop-types"
import { presets } from "react-motion"
import ObjectInspector from "react-inspector"
export default class Debug extends React.Component {
constructor() {
super()
this.state = {
jsonDumpOpen: false
}
this.toggleJsonDump = (e) => {
e.preventDefault()
this.setState({jsonDumpOpen: !this.state.jsonDumpOpen})
}
}
plusOrMinus(bool) {
return bool ? "-" : "+"
}
render() {
let { getState, getComponent } = this.props
window.props = this.props
const Collapse = getComponent("Collapse")
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,
getComponent: PropTypes.func.isRequired,
}
import React from "react"
import PropTypes from "prop-types"
export const DeepLink = ({ enabled, path, text }) => {
return (
<a className="nostyle"
onClick={enabled ? (e) => e.preventDefault() : null}
href={enabled ? `#/${path}` : null}>
<span>{text}</span>
</a>
)
}
DeepLink.propTypes = {
enabled: PropTypes.bool,
isShown: PropTypes.bool,
path: PropTypes.string,
text: PropTypes.string
}
export default DeepLink
import React from "react"
import ImPropTypes from "react-immutable-proptypes"
const EnumModel = ({ value, getComponent }) => {
let ModelCollapse = getComponent("ModelCollapse")
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<ModelCollapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</ModelCollapse>
</span>
}
EnumModel.propTypes = {
value: ImPropTypes.iterable,
getComponent: ImPropTypes.func
}
export default EnumModel
\ No newline at end of file
import React from "react"
import PropTypes from "prop-types"
import { List } from "immutable"
export default class Errors extends React.Component {
static propTypes = {
editorActions: PropTypes.object,
errSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
}
render() {
let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props
const Collapse = getComponent("Collapse")
if(editorActions && editorActions.jumpToLine) {
var jumpToLine = editorActions.jumpToLine
}
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) => {
let type = err.get("type")
if(type === "thrown" || type === "auth") {
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} />
}
if(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 style={{ "text-decoration": "underline", "cursor": "pointer" }}>
{ errorLine && jumpToLine ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null }
</div>
</div>
}
</div>
)
}
const SpecErrorItem = ( { error, jumpToLine } ) => {
let locationMessage = null
if(error.get("path")) {
if(List.isList(error.get("path"))) {
locationMessage = <small>at { error.get("path").join(".") }</small>
} else {
locationMessage = <small>at { error.get("path") }</small>
}
} else if(error.get("line") && !jumpToLine) {
locationMessage = <small>on line { error.get("line") }</small>
}
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }&nbsp;{ locationMessage }</h4>
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span>
<div style={{ "text-decoration": "underline", "cursor": "pointer" }}>
{ 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
}
ThrownErrorItem.defaultProps = {
jumpToLine: null
}
SpecErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { stringify } from "core/utils"
export default function Example(props) {
const { example, showValue, getComponent } = props
const Markdown = getComponent("Markdown")
const HighlightCode = getComponent("highlightCode")
if(!example) return null
return (
<div className="example">
{example.get("description") ? (
<section className="example__section">
<div className="example__section-header">Example Description</div>
<p>
<Markdown source={example.get("description")} />
</p>
</section>
) : null}
{showValue && example.has("value") ? (
<section className="example__section">
<div className="example__section-header">Example Value</div>
<HighlightCode value={stringify(example.get("value"))} />
</section>
) : null}
</div>
)
}
Example.propTypes = {
example: ImPropTypes.map.isRequired,
showValue: PropTypes.bool,
getComponent: PropTypes.func.isRequired,
}
/**
* @prettier
*/
import React from "react"
import { Map, List } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { stringify } from "core/utils"
// This stateful component lets us avoid writing competing values (user
// modifications vs example values) into global state, and the mess that comes
// with that: tracking which of the two values are currently used for
// Try-It-Out, which example a modified value came from, etc...
//
// The solution here is to retain the last user-modified value in
// ExamplesSelectValueRetainer's component state, so that our global state can stay
// clean, always simply being the source of truth for what value should be both
// displayed to the user and used as a value during request execution.
//
// This approach/tradeoff was chosen in order to encapsulate the particular
// logic of Examples within the Examples component tree, and to avoid
// regressions within our current implementation elsewhere (non-Examples
// definitions, OpenAPI 2.0, etc). A future refactor to global state might make
// this component unnecessary.
//
// TL;DR: this is not our usual approach, but the choice was made consciously.
// Note that `currentNamespace` isn't currently used anywhere!
const stringifyUnlessList = input =>
List.isList(input) ? input : stringify(input)
export default class ExamplesSelectValueRetainer extends React.PureComponent {
static propTypes = {
examples: ImPropTypes.map,
onSelect: PropTypes.func,
updateValue: PropTypes.func, // mechanism to update upstream value
getComponent: PropTypes.func.isRequired,
currentUserInputValue: PropTypes.any,
currentKey: PropTypes.string,
currentNamespace: PropTypes.string,
// (also proxies props for Examples)
}
static defaultProps = {
examples: Map({}),
currentNamespace: "__DEFAULT__NAMESPACE__",
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
"ExamplesSelectValueRetainer: no `onSelect` function was provided",
...args
),
updateValue: (...args) =>
console.log( // eslint-disable-line no-console
"ExamplesSelectValueRetainer: no `updateValue` function was provided",
...args
),
}
constructor(props) {
super(props)
const valueFromExample = this._getCurrentExampleValue()
this.state = {
// user edited: last value that came from the world around us, and didn't
// match the current example's value
// internal: last value that came from user selecting an Example
[props.currentNamespace]: Map({
lastUserEditedValue: this.props.currentUserInputValue,
lastDownstreamValue: valueFromExample,
isModifiedValueSelected:
// valueFromExample !== undefined &&
this.props.currentUserInputValue !== valueFromExample,
}),
}
}
_getStateForCurrentNamespace = () => {
const { currentNamespace } = this.props
return (this.state[currentNamespace] || Map()).toObject()
}
_setStateForCurrentNamespace = obj => {
const { currentNamespace } = this.props
return this._setStateForNamespace(currentNamespace, obj)
}
_setStateForNamespace = (namespace, obj) => {
const oldStateForNamespace = this.state[namespace] || Map()
const newStateForNamespace = oldStateForNamespace.mergeDeep(obj)
return this.setState({
[namespace]: newStateForNamespace,
})
}
_isCurrentUserInputSameAsExampleValue = () => {
const { currentUserInputValue } = this.props
const valueFromExample = this._getCurrentExampleValue()
return valueFromExample === currentUserInputValue
}
_getValueForExample = (exampleKey, props) => {
// props are accepted so that this can be used in componentWillReceiveProps,
// which has access to `nextProps`
const { examples } = props || this.props
return stringifyUnlessList(
(examples || Map({})).getIn([exampleKey, "value"])
)
}
_getCurrentExampleValue = props => {
// props are accepted so that this can be used in componentWillReceiveProps,
// which has access to `nextProps`
const { currentKey } = props || this.props
return this._getValueForExample(currentKey, props || this.props)
}
_onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {
const { onSelect, updateValue, currentUserInputValue } = this.props
const { lastUserEditedValue } = this._getStateForCurrentNamespace()
const valueFromExample = this._getValueForExample(key)
if (key === "__MODIFIED__VALUE__") {
updateValue(stringifyUnlessList(lastUserEditedValue))
return this._setStateForCurrentNamespace({
isModifiedValueSelected: true,
})
}
if (typeof onSelect === "function") {
onSelect(key, { isSyntheticChange }, ...otherArgs)
}
this._setStateForCurrentNamespace({
lastDownstreamValue: valueFromExample,
isModifiedValueSelected:
isSyntheticChange &&
!!currentUserInputValue &&
currentUserInputValue !== valueFromExample,
})
// we never want to send up value updates from synthetic changes
if (isSyntheticChange) return
if (typeof updateValue === "function") {
updateValue(stringifyUnlessList(valueFromExample))
}
}
componentWillReceiveProps(nextProps) {
// update `lastUserEditedValue` as new currentUserInput values come in
const { currentUserInputValue: newValue, examples, onSelect } = nextProps
const {
lastUserEditedValue,
lastDownstreamValue,
} = this._getStateForCurrentNamespace()
const valueFromCurrentExample = this._getValueForExample(
nextProps.currentKey,
nextProps
)
const exampleMatchingNewValue = examples.find(
example =>
example.get("value") === newValue ||
// sometimes data is stored as a string (e.g. in Request Bodies), so
// let's check against a stringified version of our example too
stringify(example.get("value")) === newValue
)
if (exampleMatchingNewValue) {
onSelect(examples.keyOf(exampleMatchingNewValue), {
isSyntheticChange: true,
})
} else if (
newValue !== this.props.currentUserInputValue && // value has changed
newValue !== lastUserEditedValue && // value isn't already tracked
newValue !== lastDownstreamValue // value isn't what we've seen on the other side
) {
this._setStateForNamespace(nextProps.currentNamespace, {
lastUserEditedValue: nextProps.currentUserInputValue,
isModifiedValueSelected: newValue !== valueFromCurrentExample,
})
}
}
render() {
const { currentUserInputValue, examples, currentKey, getComponent } = this.props
const {
lastDownstreamValue,
lastUserEditedValue,
isModifiedValueSelected,
} = this._getStateForCurrentNamespace()
const ExamplesSelect = getComponent("ExamplesSelect")
return (
<ExamplesSelect
examples={examples}
currentExampleKey={currentKey}
onSelect={this._onExamplesSelect}
isModifiedValueAvailable={
!!lastUserEditedValue && lastUserEditedValue !== lastDownstreamValue
}
isValueModified={
currentUserInputValue !== undefined &&
isModifiedValueSelected &&
currentUserInputValue !== this._getCurrentExampleValue()
}
/>
)
}
}
/**
* @prettier
*/
import React from "react"
import Im from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class ExamplesSelect extends React.PureComponent {
static propTypes = {
examples: ImPropTypes.map.isRequired,
onSelect: PropTypes.func,
currentExampleKey: PropTypes.string,
isModifiedValueAvailable: PropTypes.bool,
isValueModified: PropTypes.bool,
showLabels: PropTypes.bool,
}
static defaultProps = {
examples: Im.Map({}),
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
// FIXME: remove before merging to master...
`DEBUG: ExamplesSelect was not given an onSelect callback`,
...args
),
currentExampleKey: null,
showLabels: true,
}
_onSelect = (key, { isSyntheticChange = false } = {}) => {
if (typeof this.props.onSelect === "function") {
this.props.onSelect(key, {
isSyntheticChange,
})
}
}
_onDomSelect = e => {
if (typeof this.props.onSelect === "function") {
const element = e.target.selectedOptions[0]
const key = element.getAttribute("value")
this._onSelect(key, {
isSyntheticChange: false,
})
}
}
getCurrentExample = () => {
const { examples, currentExampleKey } = this.props
const currentExamplePerProps = examples.get(currentExampleKey)
const firstExamplesKey = examples.keySeq().first()
const firstExample = examples.get(firstExamplesKey)
return currentExamplePerProps || firstExample || Map({})
}
componentDidMount() {
// this is the not-so-great part of ExamplesSelect... here we're
// artificially kicking off an onSelect event in order to set a default
// value in state. the consumer has the option to avoid this by checking
// `isSyntheticEvent`, but we should really be doing this in a selector.
// TODO: clean this up
// FIXME: should this only trigger if `currentExamplesKey` is nullish?
const { onSelect, examples } = this.props
if (typeof onSelect === "function") {
const firstExample = examples.first()
const firstExampleKey = examples.keyOf(firstExample)
this._onSelect(firstExampleKey, {
isSyntheticChange: true,
})
}
}
componentWillReceiveProps(nextProps) {
const { currentExampleKey, examples } = nextProps
if (examples !== this.props.examples && !examples.has(currentExampleKey)) {
// examples have changed from under us, and the currentExampleKey is no longer
// valid.
const firstExample = examples.first()
const firstExampleKey = examples.keyOf(firstExample)
this._onSelect(firstExampleKey, {
isSyntheticChange: true,
})
}
}
render() {
const {
examples,
currentExampleKey,
isValueModified,
isModifiedValueAvailable,
showLabels,
} = this.props
return (
<div className="examples-select">
{
showLabels ? (
<span className="examples-select__section-label">Examples: </span>
) : null
}
<select
onChange={this._onDomSelect}
value={
isModifiedValueAvailable && isValueModified
? "__MODIFIED__VALUE__"
: (currentExampleKey || "")
}
>
{isModifiedValueAvailable ? (
<option value="__MODIFIED__VALUE__">[Modified value]</option>
) : null}
{examples
.map((example, exampleName) => {
return (
<option
key={exampleName} // for React
value={exampleName} // for matching to select's `value`
>
{example.get("summary") || exampleName}
</option>
)
})
.valueSeq()}
</select>
</div>
)
}
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment