├── src
├── components
│ ├── Footer.css
│ ├── Header.jsx
│ ├── Header.css
│ ├── ResultsListRow.css
│ ├── ResultsList.css
│ ├── Footer.jsx
│ ├── ResultsListRow.jsx
│ └── ResultsList.jsx
├── index.js
├── App.css
├── App.jsx
├── lib
│ ├── SpeedTest.js
│ └── config.js
└── AppContainer.jsx
├── public
└── index.html
├── README.md
├── LICENSE
├── .gitignore
├── package.json
├── webpack.config.production.js
└── webpack.config.js
/src/components/Footer.css:
--------------------------------------------------------------------------------
1 | footer {
2 | margin-top: 20px;
3 | margin-bottom: 20px;
4 | text-align: center;
5 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | AWS Speed Test
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import "core-js/stable";
3 | import "regenerator-runtime/runtime";
4 | import ReactDOM from 'react-dom';
5 | import App from './AppContainer';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
5 | color: #232F3E;
6 | }
7 |
8 | a {
9 | color: rgb(71, 81, 92);
10 | }
11 |
12 | a:visited {
13 | color: rgb(71, 81, 92);
14 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # aws-speed-test
2 | AWS Speed Test Site
3 |
4 | React application that measures latency to different AWS regions. Available at [awsspeedtest.xvf.dk](http://awsspeedtest.xvf.dk). Inspired by [AzureSpeedTest2](https://github.com/richorama/AzureSpeedTest2).
5 |
6 | ## License
7 | MIT
--------------------------------------------------------------------------------
/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import "./Header.css";
3 |
4 | export default class Hero extends React.PureComponent {
5 | render() {
6 | return (
7 |
8 | AWS Region Speed Test
9 |
10 | );
11 | }
12 | }
--------------------------------------------------------------------------------
/src/components/Header.css:
--------------------------------------------------------------------------------
1 | header {
2 | background: linear-gradient(to right, rgba(255,175,54,1) 0%, rgba(255,175,54,1) 38%, rgba(255,153,0,1) 100%);
3 | padding: 20px;
4 | margin-bottom: 20px;
5 | }
6 |
7 | header h1 {
8 | font-family: sans-serif;
9 | font-size: 30px;
10 | color: #232F3E;
11 | font-weight: 100;
12 | text-align: center;
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/ResultsListRow.css:
--------------------------------------------------------------------------------
1 | .resultslist-row__progress {
2 | background-color: #ccc;
3 | height: 5px;
4 | }
5 |
6 | .resultslist-row__flag {
7 | float: left;
8 | }
9 |
10 | .resultslist-row__name {
11 | float: left;
12 | margin-left: 15px;
13 | }
14 |
15 | .resultslist-row__region {
16 | font-style: italic;
17 | font-size: 14px;
18 | }
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Header from './components/Header';
4 | import ResultsList from './components/ResultsList';
5 | import Footer from './components/Footer';
6 |
7 | import "./App.css";
8 |
9 | export default class App extends React.PureComponent {
10 | static propTypes = {
11 | regions: ResultsList.propTypes.results,
12 | };
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
20 |
);
21 | }
22 | }
--------------------------------------------------------------------------------
/src/components/ResultsList.css:
--------------------------------------------------------------------------------
1 | .resultslist__container {
2 | width: 1100px;
3 | margin: auto;
4 | }
5 |
6 | .resultslist__table {
7 | width: 100%;
8 | }
9 |
10 | .resultslist__table th, td {
11 | padding-left: 20px;
12 | padding-right: 20px;
13 | padding-bottom: 1em;
14 | text-align: left;
15 | }
16 |
17 | .resultslist__region {
18 | width: 35%;
19 | }
20 |
21 | .resultslist__latency {
22 | width: 25%;
23 | }
24 |
25 | .resultslist__latencybar {
26 | width: 15%;
27 | }
28 |
29 | .resultslist__history {
30 | width: 25%;
31 | }
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import "./Footer.css";
4 |
5 | export default class Footer extends React.PureComponent {
6 | render() {
7 | return (
8 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Jeppe Lund Andersen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # Distribution folder
61 | dist/
62 |
63 | .DS_Store
--------------------------------------------------------------------------------
/src/lib/SpeedTest.js:
--------------------------------------------------------------------------------
1 | import config from './config';
2 |
3 | class SpeedTest {
4 | constructor() {
5 | this.queue = [];
6 | }
7 |
8 | async sample(url) {
9 | let startTime = performance.now();
10 | let cacheBust = Date.now();
11 | let result = await fetch(`${url}?${cacheBust}`);
12 | let endTime = performance.now();
13 | let latency = endTime - startTime;
14 |
15 | return latency;
16 | }
17 |
18 | start(regions, onSuccess) {
19 | for (const region of regions) {
20 | this.queue.push(region);
21 | }
22 |
23 | // Start 5 simultaneous test runs
24 | [...Array(5)].forEach(() => {
25 | this.runTest(this.queue.shift(), onSuccess);
26 | });
27 | }
28 |
29 | async runTest(region, onSuccess) {
30 | let result = await this.sample(region.ping);
31 | onSuccess({
32 | result,
33 | regionId: region.id,
34 | });
35 | this.queue.push(region);
36 |
37 | setTimeout(() => {
38 | this.runTest(this.queue.shift(), onSuccess);
39 | }, 500);
40 | }
41 | }
42 |
43 | export default SpeedTest;
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "awsspeedtest",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack --config webpack.config.production.js --mode production",
8 | "start": "webpack-dev-server --mode development"
9 | },
10 | "author": "Jeppe Andersen",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "@babel/core": "^7.5.5",
14 | "@babel/plugin-proposal-class-properties": "^7.5.5",
15 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
16 | "@babel/preset-env": "^7.5.5",
17 | "@babel/preset-react": "^7.0.0",
18 | "autoprefixer": "^9.6.1",
19 | "babel-loader": "^8.0.6",
20 | "css-loader": "^3.2.0",
21 | "core-js": "^3.2.1",
22 | "regenerator-runtime": "^0.13.3",
23 | "extract-text-webpack-plugin": "^3.0.2",
24 | "html-loader": "^0.5.5",
25 | "html-webpack-plugin": "^3.2.0",
26 | "postcss-loader": "^3.0.0",
27 | "prop-types": "^15.7.2",
28 | "react": "^16.9.0",
29 | "react-dom": "^16.9.0",
30 | "react-sparklines": "",
31 | "style-loader": "^1.0.0",
32 | "uglifyjs-webpack-plugin": "^2.2.0",
33 | "webpack": "^4.39.2",
34 | "webpack-cli": "^3.3.7",
35 | "webpack-dev-server": "^3.8.0"
36 | },
37 | "dependencies": {}
38 | }
39 |
--------------------------------------------------------------------------------
/src/AppContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from './App.jsx';
3 | import SpeedTest from './lib/SpeedTest';
4 | import config from './lib/config';
5 |
6 | export default class AppContainer extends React.Component {
7 | constructor() {
8 | super();
9 | let regions = this.getRegionsFromConfig();
10 |
11 | this.state = {
12 | results: regions,
13 | };
14 | }
15 |
16 | getRegionsFromConfig() {
17 | return config.locations.map(l => ({
18 | ...l,
19 | measurements: [],
20 | measurementCount: 0,
21 | latest: 0,
22 | mean: 0,
23 | }));
24 | }
25 |
26 | componentDidMount() {
27 | let Speedtest = new SpeedTest();
28 | Speedtest.start(this.state.results, this.handleReceiveMeasurement.bind(this));
29 | }
30 |
31 | handleReceiveMeasurement(data) {
32 | this.setState(prev => {
33 | let results = prev.results.map(item => {
34 | if (item.id !== data.regionId) {
35 | return item;
36 | }
37 |
38 | let newMeasurementCount = item.measurementCount + 1;
39 | return {
40 | ...item,
41 | measurements: [...item.measurements.slice(-100), data.result],
42 | measurementCount: newMeasurementCount,
43 | latest: data.result,
44 | mean: item.mean + ((data.result - item.mean) / newMeasurementCount),
45 | };
46 | });
47 |
48 | return {
49 | ...prev,
50 | results,
51 | };
52 | });
53 | }
54 |
55 | render() {
56 | return i.measurementCount > 0) } />;
57 | }
58 | }
--------------------------------------------------------------------------------
/src/components/ResultsListRow.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Sparklines, SparklinesLine } from 'react-sparklines';
4 |
5 | import './ResultsListRow.css';
6 |
7 | class ResultsListRow extends React.Component {
8 | static propTypes = {
9 | percentage: PropTypes.number,
10 | regionData: PropTypes.object,
11 | };
12 |
13 | render() {
14 | const latency = this.props.regionData.mean;
15 | const progressStyle = {
16 | width: this.props.percentage + "%",
17 | };
18 |
19 | const values = this.props.regionData.measurements.map(v => Math.trunc(v));
20 |
21 | return (
22 |
23 |
24 |
25 |
26 | 
27 |
28 |
29 | {this.props.regionData.name}
30 | {this.props.regionData.region}
31 |
32 |
33 |
34 |
35 | |
36 |
37 | {latency.toFixed(0)}ms
38 | |
39 |
40 |
41 |
42 | |
43 |
44 |
45 |
46 |
47 | |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default ResultsListRow;
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 | const autoprefixer = require('autoprefixer');
5 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
6 |
7 | module.exports = {
8 | entry: path.join(__dirname, 'src', 'index.js'),
9 | output: {
10 | filename: 'bundle.js',
11 | path: path.resolve(__dirname, 'dist'),
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.css$/,
17 | use: [
18 | { loader: 'style-loader' },
19 | { loader: 'css-loader', options: { importLoaders: 1 } },
20 | {
21 | loader: 'postcss-loader',
22 | options: {
23 | ident: 'postcss',
24 | plugins: [
25 | autoprefixer(),
26 | ],
27 | },
28 | },
29 | ],
30 | },
31 | {
32 | test: /.jsx?$/,
33 | include: [
34 | path.resolve(__dirname, 'src')
35 | ],
36 | use: {
37 | loader: 'babel-loader',
38 | options: {
39 | presets: ['@babel/env', '@babel/react'],
40 | plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
41 | },
42 | },
43 | },
44 | {
45 | test: /\.html$/,
46 | use: {
47 | loader: 'html-loader',
48 | options: {
49 | minimize: true,
50 | },
51 | },
52 | }],
53 | },
54 | plugins: [
55 | new HtmlWebpackPlugin({ template: './public/index.html' }),
56 | new UglifyJsPlugin(),
57 | ],
58 | resolve: {
59 | extensions: [".js", ".json", ".jsx"],
60 | },
61 | };
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const webpack = require('webpack');
3 | const path = require('path');
4 | const autoprefixer = require('autoprefixer');
5 |
6 | module.exports = {
7 | entry: path.join(__dirname, 'src', 'index.js'),
8 | output: {
9 | filename: 'bundle.js',
10 | path: path.resolve(__dirname, 'dist'),
11 | },
12 | module: {
13 | rules: [
14 | {
15 | test: /\.css$/,
16 | use: [
17 | { loader: 'style-loader' },
18 | { loader: 'css-loader', options: { importLoaders: 1 } },
19 | {
20 | loader: 'postcss-loader',
21 | options: {
22 | ident: 'postcss',
23 | plugins: [
24 | autoprefixer(),
25 | ]
26 | },
27 | },
28 | ],
29 | },
30 | {
31 | test: /.jsx?$/,
32 | include: [
33 | path.resolve(__dirname, 'src')
34 | ],
35 | use: {
36 | loader: 'babel-loader',
37 | options: {
38 | presets: ['@babel/env', '@babel/react'],
39 | plugins: ['@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties'],
40 | },
41 | },
42 | },
43 | {
44 | test: /\.html$/,
45 | use: {
46 | loader: 'html-loader',
47 | options: {
48 | minimize: true,
49 | },
50 | },
51 | }],
52 | },
53 | devtool: 'source-map',
54 | devServer: {
55 | contentBase: path.join(__dirname, "dist"),
56 | },
57 | plugins: [
58 | new HtmlWebpackPlugin({ template: './public/index.html' }),
59 | ],
60 | resolve: {
61 | extensions: [".js", ".json", ".jsx"],
62 | },
63 | };
--------------------------------------------------------------------------------
/src/components/ResultsList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ResultsListRow from './ResultsListRow';
4 |
5 | import './ResultsList.css';
6 |
7 | export default class ResultsList extends React.Component {
8 | static propTypes = {
9 | results: PropTypes.arrayOf(PropTypes.shape({
10 | id: PropTypes.string.isRequired,
11 | name: PropTypes.string.isRequired,
12 | flag: PropTypes.string.isRequired,
13 | measurements: PropTypes.arrayOf(PropTypes.number).isRequired,
14 | mean: PropTypes.number.isRequired,
15 | latest: PropTypes.number.isRequired,
16 | })).isRequired,
17 | };
18 |
19 | render() {
20 | let regions = Object.keys(this.props.results);
21 | regions.sort((a, b) => this.props.results[a].mean - this.props.results[b].mean);
22 |
23 | let regionData = regions.map(r => ({
24 | region: this.props.results[r].id,
25 | latestMeasurement: this.props.results[r].latest,
26 | measurements: this.props.results[r].measurements,
27 | mean: this.props.results[r].mean,
28 | flag: this.props.results[r].flag,
29 | name: this.props.results[r].name,
30 | }));
31 |
32 | let max = Math.max(...regionData.map(r => r.mean));
33 |
34 | let rows = regionData.map(r => (
35 | ));
39 |
40 | return (
41 |
42 |
43 |
44 |
45 | | Region |
46 | Average Latency |
47 | |
48 | History |
49 |
50 |
51 |
52 | {rows}
53 |
54 |
55 |
56 | )
57 | }
58 | }
--------------------------------------------------------------------------------
/src/lib/config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | locations: [
3 | { id: 'ap-east-1', region: 'ap-east-1', name: 'Asia Pacific (Hong Kong)', ping: 'http://dynamodb.ap-east-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/hk.svg' },
4 | { id: 'ap-northeast-1', region: 'ap-northeast-1', name: 'Asia Pacific (Tokyo)', ping: 'http://dynamodb.ap-northeast-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/jp.svg' },
5 | { id: 'ap-northeast-2', region: 'ap-northeast-2', name: 'Asia Pacific (Seoul)', ping: 'http://dynamodb.ap-northeast-2.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/kr.svg' },
6 | { id: 'ap-northeast-3', region: 'ap-northeast-3', name: 'Asia Pacific (Osaka-Local)', ping: 'http://dynamodb.ap-northeast-3.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/jp.svg' },
7 | { id: 'ap-southeast-1', region: 'ap-southeast-1', name: 'Asia Pacific (Singapore)', ping: 'http://dynamodb.ap-southeast-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/sg.svg' },
8 | { id: 'ap-southeast-2', region: 'ap-southeast-2', name: 'Asia Pacific (Sydney)', ping: 'http://dynamodb.ap-southeast-2.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/au.svg' },
9 | { id: 'ap-south-1', region: 'ap-south-1', name: 'Asia Pacific (Mumbai)', ping: 'http://dynamodb.ap-south-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/in.svg' },
10 | { id: 'ca-central-1', region: 'ca-central-1', name: 'Canada (Central)', ping: 'http://dynamodb.ca-central-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/ca.svg' },
11 | { id: 'cn-north-1', region: 'ca-north-1', name: 'China (Beijing)', ping: 'http://dynamodb.cn-north-1.amazonaws.com.cn/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/cn.svg' },
12 | { id: 'cn-northwest-1', region: 'ca-northwest-1', name: 'China (Ningxia)', ping: 'http://dynamodb.cn-northwest-1.amazonaws.com.cn/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/cn.svg' },
13 | { id: 'me-south-1', region: 'me-south-1', name: 'Middle East (Bahrain)', ping: 'http://dynamodb.me-south-1.amazonaws.com.cn/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/bh.svg' },
14 | { id: 'eu-west-1', region: 'eu-west-1', name: 'EU (Ireland)', ping: 'http://dynamodb.eu-west-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/ie.svg' },
15 | { id: 'eu-west-2', region: 'eu-west-2', name: 'EU (London)', ping: 'http://dynamodb.eu-west-2.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/gb.svg' },
16 | { id: 'eu-west-3', region: 'eu-west-3', name: 'EU (Paris)', ping: 'http://dynamodb.eu-west-3.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/fr.svg' },
17 | { id: 'eu-central-1', region: 'eu-central-1', name: 'EU (Frankfurt)', ping: 'http://dynamodb.eu-central-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/de.svg' },
18 | { id: 'eu-north-1', region: 'eu-north-1', name: 'EU (Stockholm)', ping: 'http://dynamodb.eu-north-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/se.svg' },
19 | { id: 'sa-east-1', region: 'sa-east-1', name: 'South America (São Paulo)', ping: 'http://dynamodb.sa-east-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/br.svg' },
20 | { id: 'us-west-1', region: 'us-west-1', name: 'US West (N. Carolina)', ping: 'http://dynamodb.us-west-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
21 | { id: 'us-west-2', region: 'us-west-2', name: 'US West (Oregon)', ping: 'http://dynamodb.us-west-2.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
22 | { id: 'us-east-1', region: 'us-east-1', name: 'US East (N. Virginia)', ping: 'http://dynamodb.us-east-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
23 | { id: 'us-east-2', region: 'us-east-2', name: 'US East (Ohio)', ping: 'http://dynamodb.us-east-2.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
24 | { id: 'us-gov-west-1', region: 'us-gov-west-1', name: 'GovCloud (US-West)', ping: 'http://dynamodb.us-gov-west-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
25 | { id: 'us-gov-east-1', region: 'us-gov-east-1', name: 'GovCloud (US-East)', ping: 'http://dynamodb.us-gov-east-1.amazonaws.com/ping', flag: 'https://cdn.jsdelivr.net/gh/lipis/flag-icon-css@master/flags/4x3/us.svg' },
26 | ],
27 | };
--------------------------------------------------------------------------------