├── 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 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {rows} 53 | 54 |
RegionAverage LatencyHistory
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 | }; --------------------------------------------------------------------------------