Browse Source

Move typing system from 'flow' to 'typescript'

Flow, initially, had a great momentum in the React community, but
Typescript has been rapidly improving both in community support and
tooling.

The decision to move to Typescript is made easier by Typescript having
Babel support. This means that instead of transpiling Typescript to
Javascript using the ts-loader, we can now use Babel to simply strip the
Typescript code, similar to Flow's process, making it much faster (since
Babel itself doesn't do type checking) and allowing us to keep using
Babel plugins.

More motivation points for moving to Typescript:

- It is more well-maintained, it has more monthly contributors and more
monthly closed pull requests
- It is more widely used (2.2m vs 76.1k packages)
- It has better editor integration (notably Microsoft Visual Studio
Code) and it is faster: auto import, refactoring etc.
- More libraries are written in Typescript. Flow has a lot of outdated
or missing types like 'styled-components'.
- Flow has type-related bugs that may silently bail
- Flow has a greater memory footprint
- Flow often crashes especially on Windows systems

A list of general differences between the 2 products is available here:
https://github.com/niieani/typescript-vs-flowtype

This refactoring includes the use of a new React router (no more ‘/#/‘
in the development route path) and updated libraries across the board.

Development libraries and tools have been replaced with newer ones which
means that the development process has suffered a lot of changes, most
notably, the development server is initiated by webpack instead of node.

Performance improvements can also be noticed as a result of version
jumping for a lot of libraries.

Environment variables can now be set in a root `.env` file, very useful
for development.

The version is incremented to 2.0.0 and it includes some *breaking
changes*:

- development build is started using different commands (see the
README.md)
- production build should now be started with `yarn start` instead of
`node server`, but `node server` command is still maintained for
backward compatibilty.
Sergiu Miclea 5 năm trước cách đây
mục cha
commit
2c6427f7d1
100 tập tin đã thay đổi với 816 bổ sung1024 xóa
  1. 0 46
      .babelrc
  2. 1 0
      .dockerignore
  3. 1 3
      .eslintignore
  4. 42 127
      .eslintrc
  5. 0 19
      .flowconfig
  6. 15 0
      .githooks/pre-commit
  7. 0 29
      .github/workflows/nodeci-master.yml
  8. 0 26
      .github/workflows/nodeci-pr.yml
  9. 1 2
      .gitignore
  10. 26 0
      .storybook/main.js
  11. 38 0
      .storybook/preview.js
  12. 19 3
      .vscode/settings.json
  13. 16 6
      Dockerfile
  14. 21 11
      README.md
  15. 31 0
      babel.config.js
  16. 9 7
      config.ts
  17. 0 17
      flow-typed/module_vx.x.x.js
  18. 67 95
      package.json
  19. 0 45
      private/storybook/Decorator.jsx
  20. 0 18
      private/storybook/config.js
  21. 0 12
      private/storybook/webpack.config.js
  22. 1 2
      public/index.html
  23. 13 14
      server/api/ConfigApi.ts
  24. 21 22
      server/api/LogosApi.ts
  25. 1 4
      server/api/router.ts
  26. 15 13
      server/azureProxy.ts
  27. 0 38
      server/dev.js
  28. 3 0
      server/index.js
  29. 0 66
      server/main.js
  30. 39 0
      server/main.ts
  31. 5 0
      server/start.js
  32. 6 11
      src/@types/Assessment.ts
  33. 0 2
      src/@types/Cache.ts
  34. 1 3
      src/@types/Config.ts
  35. 5 6
      src/@types/Endpoint.ts
  36. 0 2
      src/@types/Execution.ts
  37. 27 15
      src/@types/Field.ts
  38. 3 5
      src/@types/Instance.ts
  39. 0 2
      src/@types/Licence.ts
  40. 0 2
      src/@types/Log.ts
  41. 28 24
      src/@types/MainItem.ts
  42. 1 3
      src/@types/Network.ts
  43. 1 3
      src/@types/NotificationItem.ts
  44. 0 2
      src/@types/Project.ts
  45. 7 2
      src/@types/Providers.ts
  46. 2 2
      src/@types/Schedule.ts
  47. 3 5
      src/@types/Schema.ts
  48. 0 2
      src/@types/Task.ts
  49. 2 3
      src/@types/User.ts
  50. 6 8
      src/@types/WizardData.ts
  51. 17 0
      src/@types/declarations.d.ts
  52. 57 49
      src/components/App.tsx
  53. 6 7
      src/components/atoms/Arrow/Arrow.tsx
  54. 1 1
      src/components/atoms/Arrow/images/arrow-thick.ts
  55. 1 1
      src/components/atoms/Arrow/images/arrow.ts
  56. 2 1
      src/components/atoms/Arrow/package.json
  57. 0 0
      src/components/atoms/Arrow/story.tsx
  58. 28 26
      src/components/atoms/AutocompleteInput/AutocompleteInput.tsx
  59. 0 0
      src/components/atoms/AutocompleteInput/images/arrow.ts
  60. 2 1
      src/components/atoms/AutocompleteInput/package.json
  61. 0 2
      src/components/atoms/AutocompleteInput/story.tsx
  62. 6 7
      src/components/atoms/AutocompleteInput/test.tsx
  63. 13 16
      src/components/atoms/Button/Button.tsx
  64. 2 1
      src/components/atoms/Button/package.json
  65. 7 7
      src/components/atoms/Button/story.tsx
  66. 3 2
      src/components/atoms/Button/test.tsx
  67. 10 12
      src/components/atoms/Checkbox/Checkbox.tsx
  68. 2 1
      src/components/atoms/Checkbox/package.json
  69. 8 6
      src/components/atoms/Checkbox/story.tsx
  70. 3 2
      src/components/atoms/Checkbox/test.tsx
  71. 1 2
      src/components/atoms/CopyButton/CopyButton.tsx
  72. 2 1
      src/components/atoms/CopyButton/package.json
  73. 1 1
      src/components/atoms/CopyButton/story.tsx
  74. 3 2
      src/components/atoms/CopyButton/test.tsx
  75. 9 8
      src/components/atoms/CopyMultilineValue/CopyMultilineValue.tsx
  76. 2 1
      src/components/atoms/CopyMultilineValue/package.json
  77. 3 2
      src/components/atoms/CopyMultilineValue/test.tsx
  78. 11 12
      src/components/atoms/CopyValue/CopyValue.tsx
  79. 2 1
      src/components/atoms/CopyValue/package.json
  80. 3 2
      src/components/atoms/CopyValue/test.tsx
  81. 41 35
      src/components/atoms/DropdownButton/DropdownButton.tsx
  82. 0 0
      src/components/atoms/DropdownButton/images/arrow.ts
  83. 2 1
      src/components/atoms/DropdownButton/package.json
  84. 6 6
      src/components/atoms/DropdownButton/story.tsx
  85. 3 2
      src/components/atoms/DropdownButton/test.tsx
  86. 11 10
      src/components/atoms/EndpointLogos/EndpointLogos.tsx
  87. 2 1
      src/components/atoms/EndpointLogos/package.json
  88. 9 11
      src/components/atoms/EndpointLogos/resources/Generic.tsx
  89. 14 12
      src/components/atoms/EndpointLogos/story.tsx
  90. 3 2
      src/components/atoms/EndpointLogos/test.tsx
  91. 0 2
      src/components/atoms/Fonts/index.ts
  92. 8 13
      src/components/atoms/HorizontalLoading/HorizontalLoading.tsx
  93. 2 1
      src/components/atoms/HorizontalLoading/package.json
  94. 0 2
      src/components/atoms/HorizontalLoading/story.tsx
  95. 6 8
      src/components/atoms/InfoIcon/InfoIcon.tsx
  96. 2 1
      src/components/atoms/InfoIcon/package.json
  97. 2 2
      src/components/atoms/InfoIcon/story.tsx
  98. 21 13
      src/components/atoms/Logo/Logo.tsx
  99. 2 1
      src/components/atoms/Logo/package.json
  100. 0 0
      src/components/atoms/Logo/story.tsx

+ 0 - 46
.babelrc

@@ -1,46 +0,0 @@
-{
-  "presets": [
-    [
-      "env",
-      {
-        "modules": false
-      }
-    ],
-    "flow",
-    "react",
-    "stage-1"
-  ],
-  "plugins": [
-    "react-hot-loader/babel",
-    "transform-decorators-legacy"
-  ],
-  "env": {
-    "development": {
-      "plugins": [
-        "transform-es2015-modules-commonjs",
-        [
-          "styled-components",
-          {
-            "minify": false
-          }
-        ]
-      ]
-    },
-    "test": {
-      "plugins": [
-        "transform-es2015-modules-commonjs"
-      ]
-    },
-    "production": {
-      "plugins": [
-        "transform-react-remove-prop-types",
-        [
-          "styled-components",
-          {
-            "displayName": false
-          }
-        ]
-      ]
-    }
-  }
-}

+ 1 - 0
.dockerignore

@@ -4,3 +4,4 @@
 node_modules
 node_modules
 *.log
 *.log
 *.md
 *.md
+.env

+ 1 - 3
.eslintignore

@@ -1,3 +1 @@
-# flow-typed
-flow-typed/npm/*
-!flow-typed/npm/module_vx.x.x.js
+

+ 42 - 127
.eslintrc

@@ -1,146 +1,61 @@
 {
 {
-  "parser": "babel-eslint",
   "extends": [
   "extends": [
-    "airbnb"
+    "airbnb-typescript"
   ],
   ],
   "env": {
   "env": {
     "browser": true,
     "browser": true,
-    "jest": true
-  },
-  "globals": {
-    "__DEV__": true,
-    "__PROD__": true,
-    "__DEBUG__": true,
-    "__COVERAGE__": true,
-    "__BASENAME__": true
-  },
-  "settings": {
-    "import/resolver": {
-      "webpack": {
-        "config": "webpack.config.js"
-      }
-    }
+    "node": true
   },
   },
+  "globals": {},
+  "ignorePatterns": [
+    "*.svg",
+    "*.png",
+    "*.bash",
+    "*.log",
+    "*.jpg",
+    "*.woff",
+    "src/**/test.tsx",
+    "src/**/package.json"
+  ],
   "rules": {
   "rules": {
-    "react/sort-comp": [
-      1,
-      {
-        "order": [
-          "static-methods",
-          "displayName",
-          "propTypes",
-          "contextTypes",
-          "childContextTypes",
-          "mixins",
-          "statics",
-          "defaultProps",
-          "state",
-          "type-annotations",
-          "instance-variables",
-          "getters",
-          "setters",
-          "constructor",
-          "getDefaultProps",
-          "getInitialState",
-          "getChildContext",
-          "getDerivedStateFromProps",
-          "lifecycle",
-          "everything-else",
-          "^handle.+$",
-          "render"
-        ],
-        "groups": {
-          "lifecycle": [
-            "componentWillMount",
-            "componentDidMount",
-            "componentWillReceiveProps",
-            "shouldComponentUpdate",
-            "componentWillUpdate",
-            "getSnapshotBeforeUpdate",
-            "componentDidUpdate",
-            "componentDidCatch",
-            "componentWillUnmount"
-          ]
-        }
-      }
-    ],
-    "semi": [
+    "react/jsx-one-expression-per-line": "off",
+    "@typescript-eslint/semi": [
       2,
       2,
       "never"
       "never"
     ],
     ],
-    "comma-dangle": [
+    "arrow-parens": [
       2,
       2,
-      "always-multiline"
+      "as-needed"
     ],
     ],
-    "newline-per-chained-call": 0,
-    "class-methods-use-this": 0,
-    "max-len": 0,
-    "prefer-const": 0,
-    "arrow-parens": 0,
-    "react/prefer-stateless-function": 0,
-    "react/no-array-index-key": 0,
-    "react/no-danger": 0,
-    "no-param-reassign": 0,
-    "no-shadow": 0,
-    "arrow-body-style": 0,
-    "global-require": 0,
-    "no-unused-expressions": 0,
-    "no-confusing-arrow": 0,
     "no-console": "off",
     "no-console": "off",
-    "no-nested-ternary": 0,
-    "import/no-dynamic-require": 0,
-    "import/no-unresolved": 0,
-    "import/extensions": 0,
-    "import/no-extraneous-dependencies": 0,
-    "import/prefer-default-export": 0,
-    "react/require-default-props": 0,
-    "react/forbid-prop-types": 0,
-    "jsx-a11y/href-no-hash": 0,
-    "flowtype/boolean-style": [
-      "error",
-      "boolean"
-    ],
-    "flowtype/define-flow-type": "warn",
-    "flowtype/delimiter-dangle": [
-      "error",
-      "only-multiline"
-    ],
-    "flowtype/generic-spacing": [
-      "error",
-      "never"
-    ],
-    "flowtype/no-primitive-constructor-types": "error",
-    "flowtype/object-type-delimiter": [
-      "error",
-      "comma"
-    ],
-    "flowtype/require-parameter-type": "off",
-    "flowtype/require-return-type": "off",
-    "flowtype/require-valid-file-annotation": "off",
-    "flowtype/semi": [
-      "error",
-      "never"
-    ],
-    "flowtype/space-after-type-colon": [
+    "class-methods-use-this": "off",
+    "no-underscore-dangle": "off",
+    "jsx-a11y/mouse-events-have-key-events": "off",
+    "react/jsx-no-duplicate-props": "off",
+    "no-nested-ternary": "off",
+    "no-dupe-class-members": "off",
+    "object-curly-spacing": [
       "error",
       "error",
       "always"
       "always"
     ],
     ],
-    "flowtype/space-before-generic-bracket": [
-      "error",
-      "never"
-    ],
-    "flowtype/space-before-type-colon": [
+    "no-throw-literal": "off",
+    "@typescript-eslint/type-annotation-spacing": [
       "error",
       "error",
-      "never"
-    ],
-    "flowtype/union-intersection-spacing": [
-      "error",
-      "always"
+      {
+        "after": true
+      }
     ],
     ],
-    "flowtype/use-flow-type": "error",
-    "flowtype/valid-syntax": "error"
-  },
-  "plugins": [
-    "flowtype"
-  ]
+    "react/state-in-constructor": "off",
+    "react/destructuring-assignment": "off",
+    "import/no-extraneous-dependencies": "off",
+    "react/static-property-placement": "off",
+    "react/no-danger": "off",
+    "prefer-destructuring": "off",
+    "import/no-cycle": "off",
+    "@typescript-eslint/camelcase": "off",
+    "react/jsx-props-no-spreading": "off",
+    "max-classes-per-file": "off",
+    "prefer-promise-reject-errors": "off",
+    "import/prefer-default-export": "off"
+  }
 }
 }

+ 0 - 19
.flowconfig

@@ -1,19 +0,0 @@
-[ignore]
-<PROJECT_ROOT>/node_modules/*
-<PROJECT_ROOT>/dist/.*
-<PROJECT_ROOT>/private/.*
-
-[libs]
-
-[options]
-esproposal.class_static_fields=enable
-esproposal.class_instance_fields=enable
-esproposal.export_star_as=enable
-esproposal.decorators=ignore
-module.name_mapper.extension='css' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
-module.name_mapper.extension='styl' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
-module.name_mapper.extension='scss' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
-module.name_mapper.extension='png' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
-module.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
-suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
-suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore

+ 15 - 0
.githooks/pre-commit

@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Run this command after repo checkout to enable git hooks
+# git config core.hooksPath .githooks
+
+printf "\nRunning TSC:\n"
+
+yarn tsc
+
+if [[ "$?" == 0 ]]; then
+  printf "\t\033[32mTSC Passed \e[0m"
+else
+  printf "\t\033[41mTSC Failed \e[0m"
+  exit 1
+fi

+ 0 - 29
.github/workflows/nodeci-master.yml

@@ -1,29 +0,0 @@
-name: Master
-
-on:
-  push:
-    branches:
-    - master
-      
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        node-version: [12.x]
-
-    steps:
-    - uses: actions/checkout@v1
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
-      with:
-        node-version: ${{ matrix.node-version }}
-    - name: npm install, build, and test
-      run: |
-        npm install
-        npm run build --if-present
-        npm test
-      env:
-        CI: true

+ 0 - 26
.github/workflows/nodeci-pr.yml

@@ -1,26 +0,0 @@
-name: PR_Testing
-
-on: [pull_request]
-
-jobs:
-  build:
-
-    runs-on: ubuntu-latest
-
-    strategy:
-      matrix:
-        node-version: [12.x]
-
-    steps:
-    - uses: actions/checkout@v1
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
-      with:
-        node-version: ${{ matrix.node-version }}
-    - name: npm install, build, and test
-      run: |
-        npm install
-        npm run build --if-present
-        npm test
-      env:
-        CI: true

+ 1 - 2
.gitignore

@@ -3,6 +3,5 @@
 dist
 dist
 *.log
 *.log
 node_modules
 node_modules
-flow-typed/npm/*
-!flow-typed/npm/module_vx.x.x.js
 private/cypress/config.js
 private/cypress/config.js
+.env

+ 26 - 0
.storybook/main.js

@@ -0,0 +1,26 @@
+const baseConfig = require('../webpack.common')
+
+
+module.exports = {
+  stories: ['../src/**/story.tsx'],
+  webpackFinal: config => {
+    return {
+      ...config,
+      module: {
+        ...config.module,
+        rules: [
+          ...baseConfig.module.rules,
+          {
+            test: /\.js$/,
+            exclude: /node_modules/,
+            loader: require.resolve('babel-loader'),
+          },
+        ]
+      },
+      resolve: {
+        ...config.resolve,
+        ...baseConfig.resolve
+      },
+    }
+  }
+}

+ 38 - 0
.storybook/preview.js

@@ -0,0 +1,38 @@
+import React from 'react'
+import { addDecorator } from '@storybook/react'
+import styled, { createGlobalStyle } from 'styled-components'
+
+import Palette from '../src/components/styleUtils/Palette'
+import StyleProps from '../src/components/styleUtils/StyleProps'
+import Fonts from '../src/components/atoms/Fonts'
+import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
+
+const Wrapper = styled.div`
+  display: inline-block;
+  background: ${Palette.grayscale[7]};
+  padding: 32px;
+`
+
+const GlobalStyle = createGlobalStyle`
+  ${Fonts}
+  body {
+    color: ${Palette.black};
+    font-family: Rubik;
+    font-size: 14px;
+    font-weight: ${StyleProps.fontWeights.regular};
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+`
+
+addDecorator(storyFn => (
+  <Router>
+    <Switch>
+      <Wrapper>
+        <GlobalStyle />
+        {storyFn()}
+      </Wrapper>
+    </Switch>
+  </Router>
+  )
+)

+ 19 - 3
.vscode/settings.json

@@ -1,5 +1,21 @@
 {
 {
-  "flow.useNPMPackagedFlow": true,
-  "javascript.validate.enable": false,
-  "files.eol": "\n",
+  "editor.formatOnSave": true,
+  "[javascript]": {
+    "editor.formatOnSave": false,
+  },
+  "[javascriptreact]": {
+    "editor.formatOnSave": false,
+  },
+  "[typescript]": {
+    "editor.formatOnSave": false,
+  },
+  "[typescriptreact]": {
+    "editor.formatOnSave": false,
+  },
+  "debug.node.autoAttach": "on",
+  "editor.codeActionsOnSave": {
+    "source.fixAll.eslint": true
+  },
+  "eslint.enable": true,
+  "files.eol": "\n"
 }
 }

+ 16 - 6
Dockerfile

@@ -1,11 +1,21 @@
-FROM node:12.5.0
+FROM ubuntu:18.04
 
 
-WORKDIR /usr/src/app
-COPY . .
+WORKDIR /root
 
 
-RUN yarn install --production
-RUN yarn build
+RUN apt-get update && apt-get install -y curl gnupg
+RUN curl --silent --location https://deb.nodesource.com/setup_12.x | bash -
+RUN apt-get update && apt-get install -y nodejs
+
+RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
+RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
+RUN apt-get update && apt-get install -y yarn
+
+WORKDIR /root/coriolis-web
 
 
-ENTRYPOINT [ "node", "server.js" ]
+ADD ./ .
+
+RUN yarn install --production --no-progress
+RUN yarn build
 
 
+ENTRYPOINT [ "yarn", "start" ]
 EXPOSE 3000
 EXPOSE 3000

+ 21 - 11
README.md

@@ -6,30 +6,28 @@ Web  GUI for [coriolis](https://github.com/cloudbase/coriolis)
 
 
 ## Install instructions
 ## Install instructions
 
 
-- [node](https://nodejs.org/en/download/package-manager/) >=6.x and [yarn](https://yarnpkg.com/lang/en/docs/install/) are required
+- [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/lang/en/docs/install/) are required
 - clone repo
 - clone repo
 - run `yarn install` or `yarn install --production` to install packages and dependencies for development or production mode
 - run `yarn install` or `yarn install --production` to install packages and dependencies for development or production mode
-- change the `coriolisUrl` variable in ./src/config.js to match the Coriolis Server path
+- set `CORIOLIS_URL` environment variable
 
 
 ## Build instructions
 ## Build instructions
 
 
 - run `yarn build`
 - run `yarn build`
-- run `node server.js` to start the server
+- run `yarn start` to start the server
 
 
-Your server will be running at `http://localhost:3000/`
+Your server will be running at `http://localhost:3000/` (the port is configurable through `PORT` environment variable)
 
 
 ## Testing
 ## Testing
 
 
 - unit tests can be run using `yarn test`
 - unit tests can be run using `yarn test`
-- e2e integration tests can be run using `yarn cypress`. First though, you have to create the `private/cypress/config.js` file using `private/cypress/config.template.js` as a template and then run `yarn build` and `node server`.
+- e2e integration tests can be run using `yarn e2e`. First though, you have to create the `private/cypress/config.js` file using `private/cypress/config.template.js` as a template and then run `yarn build` and `node server`.
 
 
 ## Development mode
 ## Development mode
 
 
-- run `yarn start` to start local development server
-
-Your development server will be running at `http://localhost:3000/`
-
-This should be used only for development, as it contains live-reload and other development tools.
+- set env. variable `ENV='development'`
+- run `yarn ui-dev` to start local development server (starts on port 3001)
+- run `yarn server-dev` to start the express server in development mode
 
 
 You can view some of the UIs components in the [Storybook](https://github.com/storybooks/storybook) by running `yarn storybook`
 You can view some of the UIs components in the [Storybook](https://github.com/storybooks/storybook) by running `yarn storybook`
 
 
@@ -43,4 +41,16 @@ Any provider logos can be replaced using local logo images. The local image file
 
 
 You can specify one logo, in which case it will be scaled to all sizes. You can also specify logos for just a couple of the sizes, in which case the closest size to the one required will be used. Open [`ui-mod-sample.json`](ui-mod-sample.json) for more details.
 You can specify one logo, in which case it will be scaled to all sizes. You can also specify logos for just a couple of the sizes, in which case the closest size to the one required will be used. Open [`ui-mod-sample.json`](ui-mod-sample.json) for more details.
 
 
-Any option from [`config.js`](config.js) can be modified by adding the `config` field to the [`ui-mod-sample.json`](ui-mod-sample.json) file.
+Any option from [`config.ts`](config.ts) can be modified by adding the `config` field to the [`ui-mod-sample.json`](ui-mod-sample.json) file.
+
+## Environment variables
+
+All environment variables can be set in a `.env` file created in the root directory.
+
+The following is the list of environment variables and their default values:
+
+```(bash)
+NODE_MODE='production'
+CORIOLIS_URL='<your-coriolis-url>'
+MOD_JSON='<path-to-json>'
+```

+ 31 - 0
babel.config.js

@@ -0,0 +1,31 @@
+module.exports = api => {
+  api.cache.using(() => process.env.NODE_MODE)
+
+  const common = {
+    presets: [
+      ['@babel/env', { targets: { node: true } }],
+      '@babel/typescript',
+      '@babel/react',
+    ],
+    plugins: [
+      'react-hot-loader/babel',
+      [
+        '@babel/plugin-proposal-decorators',
+        {
+          legacy: true,
+        },
+      ],
+      '@babel/proposal-class-properties',
+      '@babel/proposal-object-rest-spread',
+      '@babel/plugin-proposal-optional-chaining',
+    ],
+  }
+
+  if (process.env.NODE_MODE === 'development') {
+    common.plugins.push(['babel-plugin-styled-components', { displayName: true, minify: false }])
+  } else {
+    common.plugins.push(['babel-plugin-styled-components', { displayName: false, minify: true }])
+  }
+
+  return common
+}

+ 9 - 7
config.js → config.ts

@@ -1,6 +1,4 @@
-// @flow
-
-import type { Config } from './src/types/Config'
+import type { Config } from './src/@types/Config'
 
 
 const conf: Config = {
 const conf: Config = {
 
 
@@ -31,14 +29,16 @@ const conf: Config = {
 
 
   // - Specifies the `limit` for each provider when listing all its VMs for pagination.
   // - Specifies the `limit` for each provider when listing all its VMs for pagination.
   // - If the provider is not in this list, the 'default' value will be used.
   // - If the provider is not in this list, the 'default' value will be used.
-  // - If the `default` value is lower than the number of instances that fit into a page, the latter number will be used.
+  // - If the `default` value is lower than the number of instances that
+  // fit into a page, the latter number will be used.
   // - `Infinity` value means no `limit` will be used, i.e. all VMs will be listed.
   // - `Infinity` value means no `limit` will be used, i.e. all VMs will be listed.
   instancesListBackgroundLoading: { default: 10, ovm: Infinity, 'hyper-v': Infinity },
   instancesListBackgroundLoading: { default: 10, ovm: Infinity, 'hyper-v': Infinity },
 
 
   /**
   /**
    * The list of providers for which and extra source or destination options API call will be made,
    * The list of providers for which and extra source or destination options API call will be made,
    * if the required fields have any value set.
    * if the required fields have any value set.
-   * If `requiredValues` is provided, the field specified there needs to have a certain value (specified in values)
+   * If `requiredValues` is provided, the field specified there needs to have a
+   * certain value (specified in values)
    * in order to make the options API call.
    * in order to make the options API call.
    */
    */
   extraOptionsApiCalls: [
   extraOptionsApiCalls: [
@@ -96,11 +96,13 @@ const conf: Config = {
   // The list of the users to hide in the UI
   // The list of the users to hide in the UI
   hiddenUsers: ['barbican', 'coriolis'],
   hiddenUsers: ['barbican', 'coriolis'],
 
 
-  // By default, if a field name contains `password` in it (ex.: `user_password`), it will be rendered as a password input
+  // By default, if a field name contains `password` in it (ex.: `user_password`),
+  // it will be rendered as a password input
   // If the field doesn't contain `password` in its name, the following list will be used instead
   // If the field doesn't contain `password` in its name, the following list will be used instead
   passwordFields: ['private_key_passphrase', 'secret_access_key'],
   passwordFields: ['private_key_passphrase', 'secret_access_key'],
 
 
-  // The number of items per page applicable to main lists: replicas, migrations, endpoints, users etc.
+  // The number of items per page applicable to main lists:
+  // replicas, migrations, endpoints, users etc.
   mainListItemsPerPage: 20,
   mainListItemsPerPage: 20,
 
 
   servicesUrls: {
   servicesUrls: {

+ 0 - 17
flow-typed/module_vx.x.x.js

@@ -1,17 +0,0 @@
-declare module 'module' {
-  declare module.exports: any;
-}
-
-declare module 'moment/locale/en-gb' {
-  declare module.exports: any;
-}
-
-declare module 'mobx' {
-  declare module.exports: any;
-}
-
-// Cypress
-declare var cy: any
-declare var Cypress: any
-declare var before: any
-declare var after: any

+ 67 - 95
package.json

@@ -1,121 +1,93 @@
 {
 {
   "name": "coriolis-web",
   "name": "coriolis-web",
-  "version": "1.5.1",
+  "version": "2.0.0",
   "license": "AGPL-3.0",
   "license": "AGPL-3.0",
   "scripts": {
   "scripts": {
-    "start": "npm run env:dev && node server.js --dev",
-    "env:dev": "cross-env NODE_ENV=development",
-    "env:prod": "cross-env NODE_ENV=production",
-    "cypress": "cypress open",
-    "test": "jest",
-    "testc": "jest --runInBand -t 'WizardStorage Component'",
-    "storybook": "start-storybook -p 9001 -c private/storybook",
-    "lint": "eslint src private webpack.config.js --ext js,jsx",
-    "build:clean": "rimraf \"dist/!(.git*|Procfile)**\"",
-    "build:copy": "copyfiles -u 1 public/* public/**/* dist",
-    "prebuild": "npm run build:clean && npm run build:copy",
-    "build": "npm run env:prod -- webpack",
-    "flow": "flow",
-    "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true"
-  },
-  "jest": {
-    "verbose": true,
-    "moduleDirectories": [
-      "src",
-      "node_modules"
-    ],
-    "moduleNameMapper": {
-      "^.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/private/jest/fileMock.js",
-      "^components$": "<rootDir>/private/jest/componentsMock.js"
-    },
-    "setupFiles": [
-      "<rootDir>/private/jest/shim.js",
-      "<rootDir>/private/jest/setupTests.js"
-    ],
-    "testURL": "http://localhost/"
+    "start": "node ./server",
+    "build": "webpack --config webpack.prod.js",
+    "ui-dev": "webpack-dev-server --config webpack.dev.js",
+    "server-dev": "nodemon -e ts,js -w server/**",
+    "server-debug": "node --inspect server",
+    "tsc": "npx tsc --skipLibCheck",
+    "eslint": "npx eslint -c .eslintrc \"src/**\" \"server/**\"",
+    "storybook": "start-storybook"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@storybook/react": "^3.2.15",
-    "babel-eslint": "^8.0.1",
-    "babel-jest": "^21.2.0",
+    "@storybook/react": "^5.3.19",
+    "@types/connect": "^3.4.33",
+    "@types/express": "^4.17.6",
+    "@types/file-saver": "^2.0.1",
+    "@types/js-cookie": "^2.2.6",
+    "@types/moment-timezone": "^0.5.13",
+    "@types/react-collapse": "^5.0.0",
+    "@types/react-dom": "^16.9.8",
+    "@types/react-modal": "^3.10.5",
+    "@types/react-notification-system": "^0.2.39",
+    "@types/react-router-dom": "^5.1.5",
+    "@types/react-tooltip": "^4.2.4",
+    "@types/styled-components": "^5.1.0",
+    "@typescript-eslint/eslint-plugin": "^3.2.0",
     "cypress": "^3.2.0",
     "cypress": "^3.2.0",
-    "enzyme": "^3.9.0",
-    "enzyme-adapter-react-16": "^1.11.2",
-    "eslint": "^4.8.0",
-    "eslint-config-airbnb": "^15.1.0",
-    "eslint-plugin-cypress": "^2.0.1",
-    "eslint-plugin-flowtype": "^2.46.1",
-    "eslint-plugin-import": "^2.7.0",
-    "eslint-plugin-jsx-a11y": "^6.0.2",
-    "eslint-plugin-react": "7.10.0",
-    "flow-bin": "0.78.0",
-    "flow-typed": "2.5.1",
-    "jest": "22",
-    "react-test-renderer": "^16.0.0",
-    "sinon": "^4.1.2",
-    "webpack-dev-middleware": "^1.12.0",
-    "webpack-hot-middleware": "^2.19.1"
+    "eslint": "^6.8.0",
+    "eslint-config-airbnb-typescript": "^6.3.1",
+    "eslint-plugin-import": "^2.19.1",
+    "eslint-plugin-jsx-a11y": "^6.2.3",
+    "eslint-plugin-react": "^7.17.0",
+    "nodemon": "^2.0.4"
   },
   },
   "dependencies": {
   "dependencies": {
+    "@babel/core": "^7.7.2",
+    "@babel/plugin-proposal-class-properties": "^7.7.0",
+    "@babel/plugin-proposal-decorators": "^7.7.0",
+    "@babel/plugin-proposal-object-rest-spread": "^7.6.2",
+    "@babel/plugin-proposal-optional-chaining": "^7.8.3",
+    "@babel/preset-env": "^7.7.1",
+    "@babel/preset-react": "^7.7.0",
+    "@babel/preset-typescript": "^7.7.2",
+    "@babel/register": "^7.7.0",
     "@webpack-blocks/webpack2": "^0.4.0",
     "@webpack-blocks/webpack2": "^0.4.0",
-    "ansi-to-html": "^0.6.12",
+    "ansi-to-html": "^0.6.14",
     "autobind-decorator": "^2.1.0",
     "autobind-decorator": "^2.1.0",
-    "axios": "^0.18.0",
-    "babel-core": "^6.26.0",
-    "babel-loader": "^7.1.2",
-    "babel-plugin-styled-components": "^1.2.1",
-    "babel-plugin-transform-decorators-legacy": "^1.3.4",
-    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
-    "babel-plugin-transform-react-remove-prop-types": "^0.4.9",
-    "babel-polyfill": "^6.26.0",
-    "babel-preset-env": "^1.6.0",
-    "babel-preset-flow": "^6.23.0",
-    "babel-preset-react": "^6.24.1",
-    "babel-preset-stage-1": "^6.24.1",
-    "babel-register": "^6.26.0",
+    "axios": "^0.19.2",
+    "babel-loader": "^8.0.6",
+    "babel-plugin-styled-components": "^1.10.6",
     "body-parser": "^1.18.2",
     "body-parser": "^1.18.2",
-    "copyfiles": "^1.2.0",
-    "cross-env": "^5.0.5",
-    "express": "^4.16.1",
-    "file-loader": "^1.1.5",
+    "clean-webpack-plugin": "^3.0.0",
+    "copy-webpack-plugin": "^6.0.2",
+    "dotenv": "^8.2.0",
+    "express": "^4.17.1",
+    "file-loader": "^4.2.0",
     "file-saver": "^2.0.2",
     "file-saver": "^2.0.2",
     "fs": "^0.0.1-security",
     "fs": "^0.0.1-security",
-    "history": "^4.7.2",
-    "html-webpack-plugin": "^2.30.1",
-    "js-cookie": "^2.1.4",
-    "jszip": "^3.2.2",
+    "html-webpack-plugin": "^3.2.0",
+    "js-cookie": "^2.2.1",
+    "jszip": "^3.5.0",
     "lodash": "^4.17.13",
     "lodash": "^4.17.13",
-    "mobx": "^3.6.1",
-    "mobx-react": "^4.4.2",
+    "mobx": "^5.15.4",
+    "mobx-react": "^6.2.2",
     "moment": "^2.18.1",
     "moment": "^2.18.1",
     "moment-timezone": "^0.5.21",
     "moment-timezone": "^0.5.21",
     "ms-rest-azure": "^2.4.5",
     "ms-rest-azure": "^2.4.5",
     "path": "^0.12.7",
     "path": "^0.12.7",
-    "raw-loader": "^0.5.1",
-    "react": "^16.0.0",
-    "react-collapse": "^4.0.3",
+    "react": "^16.13.1",
+    "react-collapse": "^5.0.1",
     "react-datetime": "^2.10.3",
     "react-datetime": "^2.10.3",
-    "react-dom": "^16.0.0",
-    "react-hot-loader": "next",
-    "react-modal": "^3.0.4",
+    "react-dom": "^16.13.1",
+    "react-hot-loader": "^4.12.17",
+    "react-modal": "^3.11.2",
     "react-motion": "^0.5.2",
     "react-motion": "^0.5.2",
-    "react-notification-system": "^0.2.15",
-    "react-router-dom": "^4.2.2",
-    "react-tooltip": "^3.10.0",
+    "react-notification-system": "^0.4.0",
+    "react-router-dom": "^5.1.2",
+    "react-tooltip": "^4.2.7",
     "request": "^2.88.0",
     "request": "^2.88.0",
     "require-without-cache": "^0.0.6",
     "require-without-cache": "^0.0.6",
     "rimraf": "^2.6.2",
     "rimraf": "^2.6.2",
-    "styled-components": "2.2.0",
-    "styled-tools": "^0.2.2",
-    "url-loader": "^0.6.2",
-    "webpack": "^3.6.0",
-    "webpack-blocks-happypack": "^0.1.3",
-    "webpack-blocks-split-vendor": "^0.2.1"
-  },
-  "resolutions": {
-    "acorn": "^5.7.4",
-    "https-proxy-agent": "^2.2.3",
-    "mem": "^4.0.0",
-    "minimist": "^0.2.1"
+    "styled-components": "^4.4.1",
+    "typescript": "^3.9.5",
+    "url-loader": "^4.1.0",
+    "webpack": "^4.41.2",
+    "webpack-cli": "^3.3.10",
+    "webpack-dev-server": "^3.9.0",
+    "webpack-merge": "^4.2.2"
   }
   }
 }
 }

+ 0 - 45
private/storybook/Decorator.jsx

@@ -1,45 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-// @flow
-
-import 'babel-polyfill'
-
-import * as React from 'react'
-import styled, { injectGlobal } from 'styled-components'
-import Palette from '../../src/components/styleUtils/Palette'
-import StyleProps from '../../src/components/styleUtils/StyleProps'
-
-const Wrapper = styled.div`
-  display: inline-block;
-  background: ${Palette.grayscale[7]};
-  padding: 32px;
-`
-injectGlobal`
-  body {
-    color: ${Palette.black};
-    font-family: Rubik;
-    font-size: 14px;
-    font-weight: ${StyleProps.fontWeights.regular};
-    -webkit-font-smoothing: antialiased;
-    -moz-osx-font-smoothing: grayscale;
-  }
-`
-
-type Props = {
-  children: React.Node,
-}
-const Decorator = (props: Props) => <Wrapper>{props.children}</Wrapper>
-
-export default Decorator

+ 0 - 18
private/storybook/config.js

@@ -1,18 +0,0 @@
-import React from 'react'
-import { configure, addDecorator } from '@storybook/react'
-import { BrowserRouter } from 'react-router-dom'
-import Decorator from './Decorator'
-
-const req = require.context('components', true, /story.jsx$/i)
-
-function loadStories() {
-  req.keys().forEach(filename => req(filename))
-}
-
-addDecorator(story => {
-  return React.createElement(BrowserRouter, null,
-    React.createElement(Decorator, null, story())
-  )
-})
-
-configure(loadStories, module)

+ 0 - 12
private/storybook/webpack.config.js

@@ -1,12 +0,0 @@
-const baseConfig = require('../../webpack.config')
-
-module.exports = storybookBaseConfig =>
-  Object.assign({}, storybookBaseConfig, {
-    resolve: Object.assign({}, storybookBaseConfig.resolve, {
-      modules: baseConfig.resolve.modules,
-    }),
-    module: Object.assign({}, storybookBaseConfig.module, {
-      rules: storybookBaseConfig.module.rules.concat(baseConfig.module.rules.slice(1)),
-    }),
-    node: baseConfig.node,
-  })

+ 1 - 2
public/index.html

@@ -10,11 +10,10 @@
   <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
   <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
   <link rel="manifest" href="/manifest.json">
   <link rel="manifest" href="/manifest.json">
   <meta name="theme-color" content="#ffffff">
   <meta name="theme-color" content="#ffffff">
-  <script src="env.js"></script>
 </head>
 </head>
 
 
 <body>
 <body>
   <main id="app"></main>
   <main id="app"></main>
 </body>
 </body>
 
 
-</html>
+</html>

+ 13 - 14
server/api/ConfigApi.js → server/api/ConfigApi.ts

@@ -12,42 +12,41 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
+import express from 'express'
 import path from 'path'
 import path from 'path'
 import fs from 'fs'
 import fs from 'fs'
 import requireWithoutCache from 'require-without-cache'
 import requireWithoutCache from 'require-without-cache'
 
 
-import type { Config, Services } from '../../src/types/Config'
+import type { Services } from '../../src/@types/Config'
 
 
 const getBaseUrl = () => {
 const getBaseUrl = () => {
-  let BASE_URL = process.env.CORIOLIS_URL || ''
+  const BASE_URL = process.env.CORIOLIS_URL || ''
   return BASE_URL.trim().replace(/\/$/, '')
   return BASE_URL.trim().replace(/\/$/, '')
 }
 }
 
 
 const modServicesUrls = (configServices: Services, servicesMod?: Services): Services => {
 const modServicesUrls = (configServices: Services, servicesMod?: Services): Services => {
-  let services: Services = { ...configServices }
+  const services: any = { ...configServices }
+  const localServicesMod: any = servicesMod
   Object.keys(services).forEach(key => {
   Object.keys(services).forEach(key => {
-    services[key] = ((servicesMod && servicesMod[key]) ? servicesMod[key] : services[key])
+    services[key] = ((servicesMod && localServicesMod[key]) ? localServicesMod[key] : services[key])
       .replace('{BASE_URL}', getBaseUrl())
       .replace('{BASE_URL}', getBaseUrl())
   })
   })
   return services
   return services
 }
 }
 
 
-export default (router: express$Router) => {
-  // $FlowIgnore
-  router.get('/config', (req, res) => {
-    let configPath = path.join(__dirname, '../../config.js')
-    let config: Config = requireWithoutCache(configPath, require).config
-    let modJsonPath: ?string = process.env.MOD_JSON
+export default (router: express.Router) => {
+  router.get('/config', (_, res) => {
+    const configPath = path.join(__dirname, '../../config.ts')
+    const config: any = requireWithoutCache(configPath, require).config
+    const modJsonPath: string | null | undefined = process.env.MOD_JSON
     if (!modJsonPath) {
     if (!modJsonPath) {
       config.servicesUrls = modServicesUrls(config.servicesUrls)
       config.servicesUrls = modServicesUrls(config.servicesUrls)
       res.send(config)
       res.send(config)
       return
       return
     }
     }
     try {
     try {
-      let jsonContent = fs.readFileSync(modJsonPath)
-      let configMod = JSON.parse(jsonContent).config
+      const jsonContent: any = fs.readFileSync(modJsonPath)
+      const configMod = JSON.parse(jsonContent).config
       Object.keys(configMod).forEach(key => {
       Object.keys(configMod).forEach(key => {
         if (key !== 'servicesUrls') {
         if (key !== 'servicesUrls') {
           config[key] = configMod[key]
           config[key] = configMod[key]

+ 21 - 22
server/api/LogosApi.js → server/api/LogosApi.ts

@@ -12,14 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
+import express from 'express'
 import path from 'path'
 import path from 'path'
 import fs from 'fs'
 import fs from 'fs'
 
 
 const getModJsonProviders = (jsonPath: string) => {
 const getModJsonProviders = (jsonPath: string) => {
-  let jsonContent = fs.readFileSync(jsonPath)
-  let json = JSON.parse(jsonContent)
+  const jsonContent: any = fs.readFileSync(jsonPath)
+  const json = JSON.parse(jsonContent)
   if (!json.providers) {
   if (!json.providers) {
     throw new Error()
     throw new Error()
   }
   }
@@ -29,33 +28,33 @@ const getModJsonProviders = (jsonPath: string) => {
 const getOptimalLogoHeightKey = (
 const getOptimalLogoHeightKey = (
   availableHeightKeys: string[],
   availableHeightKeys: string[],
   requestedHeight: number,
   requestedHeight: number,
-  style: ?string
+  style: string | null,
 ): string => {
 ): string => {
   let heightKeys = availableHeightKeys
   let heightKeys = availableHeightKeys
   if (style) {
   if (style) {
-    let styledKeys = heightKeys.filter(k => style ? k.indexOf(style) > -1 : false)
+    const styledKeys = heightKeys.filter(k => (style ? k.indexOf(style) > -1 : false))
     if (styledKeys.length) {
     if (styledKeys.length) {
       heightKeys = styledKeys
       heightKeys = styledKeys
     }
     }
   }
   }
 
 
-  let optimal = heightKeys.reduce((prev, curr) => {
-    let prevHeight = /d+/.exec(prev)
-    let currHeight = /d+/.exec(curr)
+  const optimal = heightKeys.reduce((prev, curr) => {
+    let prevHeight: any = /d+/.exec(prev)
+    let currHeight: any = /d+/.exec(curr)
     prevHeight = prevHeight ? Number(prevHeight[0]) : 0
     prevHeight = prevHeight ? Number(prevHeight[0]) : 0
     currHeight = currHeight ? Number(currHeight[0]) : 0
     currHeight = currHeight ? Number(currHeight[0]) : 0
-    return Math.abs(currHeight - requestedHeight) < Math.abs(prevHeight - requestedHeight) ? curr : prev
+    return Math.abs(currHeight - requestedHeight)
+      < Math.abs(prevHeight - requestedHeight) ? curr : prev
   })
   })
   return optimal
   return optimal
 }
 }
 
 
-export default (router: express$Router) => {
-  // $FlowIgnore
+export default (router: express.Router) => {
   router.get('/logos/:provider/:size/:style?', (req, res) => {
   router.get('/logos/:provider/:size/:style?', (req, res) => {
     const SIZES = [32, 42, 64, 128]
     const SIZES = [32, 42, 64, 128]
     const STYLES = ['white', 'disabled']
     const STYLES = ['white', 'disabled']
-    let { provider, size, style } = req.params
-    size = Number(size)
+    const { provider, style } = req.params
+    const size = Number(req.params.size)
 
 
     if (SIZES.indexOf(size) === -1) {
     if (SIZES.indexOf(size) === -1) {
       res.status(400).json({ error: { message: `Valid sizes are: ${SIZES.join(', ')}` } })
       res.status(400).json({ error: { message: `Valid sizes are: ${SIZES.join(', ')}` } })
@@ -65,37 +64,37 @@ export default (router: express$Router) => {
       res.status(400).json({ error: { message: `Valid styles are: ${STYLES.join(', ')}` } })
       res.status(400).json({ error: { message: `Valid styles are: ${STYLES.join(', ')}` } })
       return
       return
     }
     }
-    let logoBase = path.join(__dirname, '/resources/providerLogos')
+    const logoBase = path.join(__dirname, '/resources/providerLogos')
     let logoPath = `${logoBase}/${provider}-${size}`
     let logoPath = `${logoBase}/${provider}-${size}`
     logoPath = style ? `${logoPath}-${style}.svg` : `${logoPath}.svg`
     logoPath = style ? `${logoPath}-${style}.svg` : `${logoPath}.svg`
 
 
-    let modJsonPath: ?string = process.env.MOD_JSON
+    const modJsonPath: string | null | undefined = process.env.MOD_JSON
     if (!modJsonPath) {
     if (!modJsonPath) {
       res.sendFile(logoPath)
       res.sendFile(logoPath)
       return
       return
     }
     }
 
 
     try {
     try {
-      let providersJson = getModJsonProviders(modJsonPath)
-      let providerJson = providersJson[provider]
+      const providersJson = getModJsonProviders(modJsonPath)
+      const providerJson = providersJson[provider]
       if (!providerJson) {
       if (!providerJson) {
         res.sendFile(logoPath)
         res.sendFile(logoPath)
         return
         return
       }
       }
-      let providerLogosJson = providerJson.logos
+      const providerLogosJson = providerJson.logos
       if (!providerLogosJson) {
       if (!providerLogosJson) {
         console.log(`No logos specified in MOD_JSON file for '${provider}' provider`)
         console.log(`No logos specified in MOD_JSON file for '${provider}' provider`)
         res.sendFile(logoPath)
         res.sendFile(logoPath)
         return
         return
       }
       }
-      let providerLogosKeys = Object.keys(providerLogosJson)
+      const providerLogosKeys = Object.keys(providerLogosJson)
       if (!providerLogosKeys.length) {
       if (!providerLogosKeys.length) {
         console.log(`No logo heights specified in MOD_JSON file for '${provider}' provider`)
         console.log(`No logo heights specified in MOD_JSON file for '${provider}' provider`)
         res.sendFile(logoPath)
         res.sendFile(logoPath)
         return
         return
       }
       }
-      let optimalHeightKey = getOptimalLogoHeightKey(providerLogosKeys, size, style)
-      let modLogoPath = providerLogosJson[optimalHeightKey].path
+      const optimalHeightKey = getOptimalLogoHeightKey(providerLogosKeys, size, style)
+      const modLogoPath = providerLogosJson[optimalHeightKey].path
       if (!modLogoPath) {
       if (!modLogoPath) {
         console.log(`No logo path specified in MOD_JSON file for '${provider}' provider`)
         console.log(`No logo path specified in MOD_JSON file for '${provider}' provider`)
         res.sendFile(logoPath)
         res.sendFile(logoPath)

+ 1 - 4
server/api/router.js → server/api/router.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import express from 'express'
 import express from 'express'
 import bodyParser from 'body-parser'
 import bodyParser from 'body-parser'
 
 
@@ -26,8 +24,7 @@ const router = express.Router()
 
 
 router.use(bodyParser.json())
 router.use(bodyParser.json())
 
 
-// $FlowIgnore
-router.get('/version', (req, res) => {
+router.get('/version', (_, res) => {
   res.json({ version: packageJson.version })
   res.json({ version: packageJson.version })
 })
 })
 
 

+ 15 - 13
server/proxy.js → server/azureProxy.ts

@@ -12,23 +12,23 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
+import express from 'express'
+
 import MsRest from 'ms-rest-azure'
 import MsRest from 'ms-rest-azure'
 import bodyParser from 'body-parser'
 import bodyParser from 'body-parser'
 import axios from 'axios'
 import axios from 'axios'
 
 
 const forwardHeaders = ['authorization']
 const forwardHeaders = ['authorization']
 
 
-let buildError = (message) => {
-  return {
-    error: { message: `Proxy - ${message}` },
-  }
-}
+const buildError = (message: any) => ({
+  error: { message: `Proxy - ${message}` },
+})
 
 
-module.exports = app => {
+export default (app: express.Application) => {
   const jsonParser = bodyParser.json()
   const jsonParser = bodyParser.json()
 
 
   app.post('/azure-login', jsonParser, (req, res) => {
   app.post('/azure-login', jsonParser, (req, res) => {
-    let handleResponse = (err, credentials) => {
+    const handleResponse = (err: any, credentials: any) => {
       if (err) {
       if (err) {
         console.log(err)
         console.log(err)
         res.status(401).send(buildError('Azure API authentication error'))
         res.status(401).send(buildError('Azure API authentication error'))
@@ -36,13 +36,15 @@ module.exports = app => {
         res.send(credentials)
         res.send(credentials)
       }
       }
     }
     }
-    let connInfo = req.body
-    let userCred = connInfo.user_credentials
-    let servicePrin = connInfo.service_principal_credentials
+    const connInfo = req.body
+    const userCred = connInfo.user_credentials
+    const servicePrin = connInfo.service_principal_credentials
     if (userCred && userCred.username && userCred.password) {
     if (userCred && userCred.username && userCred.password) {
       MsRest.loginWithUsernamePassword(userCred.username, userCred.password, handleResponse)
       MsRest.loginWithUsernamePassword(userCred.username, userCred.password, handleResponse)
     } else if (servicePrin && servicePrin.client_id && servicePrin.client_secret) {
     } else if (servicePrin && servicePrin.client_id && servicePrin.client_secret) {
-      MsRest.loginWithServicePrincipalSecret(servicePrin.client_id, servicePrin.client_secret, connInfo.tenant, handleResponse)
+      MsRest.loginWithServicePrincipalSecret(
+        servicePrin.client_id, servicePrin.client_secret, connInfo.tenant, handleResponse,
+      )
     } else {
     } else {
       res.status(401).send(buildError('Azure API authentication error'))
       res.status(401).send(buildError('Azure API authentication error'))
     }
     }
@@ -50,8 +52,8 @@ module.exports = app => {
 
 
   app.get('/proxy/*', (req, res) => {
   app.get('/proxy/*', (req, res) => {
     process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
     process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
-    let url = Buffer.from(req.url.substr('/proxy/'.length), 'base64').toString()
-    let headers = {}
+    const url = Buffer.from(req.url.substr('/proxy/'.length), 'base64').toString()
+    const headers: any = {}
     forwardHeaders.forEach(headerName => {
     forwardHeaders.forEach(headerName => {
       if (req.headers[headerName] != null) {
       if (req.headers[headerName] != null) {
         headers[headerName] = req.headers[headerName]
         headers[headerName] = req.headers[headerName]

+ 0 - 38
server/dev.js

@@ -1,38 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import webpack from 'webpack'
-import webpackConfig from '../webpack.config'
-
-module.exports = (app, PORT) => {
-  let isFirstTimeSuccess = false
-  const compiler = webpack(webpackConfig)
-
-  app.use(require('webpack-dev-middleware')(compiler, {
-    noInfo: false,
-    publicPath: webpackConfig.output.publicPath,
-    stats: 'errors-only',
-    log: text => {
-      let isSuccessfull = text.indexOf('webpack: Compiled successfully.') > -1
-      if (!isFirstTimeSuccess && isSuccessfull) {
-        isFirstTimeSuccess = true
-        console.log(`\x1b[36mServer is available at http://localhost:${PORT}\x1b[0m`) // eslint-disable-line no-console
-      }
-    },
-  }))
-
-  app.use(require('webpack-hot-middleware')(compiler, {
-    log: console.log, path: '/__webpack_hmr', heartbeat: 10 * 1000, // eslint-disable-line no-console
-  }))
-}

+ 3 - 0
server/index.js

@@ -0,0 +1,3 @@
+require('@babel/register')({ extensions: ['.ts', '.js'] })
+require('dotenv').config()
+require('./start')

+ 0 - 66
server/main.js

@@ -1,66 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-// @flow
-
-import express from 'express'
-import fs from 'fs'
-import path from 'path'
-
-import router from './api/router'
-
-// Create our app
-const app = express()
-
-const PORT = process.env.PORT || 3000
-const isDev = process.argv.find(a => a === '--dev')
-
-// Write file to disk with process env variables, so that the client code can read
-if (!fs.existsSync('./dist')) {
-  fs.mkdirSync('./dist')
-}
-fs.writeFileSync('./dist/env.js', `window.env = {
-  ENV: '${isDev ? 'development' : 'production'}',
-}
-`)
-
-if (isDev) {
-  require('./dev')(app, PORT)
-}
-
-app.use(express.static('dist'))
-
-require('./proxy')(app)
-
-app.use('/api', router)
-
-if (isDev) {
-  // $FlowIgnore
-  app.use((req, res) => {
-    res.redirect(`${req.baseUrl}/#${req.url}`)
-  })
-} else {
-  // $FlowIgnore
-  app.get('*/env.js', (req, res) => {
-    res.sendFile(path.resolve(__dirname, '../dist', 'env.js'))
-  })
-  // $FlowIgnore
-  app.get('*', (req, res) => {
-    res.sendFile(path.resolve(__dirname, '../dist', 'index.html'))
-  })
-}
-
-app.listen(PORT, () => {
-  console.log(`Express server is up on port ${PORT}`)
-})

+ 39 - 0
server/main.ts

@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import express from 'express'
+import path from 'path'
+
+import router from './api/router'
+import azureProxy from './azureProxy'
+
+export default () => {
+  const app = express()
+
+  const PORT = process.env.PORT || 3000
+
+  app.use(express.static('dist'))
+
+  azureProxy(app)
+
+  app.use('/api', router)
+
+  app.get('*', (_, res) => {
+    res.sendFile(path.resolve(__dirname, '../dist', 'index.html'))
+  })
+
+  app.listen(PORT, () => {
+    console.log(`Express server is up on port ${PORT}`)
+  })
+}

+ 5 - 0
server/start.js

@@ -0,0 +1,5 @@
+// This file is required because we cannot directly import a .ts file from index.js
+
+import main from './main'
+
+main()

+ 6 - 11
src/types/Assessment.js → src/@types/Assessment.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Endpoint } from './Endpoint'
 import type { Endpoint } from './Endpoint'
 import type { Instance } from './Instance'
 import type { Instance } from './Instance'
 import type { NetworkMap } from './Network'
 import type { NetworkMap } from './Network'
@@ -23,7 +21,7 @@ export type VmSize = {
   size?: string,
   size?: string,
 }
 }
 
 
-export type Location = {
+export type AzureLocation = {
   id: string,
   id: string,
   name: string,
   name: string,
 }
 }
@@ -38,7 +36,7 @@ export type VmItem = {
   properties: {
   properties: {
     recommendedSize: string,
     recommendedSize: string,
     disks: {
     disks: {
-      [string]: {
+      [diskName: string]: {
         recommendedDiskType: string,
         recommendedDiskType: string,
       },
       },
     },
     },
@@ -57,9 +55,6 @@ export type Assessment = {
   groupName: string,
   groupName: string,
   assessmentName: string,
   assessmentName: string,
   location: string,
   location: string,
-  properties: {
-    azureLocation: string,
-  },
   project: {
   project: {
     name: string,
     name: string,
   },
   },
@@ -70,14 +65,14 @@ export type Assessment = {
     azureLocation: string,
     azureLocation: string,
     numberOfMachines: string,
     numberOfMachines: string,
   },
   },
-  connectionInfo: { subscription_id: string } & $PropertyType<Endpoint, 'connection_info'>,
+  connectionInfo: { subscription_id: string } & Endpoint['connection_info'],
 }
 }
 
 
 export type MigrationInfo = {
 export type MigrationInfo = {
-  source: ?Endpoint,
+  source: Endpoint | null,
   target: Endpoint,
   target: Endpoint,
   selectedInstances: Instance[],
   selectedInstances: Instance[],
-  fieldValues: { [string]: any },
+  fieldValues: { [fieldValue: string]: any },
   networks: NetworkMap[],
   networks: NetworkMap[],
-  vmSizes: { [string]: VmSize },
+  vmSizes: { [vmSize: string]: VmSize },
 }
 }

+ 0 - 2
src/types/Cache.js → src/@types/Cache.ts

@@ -1,5 +1,3 @@
-// @flow
-
 export type Cache = {
 export type Cache = {
   [key: string]: {
   [key: string]: {
     data: any,
     data: any,

+ 1 - 3
src/types/Config.js → src/@types/Config.ts

@@ -1,5 +1,3 @@
-// @flow
-
 type Type = 'source' | 'destination'
 type Type = 'source' | 'destination'
 
 
 type ExtraOption = {
 type ExtraOption = {
@@ -28,7 +26,7 @@ export type Config = {
   showOpenstackCurrentUserSwitch: boolean,
   showOpenstackCurrentUserSwitch: boolean,
   useBarbicanSecrets: boolean,
   useBarbicanSecrets: boolean,
   requestPollTimeout: number,
   requestPollTimeout: number,
-  instancesListBackgroundLoading: { default: number, [string]: number },
+  instancesListBackgroundLoading: { default: number, [prop: string]: number },
   extraOptionsApiCalls: ExtraOption[],
   extraOptionsApiCalls: ExtraOption[],
   providerSortPriority: { [providerName: string]: number },
   providerSortPriority: { [providerName: string]: number },
   hiddenUsers: string[],
   hiddenUsers: string[],

+ 5 - 6
src/types/Endpoint.js → src/@types/Endpoint.ts

@@ -12,9 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Disk } from './Instance'
 import type { Disk } from './Instance'
+import type { ProviderTypes } from './Providers'
 
 
 export type Validation = {
 export type Validation = {
   valid: boolean,
   valid: boolean,
@@ -25,13 +24,14 @@ export type Endpoint = {
   id: string,
   id: string,
   name: string,
   name: string,
   description: string,
   description: string,
-  type: string,
+  type: ProviderTypes,
   created_at: Date,
   created_at: Date,
   connection_info: {
   connection_info: {
     secret_ref?: string,
     secret_ref?: string,
     host?: string,
     host?: string,
-    [string]: mixed
+    [prop: string]: any
   },
   },
+  [prop: string]: any
 }
 }
 
 
 export type MultiValidationItem = {
 export type MultiValidationItem = {
@@ -42,8 +42,7 @@ export type MultiValidationItem = {
 
 
 export type OptionValues = {
 export type OptionValues = {
   name: string,
   name: string,
-  // $FlowIssue
-  values: string[] | { name: string, id: string, [string]: mixed }[],
+  values: string[] | { name: string, id: string, [prop: string]: any }[],
   config_default: string | { name: string, id: string },
   config_default: string | { name: string, id: string },
 }
 }
 
 

+ 0 - 2
src/types/Execution.js → src/@types/Execution.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Task } from './Task'
 import type { Task } from './Task'
 
 
 export type Execution = {
 export type Execution = {

+ 27 - 15
src/types/Field.js → src/@types/Field.ts

@@ -12,18 +12,23 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 import LabelDictionary from '../utils/LabelDictionary'
 import LabelDictionary from '../utils/LabelDictionary'
+import { ProviderTypes } from './Providers'
+
+type Separator = { separator: boolean }
+type EnumItemObject = { label?: string, value?: any, name?: string, id?: string | null }
+export const isEnumSeparator = (e: any): e is Separator => (typeof e !== 'string' && e.separator === true)
 
 
+export type EnumItem = (
+  string | EnumItemObject | Separator
+)
 export type Field = {
 export type Field = {
   name: string,
   name: string,
   type?: string,
   type?: string,
   value?: any,
   value?: any,
   label?: string,
   label?: string,
-  // $FlowIssue
-  enum?: string[] | { id: string, name: string, [string]: mixed }[],
+  enum?: EnumItem[],
   default?: any,
   default?: any,
   password?: boolean,
   password?: boolean,
   nullableBoolean?: boolean,
   nullableBoolean?: boolean,
@@ -45,8 +50,13 @@ export type Field = {
 const migrationImageOsTypes = ['windows', 'linux']
 const migrationImageOsTypes = ['windows', 'linux']
 
 
 class FieldHelper {
 class FieldHelper {
-  getValueAlias(name: string, value: any, fields: Field[], targetProvider: ?string): string {
-    let plugin = targetProvider && (OptionsSchemaPlugin[targetProvider] || OptionsSchemaPlugin.default)
+  getValueAlias(
+    name: string,
+    value: any,
+    fields: Field[],
+    targetProvider: ProviderTypes | null | undefined,
+  ): string {
+    const plugin = targetProvider && OptionsSchemaPlugin.for(targetProvider)
 
 
     if (value === true) {
     if (value === true) {
       return 'Yes'
       return 'Yes'
@@ -54,7 +64,7 @@ class FieldHelper {
     if (value === false) {
     if (value === false) {
       return 'No'
       return 'No'
     }
     }
-    let findField = (f: Field[]) => f.find(f1 => f1.name === name)
+    const findField = (f: Field[]) => f.find(f1 => f1.name === name)
     let field = findField(fields)
     let field = findField(fields)
     if (!field) {
     if (!field) {
       fields.forEach(f => {
       fields.forEach(f => {
@@ -74,10 +84,10 @@ class FieldHelper {
         }
         }
       })
       })
     }
     }
-    let findInEnum = (v: any) => {
+    const findInEnum = (v: any) => {
       let valueName = v
       let valueName = v
       if (field && field.enum) {
       if (field && field.enum) {
-        let enumObject = field.enum.find(e => e.id ? e.id === v : false)
+        const enumObject: any = field.enum.find((e: any) => (e.id ? e.id === v : false))
         if (enumObject && enumObject.name) {
         if (enumObject && enumObject.name) {
           valueName = enumObject.name
           valueName = enumObject.name
         } else if (field && LabelDictionary.enumFields.find(f => field && f === field.name)) {
         } else if (field && LabelDictionary.enumFields.find(f => field && f === field.name)) {
@@ -87,23 +97,25 @@ class FieldHelper {
       return valueName
       return valueName
     }
     }
     if (value.join) {
     if (value.join) {
-      return value.map(v => findInEnum(v)).join(', ')
+      return value.map((v: any) => findInEnum(v)).join(', ')
     }
     }
 
 
-    let isImageMapField = migrationImageOsTypes.find(os => `${os}_os_image` === name)
+    const isImageMapField = migrationImageOsTypes.find(os => `${os}_os_image` === name)
     if (isImageMapField) {
     if (isImageMapField) {
-      let migrImageField = plugin && fields.find(f => f.name === plugin.migrationImageMapFieldName)
+      const migrImageField = plugin && fields
+        .find(f => f.name === plugin.migrationImageMapFieldName)
       if (migrImageField && migrImageField.properties) {
       if (migrImageField && migrImageField.properties) {
-        let imageField = migrImageField.properties.find(p => p.name === name)
+        const imageField = migrImageField.properties.find(p => p.name === name)
         if (imageField && imageField.enum) {
         if (imageField && imageField.enum) {
-          let imageFieldValueObject = imageField.enum.find(e => e.id ? e.id === value : false)
+          const imageFieldValueObject: any = imageField.enum
+            .find((e: any) => (e.id ? e.id === value : false))
           if (imageFieldValueObject) {
           if (imageFieldValueObject) {
             return imageFieldValueObject.name
             return imageFieldValueObject.name
           }
           }
         }
         }
       }
       }
     }
     }
-    // $FlowIssue
+
     return findInEnum(value)
     return findInEnum(value)
   }
   }
 }
 }

+ 3 - 5
src/types/Instance.js → src/@types/Instance.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type Nic = {
 export type Nic = {
   id: string,
   id: string,
   network_name: string,
   network_name: string,
@@ -39,7 +37,7 @@ export type Instance = {
   id: string,
   id: string,
   name: string,
   name: string,
   flavor_name: string,
   flavor_name: string,
-  instance_name?: ?string,
+  instance_name?: string | null,
   num_cpu: number,
   num_cpu: number,
   memory_mb: number,
   memory_mb: number,
   os_type: string,
   os_type: string,
@@ -50,8 +48,8 @@ export type Instance = {
 }
 }
 
 
 export type InstanceScript = {
 export type InstanceScript = {
-  global?: ?string,
-  instanceName?: ?string,
+  global?: string | null,
+  instanceName?: string | null,
   scriptContent: string,
   scriptContent: string,
   fileName: string,
   fileName: string,
 }
 }

+ 0 - 2
src/types/Licence.js → src/@types/Licence.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type Licence = {
 export type Licence = {
   currentPeriodStart: Date,
   currentPeriodStart: Date,
   currentPeriodEnd: Date,
   currentPeriodEnd: Date,

+ 0 - 2
src/types/Log.js → src/@types/Log.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type Log = {
 export type Log = {
   log_name: string,
   log_name: string,
 }
 }

+ 28 - 24
src/types/MainItem.js → src/@types/MainItem.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Execution } from './Execution'
 import type { Execution } from './Execution'
 import type { Task } from './Task'
 import type { Task } from './Task'
 import type { Instance } from './Instance'
 import type { Instance } from './Instance'
@@ -36,7 +34,27 @@ export type UpdateData = {
   network: NetworkMap[],
   network: NetworkMap[],
   storage: StorageMap[],
   storage: StorageMap[],
 }
 }
-
+type NetworkMapSecurityGroups = { id: string, security_groups?: string[] }
+type NetworkMapSourceDest = {
+  [prop: string]: {
+    source_network: string,
+    destination_network: string,
+  }
+}
+export const isNetworkMapSecurityGroups = (n: any): n is NetworkMapSecurityGroups => (typeof n !== 'string' && n && n.security_groups)
+export const isNetworkMapSourceDest = (n: any): n is NetworkMapSourceDest => (typeof n !== 'string' && (!n || !n.security_groups))
+export type TransferNetworkMap = NetworkMapSourceDest | string | NetworkMapSecurityGroups
+export type StorageMapping = {
+  backend_mappings: {
+    destination: string,
+    source: string,
+  }[],
+  default: string | null,
+  disk_mappings: {
+    destination: string,
+    disk_id: string,
+  }[] | null,
+}
 export type MainItem = {
 export type MainItem = {
   id: string,
   id: string,
   executions: Execution[],
   executions: Execution[],
@@ -51,26 +69,12 @@ export type MainItem = {
   destination_endpoint_id: string,
   destination_endpoint_id: string,
   instances: string[],
   instances: string[],
   type: 'replica' | 'migration',
   type: 'replica' | 'migration',
-  info: { [string]: MainItemInfo },
-  destination_environment: { [string]: mixed },
-  source_environment: { [string]: mixed },
-  transfer_result: ?{ [string]: Instance },
+  info: { [prop: string]: MainItemInfo },
+  destination_environment: { [prop: string]: any },
+  source_environment: { [prop: string]: any },
+  transfer_result: { [prop: string]: Instance } | null,
   replication_count?: number,
   replication_count?: number,
-  storage_mappings?: ?{
-    backend_mappings: ?{
-      destination: string,
-      source: string,
-    }[],
-    default: ?string,
-    disk_mappings: ?{
-      destination: string,
-      disk_id: string,
-    }[],
-  },
-  network_map?: {
-    [string]: {
-      source_network: string,
-      destination_network: string,
-    } | string | { id: string, security_groups?: string[] }
-  }
+  storage_mappings?: StorageMapping | null,
+  network_map?: TransferNetworkMap
+  [prop: string]: any
 }
 }

+ 1 - 3
src/types/Network.js → src/@types/Network.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Nic } from './Instance'
 import type { Nic } from './Instance'
 
 
 export type SecurityGroup = string | {
 export type SecurityGroup = string | {
@@ -30,5 +28,5 @@ export type Network = {
 export type NetworkMap = {
 export type NetworkMap = {
   sourceNic: Nic,
   sourceNic: Nic,
   targetNetwork: Network,
   targetNetwork: Network,
-  targetSecurityGroups?: ?SecurityGroup[],
+  targetSecurityGroups?: SecurityGroup[] | null,
 }
 }

+ 1 - 3
src/types/NotificationItem.js → src/@types/NotificationItem.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type AlertInfoLevel = 'success' | 'error' | 'info'
 export type AlertInfoLevel = 'success' | 'error' | 'info'
 
 
 export type AlertInfoOptions = {
 export type AlertInfoOptions = {
@@ -24,7 +22,7 @@ export type AlertInfoOptions = {
 }
 }
 
 
 export type AlertInfo = {
 export type AlertInfo = {
-  options?: ?AlertInfoOptions,
+  options?: AlertInfoOptions | null,
   message: string,
   message: string,
   title?: string,
   title?: string,
   id?: string,
   id?: string,

+ 0 - 2
src/types/Project.js → src/@types/Project.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type Project = {
 export type Project = {
   id: string,
   id: string,
   name: string,
   name: string,

+ 7 - 2
server.js → src/@types/Providers.ts

@@ -12,5 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-require('babel-register')
-require('./server/main')
+export type ProviderTypes = 'azure' | 'openstack' | 'opc' | 'oracle_vm' | 'vmware_vsphere' | 'aws' | 'oci' | 'hyper-v' | 'scvmm'
+
+export type Providers = {
+  [provider in ProviderTypes]: {
+    types: number[],
+  }
+}

+ 2 - 2
src/types/Schedule.js → src/@types/Schedule.ts

@@ -12,7 +12,7 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
+export type ScheduleFieldName = 'hour' | 'minute' | 'month' | 'dow' | 'dom'
 
 
 export type ScheduleInfo = {
 export type ScheduleInfo = {
   hour?: number,
   hour?: number,
@@ -24,7 +24,7 @@ export type ScheduleInfo = {
 
 
 export type Schedule = {
 export type Schedule = {
   id?: string,
   id?: string,
-  enabled?: ?boolean,
+  enabled?: boolean | null,
   schedule?: ScheduleInfo,
   schedule?: ScheduleInfo,
   expiration_date?: Date,
   expiration_date?: Date,
   shutdown_instances?: boolean,
   shutdown_instances?: boolean,

+ 3 - 5
src/types/Schema.js → src/@types/Schema.ts

@@ -12,11 +12,9 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 // export type Schema = {
 // export type Schema = {
 //   properties: {
 //   properties: {
-//     [string]: {
+//     [prop: string]: {
 //       name: string,
 //       name: string,
 //     }
 //     }
 //   },
 //   },
@@ -26,7 +24,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
 export type SchemaProperties = {
 export type SchemaProperties = {
   properties: {
   properties: {
-    [string]: {
+    [prop: string]: {
       type: 'array',
       type: 'array',
       items: {
       items: {
         type: string,
         type: string,
@@ -44,7 +42,7 @@ export type SchemaProperties = {
 }
 }
 
 
 export type SchemaDefinitions = {
 export type SchemaDefinitions = {
-  [string]: SchemaProperties,
+  [prop: string]: SchemaProperties,
 }
 }
 
 
 export type Schema = {
 export type Schema = {

+ 0 - 2
src/types/Task.js → src/@types/Task.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 export type ProgressUpdate = {
 export type ProgressUpdate = {
   message: string,
   message: string,
   created_at: Date,
   created_at: Date,

+ 2 - 3
src/types/User.js → src/@types/User.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Project } from './Project'
 import type { Project } from './Project'
 
 
 export type User = {
 export type User = {
@@ -26,9 +24,10 @@ export type User = {
   enabled?: boolean,
   enabled?: boolean,
   project_id?: string,
   project_id?: string,
   domain_id?: string,
   domain_id?: string,
-  isAdmin?: ?boolean,
+  isAdmin?: boolean | null,
   password?: string,
   password?: string,
   extra?: any,
   extra?: any,
+  token?: string
 }
 }
 
 
 export type Credentials = {
 export type Credentials = {

+ 6 - 8
src/types/WizardData.js → src/@types/WizardData.ts

@@ -12,19 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import type { Instance } from './Instance'
 import type { Instance } from './Instance'
 import type { NetworkMap } from './Network'
 import type { NetworkMap } from './Network'
 import type { Endpoint } from './Endpoint'
 import type { Endpoint } from './Endpoint'
 
 
 export type WizardData = {
 export type WizardData = {
-  destOptions?: ?{ [string]: any },
-  sourceOptions?: ?{ [string]: any },
-  selectedInstances?: ?Instance[],
-  networks?: ?NetworkMap[],
-  source?: ?Endpoint,
-  target?: ?Endpoint,
+  destOptions?: { [prop: string]: any } | null,
+  sourceOptions?: { [prop: string]: any } | null,
+  selectedInstances?: Instance[] | null,
+  networks?: NetworkMap[] | null,
+  source?: Endpoint | null,
+  target?: Endpoint | null,
 }
 }
 
 
 export type WizardPage = {
 export type WizardPage = {

+ 17 - 0
src/@types/declarations.d.ts

@@ -0,0 +1,17 @@
+declare module 'imgur'
+
+declare module '*.png'
+declare module '*.jpg'
+declare module '*.svg'
+declare module '*.woff'
+
+declare module 'ansi-to-html'
+
+declare module 'require-without-cache'
+
+interface Window {
+  /**
+   * Needed for KeyboardManager conflict resolution
+   */
+  handlingEnterKey: boolean | undefined
+}

+ 57 - 49
src/components/App.jsx → src/components/App.tsx

@@ -12,11 +12,10 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
+import { hot } from 'react-hot-loader/root'
 import React from 'react'
 import React from 'react'
-import { Switch, Route } from 'react-router-dom'
-import styled, { injectGlobal } from 'styled-components'
+import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
+import styled, { createGlobalStyle } from 'styled-components'
 import { observe } from 'mobx'
 import { observe } from 'mobx'
 
 
 import Fonts from './atoms/Fonts'
 import Fonts from './atoms/Fonts'
@@ -48,8 +47,8 @@ import Palette from './styleUtils/Palette'
 import StyleProps from './styleUtils/StyleProps'
 import StyleProps from './styleUtils/StyleProps'
 import configLoader from '../utils/Config'
 import configLoader from '../utils/Config'
 
 
-injectGlobal`
-  ${Fonts}
+const GlobalStyle = createGlobalStyle`
+ ${Fonts}
   html, body, main {
   html, body, main {
     height: 100%;
     height: 100%;
     display: flex;
     display: flex;
@@ -66,7 +65,8 @@ injectGlobal`
     -moz-osx-font-smoothing: grayscale;
     -moz-osx-font-smoothing: grayscale;
   }
   }
 `
 `
-const Wrapper = styled.div`
+
+const Wrapper = styled.div<any>`
   height: 100%;
   height: 100%;
   min-height: 0;
   min-height: 0;
   display: flex;
   display: flex;
@@ -84,10 +84,13 @@ class App extends React.Component<{}, State> {
   state = {
   state = {
     isConfigReady: false,
     isConfigReady: false,
   }
   }
+
   awaitingRefresh: boolean = false
   awaitingRefresh: boolean = false
 
 
-  async componentWillMount() {
-    observe(userStore, 'loggedUser', () => { this.setState({}) })
+  async componentDidMount() {
+    observe(userStore, 'loggedUser', () => {
+      this.setState({})
+    })
     await configLoader.load()
     await configLoader.load()
     userStore.tokenLogin()
     userStore.tokenLogin()
     this.setState({ isConfigReady: true })
     this.setState({ isConfigReady: true })
@@ -105,20 +108,22 @@ class App extends React.Component<{}, State> {
       subtitle: string,
       subtitle: string,
       showAuthAnimation?: boolean,
       showAuthAnimation?: boolean,
       showDenied?: boolean,
       showDenied?: boolean,
-    }) => (<Route
-      path={options.path}
-      exact={options.exact}
-      render={() => (
-        <MessagePage
-          title={options.title}
-          subtitle={options.subtitle}
-          showAuthAnimation={options.showAuthAnimation}
-          showDenied={options.showDenied}
+    }) => (
+        <Route
+          path={options.path}
+          exact={options.exact}
+          render={() => (
+            <MessagePage
+              title={options.title}
+              subtitle={options.subtitle}
+              showAuthAnimation={options.showAuthAnimation}
+              showDenied={options.showDenied}
+            />
+          )}
         />
         />
-      )}
-    />)
+      )
 
 
-    let renderRoute = (path: string, component: any, exact?: boolean) => {
+    const renderRoute = (path: string, component: any, exact?: boolean) => {
       if (!userStore.loggedUser) {
       if (!userStore.loggedUser) {
         return renderMessagePage({
         return renderMessagePage({
           path,
           path,
@@ -131,12 +136,12 @@ class App extends React.Component<{}, State> {
       return <Route path={path} component={component} exact={exact} />
       return <Route path={path} component={component} exact={exact} />
     }
     }
 
 
-    let renderOptionalRoute = (name: string, component: any, path?: string, exact?: boolean) => {
+    const renderOptionalRoute = (name: string, component: any, path?: string, exact?: boolean) => {
       if (configLoader.config.disabledPages.find(p => p === name)) {
       if (configLoader.config.disabledPages.find(p => p === name)) {
         return null
         return null
       }
       }
-      let actualPath = `${path || `/${name}`}`
-      let requiresAdmin = Boolean(navigationMenu.find(n => n.value === name && n.requiresAdmin))
+      const actualPath = `${path || `/${name}`}`
+      const requiresAdmin = Boolean(navigationMenu.find(n => n.value === name && n.requiresAdmin))
       if (!requiresAdmin) {
       if (!requiresAdmin) {
         return renderRoute(actualPath, component, exact)
         return renderRoute(actualPath, component, exact)
       }
       }
@@ -166,34 +171,37 @@ class App extends React.Component<{}, State> {
 
 
     return (
     return (
       <Wrapper>
       <Wrapper>
-        <Switch>
-          {renderRoute('/', DashboardPage, true)}
-          <Route path="/login" component={LoginPage} />
-          {renderRoute('/dashboard', DashboardPage)}
-          {renderRoute('/replicas', ReplicasPage)}
-          {renderRoute('/replica/:id', ReplicaDetailsPage, true)}
-          {renderRoute('/replica/:page/:id', ReplicaDetailsPage)}
-          {renderRoute('/migrations', MigrationsPage)}
-          {renderRoute('/migration/:id', MigrationDetailsPage, true)}
-          {renderRoute('/migration/:page/:id', MigrationDetailsPage)}
-          {renderRoute('/endpoints', EndpointsPage)}
-          {renderRoute('/endpoint/:id', EndpointDetailsPage)}
-          {renderRoute('/wizard/:type', WizardPage)}
-          {renderOptionalRoute('planning', AssessmentsPage)}
-          {renderOptionalRoute('planning', AssessmentDetailsPage, '/assessment/:info')}
-          {renderOptionalRoute('users', UsersPage)}
-          {renderOptionalRoute('users', UserDetailsPage, '/user/:id', true)}
-          {renderOptionalRoute('projects', ProjectsPage)}
-          {renderOptionalRoute('projects', ProjectDetailsPage, '/project/:id', true)}
-          {renderOptionalRoute('logging', LogsPage)}
-          {renderRoute('/streamlog', LogStreamPage)}
-          <Route component={MessagePage} />
-        </Switch>
+        <GlobalStyle />
+        <Router>
+          <Switch>
+            {renderRoute('/', DashboardPage, true)}
+            <Route path="/login" component={LoginPage} />
+            {renderRoute('/dashboard', DashboardPage)}
+            {renderRoute('/replicas', ReplicasPage)}
+            {renderRoute('/replica/:id', ReplicaDetailsPage, true)}
+            {renderRoute('/replica/:page/:id', ReplicaDetailsPage)}
+            {renderRoute('/migrations', MigrationsPage)}
+            {renderRoute('/migration/:id', MigrationDetailsPage, true)}
+            {renderRoute('/migration/:page/:id', MigrationDetailsPage)}
+            {renderRoute('/endpoints', EndpointsPage)}
+            {renderRoute('/endpoint/:id', EndpointDetailsPage)}
+            {renderRoute('/wizard/:type', WizardPage)}
+            {renderOptionalRoute('planning', AssessmentsPage)}
+            {renderOptionalRoute('planning', AssessmentDetailsPage, '/assessment/:info')}
+            {renderOptionalRoute('users', UsersPage)}
+            {renderOptionalRoute('users', UserDetailsPage, '/user/:id', true)}
+            {renderOptionalRoute('projects', ProjectsPage)}
+            {renderOptionalRoute('projects', ProjectDetailsPage, '/project/:id', true)}
+            {renderOptionalRoute('logging', LogsPage)}
+            {renderRoute('/streamlog', LogStreamPage)}
+            <Route component={MessagePage} />
+          </Switch>
+        </Router>
         <Notifications />
         <Notifications />
         <Tooltip />
         <Tooltip />
-      </Wrapper >
+      </Wrapper>
     )
     )
   }
   }
 }
 }
 
 
-export default App
+export default hot(App)

+ 6 - 7
src/components/atoms/Arrow/Arrow.jsx → src/components/atoms/Arrow/Arrow.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 import styled from 'styled-components'
@@ -21,22 +19,22 @@ import styled from 'styled-components'
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 
 
-import arrowImage from './images/arrow.js'
-import arrowThickImage from './images/arrow-thick.js'
+import arrowImage from './images/arrow'
+import arrowThickImage from './images/arrow-thick'
 
 
-const getOrientation = props => `
+const getOrientation = (props: any) => `
   ${props.orientation === 'left' ? 'transform: rotate(180deg);' : ''}
   ${props.orientation === 'left' ? 'transform: rotate(180deg);' : ''}
   ${props.orientation === 'up' ? 'transform: rotate(-90deg);' : ''}
   ${props.orientation === 'up' ? 'transform: rotate(-90deg);' : ''}
   ${props.orientation === 'down' ? 'transform: rotate(90deg);' : ''}
   ${props.orientation === 'down' ? 'transform: rotate(90deg);' : ''}
 `
 `
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   width: 16px;
   width: 16px;
   height: 16px;
   height: 16px;
   display: flex;
   display: flex;
   justify-content: center;
   justify-content: center;
   align-items: center;
   align-items: center;
-  cursor: ${props => props.useDefaultCursor || props.disabled ? 'default' : 'pointer'};
+  cursor: ${props => (props.useDefaultCursor || props.disabled ? 'default' : 'pointer')};
   opacity: ${props => props.opacity};
   opacity: ${props => props.opacity};
   transition: all ${StyleProps.animations.swift};
   transition: all ${StyleProps.animations.swift};
   ${props => getOrientation(props)}
   ${props => getOrientation(props)}
@@ -65,6 +63,7 @@ class Arrow extends React.Component<Props> {
     color = this.props.disabled ? Palette.grayscale[0] : color
     color = this.props.disabled ? Palette.grayscale[0] : color
     return (
     return (
       <Wrapper
       <Wrapper
+        // eslint-disable-next-line react/jsx-props-no-spreading
         {...this.props}
         {...this.props}
         dangerouslySetInnerHTML={
         dangerouslySetInnerHTML={
           { __html: this.props.thick ? arrowThickImage(color) : arrowImage(color) }
           { __html: this.props.thick ? arrowThickImage(color) : arrowImage(color) }

+ 1 - 1
src/components/atoms/Arrow/images/arrow-thick.js → src/components/atoms/Arrow/images/arrow-thick.ts

@@ -1,4 +1,4 @@
-export default color => `<?xml version="1.0" encoding="UTF-8"?>
+export default (color: string) => `<?xml version="1.0" encoding="UTF-8"?>
 <svg width="8px" height="16px" viewBox="0 0 8 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <svg width="8px" height="16px" viewBox="0 0 8 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <!-- Generator: Sketch 52.3 (67297) - http://www.bohemiancoding.com/sketch -->
     <!-- Generator: Sketch 52.3 (67297) - http://www.bohemiancoding.com/sketch -->
 
 

+ 1 - 1
src/components/atoms/Arrow/images/arrow.js → src/components/atoms/Arrow/images/arrow.ts

@@ -12,7 +12,7 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-const arrow = color => `<?xml version="1.0" encoding="UTF-8"?>
+const arrow = (color: string) => `<?xml version="1.0" encoding="UTF-8"?>
 <svg width="7px" height="12px" viewBox="0 0 7 12"
 <svg width="7px" height="12px" viewBox="0 0 7 12"
 version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
     <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->

+ 2 - 1
src/components/atoms/Arrow/package.json

@@ -2,5 +2,6 @@
   "name": "Arrow",
   "name": "Arrow",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./Arrow.jsx"
+  "main":"./Arrow.tsx"
 }
 }
+

+ 0 - 0
src/components/atoms/Arrow/story.jsx → src/components/atoms/Arrow/story.tsx


+ 28 - 26
src/components/atoms/AutocompleteInput/AutocompleteInput.jsx → src/components/atoms/AutocompleteInput/AutocompleteInput.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import * as React from 'react'
 import * as React from 'react'
 import styled, { css } from 'styled-components'
 import styled, { css } from 'styled-components'
 
 
@@ -23,7 +21,7 @@ import TextInput from '../TextInput'
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 
 
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.width) {
   if (props.width) {
     return props.width - 2
     return props.width - 2
   }
   }
@@ -35,32 +33,32 @@ const getWidth = props => {
   return StyleProps.inputSizes.regular.width - 2
   return StyleProps.inputSizes.regular.width - 2
 }
 }
 
 
-const getBorder = props => {
+const getBorder = (props: any) => {
   if (props.embedded) {
   if (props.embedded) {
     return css`border: none;`
     return css`border: none;`
   }
   }
   return css`border: 1px solid ${props.highlight ? Palette.alert : props.disabled ? Palette.grayscale[0] : Palette.grayscale[3]};`
   return css`border: 1px solid ${props.highlight ? Palette.alert : props.disabled ? Palette.grayscale[0] : Palette.grayscale[3]};`
 }
 }
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   position: relative;
   position: relative;
-  width: ${props => props.embedded ? 'calc(100% + 8px)' : `${getWidth(props)}px`};
-  height: ${props => props.large ? StyleProps.inputSizes.large.height - 2
-    : StyleProps.inputSizes.regular.height - 2}px;
+  width: ${props => (props.embedded ? 'calc(100% + 8px)' : `${getWidth(props)}px`)};
+  height: ${props => (props.large ? StyleProps.inputSizes.large.height - 2
+    : StyleProps.inputSizes.regular.height - 2)}px;
   ${props => getBorder(props)}
   ${props => getBorder(props)}
   border-radius: ${StyleProps.borderRadius};
   border-radius: ${StyleProps.borderRadius};
-  cursor: ${props => props.disabled ? 'default' : 'pointer'};
+  cursor: ${props => (props.disabled ? 'default' : 'pointer')};
   transition: all ${StyleProps.animations.swift};
   transition: all ${StyleProps.animations.swift};
-  background: ${props => props.disabled && !props.embedded ? Palette.grayscale[0] : 'white'};
+  background: ${props => (props.disabled && !props.embedded ? Palette.grayscale[0] : 'white')};
 
 
-  #dropdown-arrow-image {stroke: ${props => props.disabled ? Palette.grayscale[3] : Palette.black};}
-  ${props => props.focus ? css`border-color: ${Palette.primary};` : ''}
-  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
+  #dropdown-arrow-image {stroke: ${props => (props.disabled ? Palette.grayscale[3] : Palette.black)};}
+  ${props => (props.focus ? css`border-color: ${Palette.primary};` : '')}
+  ${props => (props.disabledLoading ? StyleProps.animations.disabledLoading : '')}
 
 
 `
 `
-const Arrow = styled.div`
+const Arrow = styled.div<any>`
   position: absolute;
   position: absolute;
   top: 8px;
   top: 8px;
   right: 8px;
   right: 8px;
@@ -73,7 +71,7 @@ const Arrow = styled.div`
 type Props = {
 type Props = {
   value: string,
   value: string,
   customRef?: (ref: HTMLElement) => void,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   onChange: (value: string) => void,
   onChange: (value: string) => void,
   disabled?: boolean,
   disabled?: boolean,
   disabledLoading?: boolean,
   disabledLoading?: boolean,
@@ -81,7 +79,7 @@ type Props = {
   large?: boolean,
   large?: boolean,
   onFocus?: () => void,
   onFocus?: () => void,
   onBlur?: () => void,
   onBlur?: () => void,
-  onInputKeyDown?: (e: SyntheticKeyboardEvent<HTMLInputElement>) => void,
+  onInputKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
   highlight?: boolean,
   highlight?: boolean,
   embedded?: boolean,
   embedded?: boolean,
 }
 }
@@ -89,14 +87,14 @@ type State = {
   textInputFocus: boolean,
   textInputFocus: boolean,
 }
 }
 class AutocompleteInput extends React.Component<Props, State> {
 class AutocompleteInput extends React.Component<Props, State> {
-  state = {
+  state: State = {
     textInputFocus: false,
     textInputFocus: false,
   }
   }
 
 
-  textInputRef: HTMLElement
+  textInputRef: HTMLElement | undefined
 
 
   render() {
   render() {
-    let disabled = this.props.disabled || this.props.disabledLoading
+    const disabled = this.props.disabled || this.props.disabledLoading
     return (
     return (
       <Wrapper
       <Wrapper
         large={this.props.large}
         large={this.props.large}
@@ -106,11 +104,11 @@ class AutocompleteInput extends React.Component<Props, State> {
         disabled={disabled}
         disabled={disabled}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
         embedded={this.props.embedded}
         embedded={this.props.embedded}
-        innerRef={e => {
+        ref={(e: HTMLElement) => {
           if (this.props.customRef) {
           if (this.props.customRef) {
             this.props.customRef(e)
             this.props.customRef(e)
-          } else if (this.props.innerRef) {
-            this.props.innerRef(e)
+          } else if (this.props.ref) {
+            this.props.ref(e)
           }
           }
         }}
         }}
       >
       >
@@ -128,16 +126,20 @@ class AutocompleteInput extends React.Component<Props, State> {
           height="29px"
           height="29px"
           lineHeight="30px"
           lineHeight="30px"
           placeholder="Type to search ..."
           placeholder="Type to search ..."
-          onFocus={() => { if (this.props.onFocus) { this.props.onFocus() } this.setState({ textInputFocus: true }) }}
-          onBlur={() => { if (this.props.onBlur) { this.props.onBlur() } this.setState({ textInputFocus: false }) }}
-          innerRef={ref => { this.textInputRef = ref }}
+          onFocus={() => {
+            if (this.props.onFocus) { this.props.onFocus() } this.setState({ textInputFocus: true })
+          }}
+          onBlur={() => {
+            if (this.props.onBlur) { this.props.onBlur() } this.setState({ textInputFocus: false })
+          }}
+          _ref={(ref: HTMLElement | undefined) => { this.textInputRef = ref }}
           onInputKeyDown={this.props.onInputKeyDown}
           onInputKeyDown={this.props.onInputKeyDown}
         />
         />
         <Arrow
         <Arrow
           data-test-id="acInput-arrow"
           data-test-id="acInput-arrow"
           disabled={disabled}
           disabled={disabled}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
-          onClick={() => { this.textInputRef.focus() }}
+          onClick={() => { if (this.textInputRef) this.textInputRef.focus() }}
         />
         />
       </Wrapper>
       </Wrapper>
     )
     )

+ 0 - 0
src/components/atoms/AutocompleteInput/images/arrow.js → src/components/atoms/AutocompleteInput/images/arrow.ts


+ 2 - 1
src/components/atoms/AutocompleteInput/package.json

@@ -2,5 +2,6 @@
   "name": "AutocompleteInput",
   "name": "AutocompleteInput",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./AutocompleteInput.jsx"
+  "main":"./AutocompleteInput.tsx"
 }
 }
+

+ 0 - 2
src/components/atoms/AutocompleteInput/story.jsx → src/components/atoms/AutocompleteInput/story.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import { storiesOf } from '@storybook/react'
 import AutocompleteInput from '.'
 import AutocompleteInput from '.'

+ 6 - 7
src/components/atoms/AutocompleteInput/test.jsx → src/components/atoms/AutocompleteInput/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import sinon from 'sinon'
 import sinon from 'sinon'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
@@ -23,7 +21,7 @@ import AutocompleteInput from '.'
 type Props = {
 type Props = {
   value: string,
   value: string,
   customRef?: (ref: HTMLElement) => void,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   onChange: (value: string) => void,
   onChange: (value: string) => void,
   onClick?: () => void,
   onClick?: () => void,
   disabled?: boolean,
   disabled?: boolean,
@@ -34,12 +32,13 @@ type Props = {
 }
 }
 
 
 const wrap = (props: Props) => new TW(shallow(
 const wrap = (props: Props) => new TW(shallow(
-  <AutocompleteInput {...props} />
+  // eslint-disable-next-line react/jsx-props-no-spreading
+  <AutocompleteInput {...props} />,
 ), 'acInput')
 ), 'acInput')
 
 
 describe('AutocompleteInput Component', () => {
 describe('AutocompleteInput Component', () => {
   it('renders input with correct data', () => {
   it('renders input with correct data', () => {
-    let wrapper = wrap({
+    const wrapper = wrap({
       value: 'value',
       value: 'value',
       onChange: () => { },
       onChange: () => { },
     })
     })
@@ -49,8 +48,8 @@ describe('AutocompleteInput Component', () => {
   })
   })
 
 
   it('dispatches click', () => {
   it('dispatches click', () => {
-    let onClick = sinon.spy()
-    let wrapper = wrap({
+    const onClick = sinon.spy()
+    const wrapper = wrap({
       value: 'value',
       value: 'value',
       onChange: () => { },
       onChange: () => { },
       onClick,
       onClick,

+ 13 - 16
src/components/atoms/Button/Button.jsx → src/components/atoms/Button/Button.tsx

@@ -12,15 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import styled from 'styled-components'
 import styled from 'styled-components'
 
 
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 
 
-const backgroundColor = (props) => {
+const backgroundColor = (props: any) => {
   if (props.hollow) {
   if (props.hollow) {
     if (props.transparent) {
     if (props.transparent) {
       return 'transparent'
       return 'transparent'
@@ -35,14 +33,14 @@ const backgroundColor = (props) => {
   }
   }
   return Palette.primary
   return Palette.primary
 }
 }
-const disabledBackgroundColor = props => {
+const disabledBackgroundColor = (props: any) => {
   if (props.secondary && props.hollow) {
   if (props.secondary && props.hollow) {
     return Palette.grayscale[7]
     return Palette.grayscale[7]
   }
   }
   return backgroundColor(props)
   return backgroundColor(props)
 }
 }
 
 
-const hoverBackgroundColor = (props) => {
+const hoverBackgroundColor = (props: any) => {
   if (props.disabled && props.secondary && props.hollow) {
   if (props.disabled && props.secondary && props.hollow) {
     return Palette.grayscale[7]
     return Palette.grayscale[7]
   }
   }
@@ -61,7 +59,7 @@ const hoverBackgroundColor = (props) => {
   return Palette.primary
   return Palette.primary
 }
 }
 
 
-const border = (props) => {
+const border = (props: any) => {
   if (props.hollow) {
   if (props.hollow) {
     if (props.secondary) {
     if (props.secondary) {
       return `border: 1px solid ${Palette.grayscale[3]};`
       return `border: 1px solid ${Palette.grayscale[3]};`
@@ -73,14 +71,14 @@ const border = (props) => {
   }
   }
   return ''
   return ''
 }
 }
-const disabledBorder = props => {
+const disabledBorder = (props: any) => {
   if (props.secondary && props.hollow) {
   if (props.secondary && props.hollow) {
     return 'border: none;'
     return 'border: none;'
   }
   }
   return border(props)
   return border(props)
 }
 }
 
 
-const color = (props) => {
+const color = (props: any) => {
   if (props.hollow) {
   if (props.hollow) {
     if (props.secondary) {
     if (props.secondary) {
       return props.disabled ? Palette.grayscale[3] : Palette.black
       return props.disabled ? Palette.grayscale[3] : Palette.black
@@ -92,7 +90,7 @@ const color = (props) => {
   }
   }
   return 'white'
   return 'white'
 }
 }
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.width) {
   if (props.width) {
     return props.width
     return props.width
   }
   }
@@ -116,13 +114,13 @@ const StyledButton = styled.button`
   font-size: inherit;
   font-size: inherit;
   transition: background-color ${StyleProps.animations.swift}, opacity ${StyleProps.animations.swift};
   transition: background-color ${StyleProps.animations.swift}, opacity ${StyleProps.animations.swift};
   &:disabled {
   &:disabled {
-    opacity: ${props => props.secondary ? 1 : 0.7};
+    opacity: ${(props: any) => (props.secondary ? 1 : 0.7)};
     cursor: not-allowed;
     cursor: not-allowed;
     background-color: ${props => disabledBackgroundColor(props)};
     background-color: ${props => disabledBackgroundColor(props)};
     ${props => disabledBorder(props)}
     ${props => disabledBorder(props)}
   }
   }
   &:hover {
   &:hover {
-    ${props => props.hollow ? `color: ${props.disabled ? Palette.grayscale[3] : 'white'};` : ''}
+    ${(props: any) => (props.hollow ? `color: ${props.disabled ? Palette.grayscale[3] : 'white'};` : '')}
     background-color: ${props => hoverBackgroundColor(props)};
     background-color: ${props => hoverBackgroundColor(props)};
   }
   }
   &:focus {
   &:focus {
@@ -130,10 +128,9 @@ const StyledButton = styled.button`
   }
   }
 `
 `
 
 
-const Button = (props: any) => {
-  return (
-    <StyledButton {...props} onFocus={e => { e.target.blur() }} />
-  )
-}
+const Button = (props: any) => (
+  // eslint-disable-next-line react/jsx-props-no-spreading
+  <StyledButton {...props} onFocus={e => { e.target.blur() }} />
+)
 
 
 export default Button
 export default Button

+ 2 - 1
src/components/atoms/Button/package.json

@@ -2,5 +2,6 @@
   "name": "Button",
   "name": "Button",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./Button.jsx"
+  "main":"./Button.tsx"
 }
 }
+

+ 7 - 7
src/components/atoms/Button/story.jsx → src/components/atoms/Button/story.tsx

@@ -13,25 +13,25 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
 import React from 'react'
 import React from 'react'
-import { storiesOf, action } from '@storybook/react'
+import { storiesOf } from '@storybook/react'
 import Button from '.'
 import Button from '.'
 
 
 storiesOf('Button', module)
 storiesOf('Button', module)
   .add('primary', () => (
   .add('primary', () => (
-    <Button onClick={action('clicked')}>Hello</Button>
+    <Button>Hello</Button>
   ))
   ))
   .add('secondary', () => (
   .add('secondary', () => (
-    <Button secondary onClick={action('clicked')}>Hello</Button>
+    <Button secondary>Hello</Button>
   ))
   ))
   .add('alert', () => (
   .add('alert', () => (
-    <Button alert onClick={action('clicked')}>Hello</Button>
+    <Button alert>Hello</Button>
   ))
   ))
   .add('hollow primary', () => (
   .add('hollow primary', () => (
-    <Button hollow onClick={action('clicked')}>Hello</Button>
+    <Button hollow>Hello</Button>
   ))
   ))
   .add('hollow secondary', () => (
   .add('hollow secondary', () => (
-    <Button hollow secondary onClick={action('clicked')}>Hello</Button>
+    <Button hollow secondary>Hello</Button>
   ))
   ))
   .add('hollow alert', () => (
   .add('hollow alert', () => (
-    <Button hollow alert onClick={action('clicked')}>Hello</Button>
+    <Button hollow alert>Hello</Button>
   ))
   ))

+ 3 - 2
src/components/atoms/Button/test.jsx → src/components/atoms/Button/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
 import sinon from 'sinon'
@@ -40,3 +38,6 @@ describe('Button Component', () => {
     expect(onButtonClick.calledOnce).toBe(true)
     expect(onButtonClick.calledOnce).toBe(true)
   })
   })
 })
 })
+
+
+

+ 10 - 12
src/components/atoms/Checkbox/Checkbox.jsx → src/components/atoms/Checkbox/Checkbox.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import * as React from 'react'
 import * as React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled, { css } from 'styled-components'
 import styled, { css } from 'styled-components'
@@ -23,17 +21,17 @@ import StyleProps from '../../styleUtils/StyleProps'
 
 
 import checkmarkImage from './images/checkmark.svg'
 import checkmarkImage from './images/checkmark.svg'
 
 
-const CheckmarkImage = styled.div`
+const CheckmarkImage = styled.div<any>`
   width: 10px;
   width: 10px;
   height: 7px;
   height: 7px;
   background: url('${checkmarkImage}') no-repeat center;
   background: url('${checkmarkImage}') no-repeat center;
   transform: scale(0);
   transform: scale(0);
   transition: transform 250ms cubic-bezier(0, 1.4, 1, 1);
   transition: transform 250ms cubic-bezier(0, 1.4, 1, 1);
 `
 `
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   display: flex;
-  ${props => props.disabled ? '' : 'cursor: pointer;'}
-  ${props => props.disabled ? 'opacity: 0.9;' : ''}
+  ${(props: any) => (props.disabled ? '' : 'cursor: pointer;')}
+  ${(props: any) => (props.disabled ? 'opacity: 0.9;' : '')}
   justify-content: center;
   justify-content: center;
   align-items: center;
   align-items: center;
   ${StyleProps.exactSize('16px')}
   ${StyleProps.exactSize('16px')}
@@ -41,13 +39,13 @@ const Wrapper = styled.div`
   border-radius: 3px;
   border-radius: 3px;
   background: white;
   background: white;
   transition: all ${StyleProps.animations.swift};
   transition: all ${StyleProps.animations.swift};
-  ${props => props.checked ? css`
+  ${(props: any) => (props.checked ? css`
     border-color: ${Palette.primary};
     border-color: ${Palette.primary};
     background: ${Palette.primary};
     background: ${Palette.primary};
     ${CheckmarkImage} {
     ${CheckmarkImage} {
       transform: scale(1);
       transform: scale(1);
     }
     }
-  ` : ''}
+  ` : '')}
   :focus {
   :focus {
     border: 1px solid ${Palette.primary};
     border: 1px solid ${Palette.primary};
     outline: none;
     outline: none;
@@ -60,8 +58,8 @@ type Props = {
   disabled?: boolean,
   disabled?: boolean,
   onChange?: (checked: boolean) => void,
   onChange?: (checked: boolean) => void,
   'data-test-id'?: string,
   'data-test-id'?: string,
-  onMouseDown?: (e: SyntheticMouseEvent<HTMLDivElement>) => void,
-  onMouseUp?: (e: SyntheticMouseEvent<HTMLDivElement>) => void,
+  onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void,
+  onMouseUp?: (e: React.MouseEvent<HTMLDivElement>) => void,
 }
 }
 @observer
 @observer
 class Checkbox extends React.Component<Props> {
 class Checkbox extends React.Component<Props> {
@@ -73,7 +71,7 @@ class Checkbox extends React.Component<Props> {
     this.props.onChange(!this.props.checked)
     this.props.onChange(!this.props.checked)
   }
   }
 
 
-  handleKeyDown(e: SyntheticKeyboardEvent<HTMLDivElement>) {
+  handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
     if (e.key !== ' ') {
     if (e.key !== ' ') {
       return
       return
     }
     }
@@ -90,7 +88,7 @@ class Checkbox extends React.Component<Props> {
         checked={this.props.checked}
         checked={this.props.checked}
         disabled={this.props.disabled}
         disabled={this.props.disabled}
         tabIndex={0}
         tabIndex={0}
-        onKeyDown={e => { this.handleKeyDown(e) }}
+        onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => { this.handleKeyDown(e) }}
         onMouseDown={this.props.onMouseDown}
         onMouseDown={this.props.onMouseDown}
         onMouseUp={this.props.onMouseUp}
         onMouseUp={this.props.onMouseUp}
       >
       >

+ 2 - 1
src/components/atoms/Checkbox/package.json

@@ -2,5 +2,6 @@
   "name": "Checkbox",
   "name": "Checkbox",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./Checkbox.jsx"
+  "main":"./Checkbox.tsx"
 }
 }
+

+ 8 - 6
src/components/atoms/Checkbox/story.jsx → src/components/atoms/Checkbox/story.tsx

@@ -17,17 +17,19 @@ import { storiesOf } from '@storybook/react'
 import Checkbox from '.'
 import Checkbox from '.'
 
 
 class Wrapper extends React.Component {
 class Wrapper extends React.Component {
-  constructor() {
-    super()
-    this.state = { checked: false }
-  }
+  state = { checked: false }
 
 
-  handleChange(checked) {
+  handleChange(checked: boolean) {
     this.setState({ checked })
     this.setState({ checked })
   }
   }
 
 
   render() {
   render() {
-    return <Checkbox checked={this.state.checked} onChange={checked => { this.handleChange(checked) }} />
+    return (
+      <Checkbox
+        checked={this.state.checked}
+        onChange={checked => { this.handleChange(checked) }}
+      />
+    )
   }
   }
 }
 }
 
 

+ 3 - 2
src/components/atoms/Checkbox/test.jsx → src/components/atoms/Checkbox/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
 import sinon from 'sinon'
@@ -41,3 +39,6 @@ describe('Checkbox Component', () => {
     expect(onChange.notCalled).toBe(true)
     expect(onChange.notCalled).toBe(true)
   })
   })
 })
 })
+
+
+

+ 1 - 2
src/components/atoms/CopyButton/CopyButton.jsx → src/components/atoms/CopyButton/CopyButton.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 import styled from 'styled-components'
@@ -36,6 +34,7 @@ const Wrapper = styled.span`
 class CopyButton extends React.Component<{}> {
 class CopyButton extends React.Component<{}> {
   render() {
   render() {
     return (
     return (
+      // eslint-disable-next-line react/jsx-props-no-spreading
       <Wrapper {...this.props} />
       <Wrapper {...this.props} />
     )
     )
   }
   }

+ 2 - 1
src/components/atoms/CopyButton/package.json

@@ -2,5 +2,6 @@
   "name": "CopyButton",
   "name": "CopyButton",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./CopyButton.jsx"
+  "main":"./CopyButton.tsx"
 }
 }
+

+ 1 - 1
src/components/atoms/CopyButton/story.jsx → src/components/atoms/CopyButton/story.tsx

@@ -18,7 +18,7 @@ import styled from 'styled-components'
 
 
 import CopyButton from '.'
 import CopyButton from '.'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   cursor: pointer;
   display: inline-block;
   display: inline-block;
 
 

+ 3 - 2
src/components/atoms/CopyButton/test.jsx → src/components/atoms/CopyButton/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import CopyButton from '.'
 import CopyButton from '.'
@@ -27,3 +25,6 @@ describe('CopyButton Component', () => {
     expect(span.prop('onClick')).toBe(onClick)
     expect(span.prop('onClick')).toBe(onClick)
   })
   })
 })
 })
+
+
+

+ 9 - 8
src/components/atoms/CopyMultilineValue/CopyMultilineValue.jsx → src/components/atoms/CopyMultilineValue/CopyMultilineValue.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 import styled from 'styled-components'
@@ -26,7 +24,7 @@ const CopyButtonStyled = styled(CopyButton)`
   background-position-y: 4px;
   background-position-y: 4px;
   margin-left: 4px;
   margin-left: 4px;
 `
 `
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   cursor: pointer;
   word-break: break-word;
   word-break: break-word;
 
 
@@ -37,19 +35,22 @@ const Wrapper = styled.div`
 
 
 type Props = {
 type Props = {
   'data-test-id'?: string,
   'data-test-id'?: string,
-  value: string,
+  value: string | null | undefined,
   onCopy?: (value: string) => void,
   onCopy?: (value: string) => void,
   useDangerousHtml?: boolean,
   useDangerousHtml?: boolean,
 }
 }
 @observer
 @observer
 class CopyMultineValue extends React.Component<Props> {
 class CopyMultineValue extends React.Component<Props> {
   handleCopy() {
   handleCopy() {
-    let value = this.props.value
+    let { value } = this.props
+    if (!value) {
+      return
+    }
     if (this.props.useDangerousHtml) {
     if (this.props.useDangerousHtml) {
       value = value.replace(/<br\s*\/>/g, '\n').replace(/<.*?>/g, '')
       value = value.replace(/<br\s*\/>/g, '\n').replace(/<.*?>/g, '')
     }
     }
 
 
-    let succesful = DomUtils.copyTextToClipboard(value)
+    const succesful = DomUtils.copyTextToClipboard(value)
     if (this.props.onCopy) this.props.onCopy(value)
     if (this.props.onCopy) this.props.onCopy(value)
 
 
     if (succesful) {
     if (succesful) {
@@ -58,9 +59,9 @@ class CopyMultineValue extends React.Component<Props> {
   }
   }
 
 
   render() {
   render() {
-    let text = this.props.value
+    let text: React.ReactNode = this.props.value
     if (this.props.useDangerousHtml) {
     if (this.props.useDangerousHtml) {
-      text = <span dangerouslySetInnerHTML={{ __html: text }} />
+      text = <span dangerouslySetInnerHTML={{ __html: text as string }} />
     }
     }
 
 
     return (
     return (

+ 2 - 1
src/components/atoms/CopyMultilineValue/package.json

@@ -2,5 +2,6 @@
   "name": "CopyMultilineValue",
   "name": "CopyMultilineValue",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./CopyMultilineValue.jsx"
+  "main":"./CopyMultilineValue.tsx"
 }
 }
+

+ 3 - 2
src/components/atoms/CopyMultilineValue/test.jsx → src/components/atoms/CopyMultilineValue/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
 import sinon from 'sinon'
@@ -35,3 +33,6 @@ describe('CopyMultilineValue Component', () => {
     expect(onCopy.args[0][0]).toBe('the_value')
     expect(onCopy.args[0][0]).toBe('the_value')
   })
   })
 })
 })
+
+
+

+ 11 - 12
src/components/atoms/CopyValue/CopyValue.jsx → src/components/atoms/CopyValue/CopyValue.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 import styled from 'styled-components'
@@ -22,16 +20,16 @@ import CopyButton from '../CopyButton'
 import DomUtils from '../../../utils/DomUtils'
 import DomUtils from '../../../utils/DomUtils'
 import notificationStore from '../../../stores/NotificationStore'
 import notificationStore from '../../../stores/NotificationStore'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   cursor: pointer;
   display: flex;
   display: flex;
   &:hover > span:last-child {
   &:hover > span:last-child {
     opacity: 1;
     opacity: 1;
   }
   }
 `
 `
-const Value = styled.span`
-  width: ${props => `${props.width || 'auto'}`};
-  ${props => props.maxWidth ? `max-width: ${props.maxWidth};` : ''}
+const Value = styled.span<any>`
+  width: ${(props: any) => `${props.width || 'auto'}`};
+  ${(props: any) => (props.maxWidth ? `max-width: ${props.maxWidth};` : '')}
   white-space: nowrap;
   white-space: nowrap;
   overflow: hidden;
   overflow: hidden;
   text-overflow: ellipsis;
   text-overflow: ellipsis;
@@ -48,10 +46,10 @@ type Props = {
 }
 }
 @observer
 @observer
 class CopyValue extends React.Component<Props> {
 class CopyValue extends React.Component<Props> {
-  handleCopyIdClick(e: Event) {
+  handleCopyIdClick(e: React.MouseEvent<HTMLDivElement>) {
     if (e && e.stopPropagation) e.stopPropagation()
     if (e && e.stopPropagation) e.stopPropagation()
 
 
-    let succesful = DomUtils.copyTextToClipboard(this.props.value)
+    const succesful = DomUtils.copyTextToClipboard(this.props.value)
     if (this.props.onCopy) this.props.onCopy(this.props.value)
     if (this.props.onCopy) this.props.onCopy(this.props.value)
 
 
     if (succesful) {
     if (succesful) {
@@ -64,16 +62,17 @@ class CopyValue extends React.Component<Props> {
   render() {
   render() {
     return (
     return (
       <Wrapper
       <Wrapper
-        onClick={e => { this.handleCopyIdClick(e) }}
-        onMouseDown={e => { e.stopPropagation() }}
-        onMouseUp={e => { e.stopPropagation() }}
+        onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { this.handleCopyIdClick(e) }}
+        onMouseDown={(e: { stopPropagation: () => void }) => { e.stopPropagation() }}
+        onMouseUp={(e: { stopPropagation: () => void }) => { e.stopPropagation() }}
         data-test-id={this.props['data-test-id'] || 'copyValue'}
         data-test-id={this.props['data-test-id'] || 'copyValue'}
       >
       >
         <Value
         <Value
           data-test-id="copyValue-value"
           data-test-id="copyValue-value"
           width={this.props.width}
           width={this.props.width}
           maxWidth={this.props.maxWidth}
           maxWidth={this.props.maxWidth}
-        >{this.props.value}</Value>
+        >{this.props.value}
+        </Value>
         <CopyButton />
         <CopyButton />
       </Wrapper>
       </Wrapper>
     )
     )

+ 2 - 1
src/components/atoms/CopyValue/package.json

@@ -2,5 +2,6 @@
   "name": "CopyValue",
   "name": "CopyValue",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./CopyValue.jsx"
+  "main":"./CopyValue.tsx"
 }
 }
+

+ 3 - 2
src/components/atoms/CopyValue/test.jsx → src/components/atoms/CopyValue/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
 import sinon from 'sinon'
@@ -36,3 +34,6 @@ describe('CopyValue Component', () => {
     expect(onCopy.args[0][0]).toBe('the_value')
     expect(onCopy.args[0][0]).toBe('the_value')
   })
   })
 })
 })
+
+
+

+ 41 - 35
src/components/atoms/DropdownButton/DropdownButton.jsx → src/components/atoms/DropdownButton/DropdownButton.tsx

@@ -12,17 +12,15 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import styled, { css } from 'styled-components'
 import styled, { css } from 'styled-components'
 
 
-import arrowImage from './images/arrow.js'
+import arrowImage from './images/arrow'
 
 
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 
 
-const getLabelColor = props => {
+const getLabelColor = (props: any) => {
   if (props.disabled) {
   if (props.disabled) {
     return Palette.grayscale[3]
     return Palette.grayscale[3]
   }
   }
@@ -33,18 +31,18 @@ const getLabelColor = props => {
 
 
   return Palette.black
   return Palette.black
 }
 }
-const Label = styled.div`
-  color: ${props => getLabelColor(props)};
-  margin: 0 32px 0 ${props => props.embedded ? 0 : 16}px;
+const Label = styled.div<any>`
+  color: ${(props: any) => getLabelColor(props)};
+  margin: 0 32px 0 ${(props: any) => (props.embedded ? 0 : 16)}px;
   overflow: hidden;
   overflow: hidden;
   text-overflow: ellipsis;
   text-overflow: ellipsis;
   white-space: nowrap;
   white-space: nowrap;
   flex-grow: 1;
   flex-grow: 1;
-  ${props => props.useBold ? `font-weight: ${StyleProps.fontWeights.medium};` : ''}
-  ${props => props.centered ? 'text-align: center;' : ''}
+  ${(props: any) => (props.useBold ? `font-weight: ${StyleProps.fontWeights.medium};` : '')}
+  ${(props: any) => (props.centered ? 'text-align: center;' : '')}
 `
 `
 
 
-const getBackgroundColor = props => {
+const getBackgroundColor = (props: any) => {
   if (props.embedded) {
   if (props.embedded) {
     return 'white'
     return 'white'
   }
   }
@@ -63,7 +61,7 @@ const getBackgroundColor = props => {
 
 
   return 'white'
   return 'white'
 }
 }
-const getArrowColor = props => {
+const getArrowColor = (props: any) => {
   if (props.disabled) {
   if (props.disabled) {
     return Palette.grayscale[3]
     return Palette.grayscale[3]
   }
   }
@@ -78,7 +76,7 @@ const getArrowColor = props => {
 
 
   return Palette.black
   return Palette.black
 }
 }
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.large) {
   if (props.large) {
     return StyleProps.inputSizes.large.width - 2
     return StyleProps.inputSizes.large.width - 2
   }
   }
@@ -87,7 +85,7 @@ const getWidth = props => {
   }
   }
   return StyleProps.inputSizes.regular.width - 2
   return StyleProps.inputSizes.regular.width - 2
 }
 }
-const borderColor = props => {
+const borderColor = (props: any) => {
   if (props.highlight) {
   if (props.highlight) {
     return Palette.alert
     return Palette.alert
   }
   }
@@ -105,7 +103,7 @@ const borderColor = props => {
   }
   }
   return Palette.grayscale[3]
   return Palette.grayscale[3]
 }
 }
-const backgroundHover = props => {
+const backgroundHover = (props: any) => {
   if (props.disabled || props.embedded) {
   if (props.disabled || props.embedded) {
     return ''
     return ''
   }
   }
@@ -115,35 +113,35 @@ const backgroundHover = props => {
   return Palette.primary
   return Palette.primary
 }
 }
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   position: relative;
   position: relative;
-  width: ${props => getWidth(props)}px;
-  height: ${props => props.large ? StyleProps.inputSizes.large.height - 2
-    : StyleProps.inputSizes.regular.height - 2}px;
+  width: ${(props: any) => getWidth(props)}px;
+  height: ${(props: any) => (props.large ? StyleProps.inputSizes.large.height - 2
+    : StyleProps.inputSizes.regular.height - 2)}px;
   border: 1px solid ${props => borderColor(props)};
   border: 1px solid ${props => borderColor(props)};
   border-radius: ${StyleProps.borderRadius};
   border-radius: ${StyleProps.borderRadius};
-  cursor: ${props => props.disabled ? 'default' : 'pointer'};
+  cursor: ${(props: any) => (props.disabled ? 'default' : 'pointer')};
   transition: all ${StyleProps.animations.swift};
   transition: all ${StyleProps.animations.swift};
   background: ${props => getBackgroundColor(props)};
   background: ${props => getBackgroundColor(props)};
-  ${props => props.embedded ? css`
+  ${(props: any) => (props.embedded ? css`
     border: 0;
     border: 0;
     width: calc(100% + 8px);
     width: calc(100% + 8px);
-  ` : ''}
+  ` : '')}
 
 
-  #dropdown-arrow-image {stroke: ${props => getArrowColor(props)};}
+  #dropdown-arrow-image {stroke: ${(props: any) => getArrowColor(props)};}
   &:hover {
   &:hover {
-    #dropdown-arrow-image {stroke: ${props => props.disabled ? '' : props.embedded ? '' : 'white'};}
-    background: ${props => backgroundHover(props)};
+    #dropdown-arrow-image {stroke: ${(props: any) => (props.disabled ? '' : props.embedded ? '' : 'white')};}
+    background: ${(props: any) => backgroundHover(props)};
   }
   }
 
 
   &:hover ${Label} {
   &:hover ${Label} {
-    color: ${props => props.disabled ? '' : props.embedded ? '' : 'white'};
+    color: ${(props: any) => (props.disabled ? '' : props.embedded ? '' : 'white')};
   }
   }
-  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
+  ${(props: any) => (props.disabledLoading ? StyleProps.animations.disabledLoading : '')}
 `
 `
-const Arrow = styled.div`
+const Arrow = styled.div<any>`
   position: absolute;
   position: absolute;
   right: 8px;
   right: 8px;
   top: 8px;
   top: 8px;
@@ -157,7 +155,7 @@ type Props = {
   value: string,
   value: string,
   onClick?: (event: Event) => void,
   onClick?: (event: Event) => void,
   customRef?: (ref: HTMLElement) => void,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   arrowRef?: (ref: HTMLElement) => void,
   arrowRef?: (ref: HTMLElement) => void,
   className?: string,
   className?: string,
   disabled?: boolean,
   disabled?: boolean,
@@ -168,39 +166,47 @@ type Props = {
   secondary?: boolean,
   secondary?: boolean,
   centered?: boolean,
   centered?: boolean,
   outline?: boolean,
   outline?: boolean,
+  primary?: boolean,
+  width?: number,
+  useBold?: boolean,
+  onMouseDown?: () => void,
+  onMouseUp?: () => void,
 }
 }
 class DropdownButton extends React.Component<Props> {
 class DropdownButton extends React.Component<Props> {
   render() {
   render() {
-    let disabled = this.props.disabled || this.props.disabledLoading
+    const disabled = this.props.disabled || this.props.disabledLoading
     return (
     return (
       <Wrapper
       <Wrapper
         data-test-id={this.props['data-test-id'] || 'dropdownButton'}
         data-test-id={this.props['data-test-id'] || 'dropdownButton'}
+        // eslint-disable-next-line react/jsx-props-no-spreading
         {...this.props}
         {...this.props}
         disabled={disabled}
         disabled={disabled}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
-        innerRef={e => {
+        ref={(e: HTMLElement) => {
           if (this.props.customRef) {
           if (this.props.customRef) {
             this.props.customRef(e)
             this.props.customRef(e)
-          } else if (this.props.innerRef) {
-            this.props.innerRef(e)
+          } else if (this.props.ref) {
+            this.props.ref(e)
           }
           }
         }}
         }}
-        onClick={e => {
+        onClick={(e: Event) => {
           if (!disabled && this.props.onClick) this.props.onClick(e)
           if (!disabled && this.props.onClick) this.props.onClick(e)
         }}
         }}
       >
       >
         <Label
         <Label
+          // eslint-disable-next-line react/jsx-props-no-spreading
           {...this.props}
           {...this.props}
           onClick={() => { }}
           onClick={() => { }}
-          innerRef={() => { }}
+          ref={() => { }}
           data-test-id="dropdownButton-value"
           data-test-id="dropdownButton-value"
           disabled={disabled}
           disabled={disabled}
         >
         >
           {this.props.value}
           {this.props.value}
         </Label>
         </Label>
         <Arrow
         <Arrow
+          // eslint-disable-next-line react/jsx-props-no-spreading
           {...this.props}
           {...this.props}
-          innerRef={ref => { if (this.props.arrowRef) this.props.arrowRef(ref) }}
+          ref={(ref: HTMLElement) => { if (this.props.arrowRef) this.props.arrowRef(ref) }}
           onClick={() => { }}
           onClick={() => { }}
           data-test-id=""
           data-test-id=""
           disabled={disabled}
           disabled={disabled}

+ 0 - 0
src/components/atoms/DropdownButton/images/arrow.js → src/components/atoms/DropdownButton/images/arrow.ts


+ 2 - 1
src/components/atoms/DropdownButton/package.json

@@ -2,5 +2,6 @@
   "name": "DropdownButton",
   "name": "DropdownButton",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./DropdownButton.jsx"
+  "main":"./DropdownButton.tsx"
 }
 }
+

+ 6 - 6
src/components/atoms/DropdownButton/story.jsx → src/components/atoms/DropdownButton/story.tsx

@@ -13,22 +13,22 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
 import React from 'react'
 import React from 'react'
-import { storiesOf, action } from '@storybook/react'
+import { storiesOf } from '@storybook/react'
 import DropdownButton from '.'
 import DropdownButton from '.'
 
 
 storiesOf('DropdownButton', module)
 storiesOf('DropdownButton', module)
   .add('default', () => (
   .add('default', () => (
-    <DropdownButton value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton value="Dropdown Button" />
   ))
   ))
   .add('primary', () => (
   .add('primary', () => (
-    <DropdownButton primary value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton primary value="Dropdown Button" />
   ))
   ))
   .add('disabled', () => (
   .add('disabled', () => (
-    <DropdownButton disabled value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton disabled value="Dropdown Button" />
   ))
   ))
   .add('disabled loading', () => (
   .add('disabled loading', () => (
-    <DropdownButton disabledLoading value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton disabledLoading value="Dropdown Button" />
   ))
   ))
   .add('secondary centered', () => (
   .add('secondary centered', () => (
-    <DropdownButton secondary centered value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton secondary centered value="Dropdown Button" />
   ))
   ))

+ 3 - 2
src/components/atoms/DropdownButton/test.jsx → src/components/atoms/DropdownButton/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
 import sinon from 'sinon'
@@ -42,3 +40,6 @@ describe('DropdownButton Component', () => {
     expect(onClick.notCalled).toBe(true)
     expect(onClick.notCalled).toBe(true)
   })
   })
 })
 })
+
+
+

+ 11 - 10
src/components/atoms/EndpointLogos/EndpointLogos.jsx → src/components/atoms/EndpointLogos/EndpointLogos.tsx

@@ -12,22 +12,20 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled, { css } from 'styled-components'
 import styled, { css } from 'styled-components'
 
 
 import Generic from './resources/Generic'
 import Generic from './resources/Generic'
 
 
-const Wrapper = styled.div``
-const Logo = styled.div`
+const Wrapper = styled.div<any>``
+const Logo = styled.div<any>`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
   width: ${props => props.width}px;
   width: ${props => props.width}px;
   height: ${props => props.height}px;
   height: ${props => props.height}px;
-  ${props => props.url ? css`background: url('${props.url}') no-repeat center;` : ''}
+  ${(props: any) => (props.url ? css`background: url('${props.url}') no-repeat center;` : '')}
   background-size: contain;
   background-size: contain;
 `
 `
 const widthHeights = [
 const widthHeights = [
@@ -40,12 +38,14 @@ const PROVIDER_LOGOS = [
   'azure', 'openstack', 'opc', 'oracle_vm', 'vmware_vsphere', 'aws', 'oci', 'hyper-v', 'scvmm',
   'azure', 'openstack', 'opc', 'oracle_vm', 'vmware_vsphere', 'aws', 'oci', 'hyper-v', 'scvmm',
 ]
 ]
 type Props = {
 type Props = {
-  endpoint?: ?string,
+  endpoint?: string | null,
   height: number,
   height: number,
   disabled?: boolean,
   disabled?: boolean,
   white?: boolean,
   white?: boolean,
   baseUrl?: string,
   baseUrl?: string,
+  onClick?: () => void
   'data-test-id'?: string,
   'data-test-id'?: string,
+  style?: React.CSSProperties
 }
 }
 @observer
 @observer
 class EndpointLogos extends React.Component<Props> {
 class EndpointLogos extends React.Component<Props> {
@@ -66,21 +66,22 @@ class EndpointLogos extends React.Component<Props> {
   }
   }
 
 
   render() {
   render() {
-    let size = widthHeights.find(wh => wh.h === this.props.height)
+    const size = widthHeights.find(wh => wh.h === this.props.height)
 
 
     if (!size) {
     if (!size) {
       return null
       return null
     }
     }
 
 
-    let imageUrl: ?string = null
-    let provider = this.props.endpoint
+    let imageUrl: string | null = null
+    const provider = this.props.endpoint
     if (provider && PROVIDER_LOGOS.indexOf(provider) > -1) {
     if (provider && PROVIDER_LOGOS.indexOf(provider) > -1) {
       imageUrl = `${this.props.baseUrl || ''}/api/logos/${provider}/${size.h}`
       imageUrl = `${this.props.baseUrl || ''}/api/logos/${provider}/${size.h}`
-      let style = this.props.white ? 'white' : this.props.disabled ? 'disabled' : null
+      const style = this.props.white ? 'white' : this.props.disabled ? 'disabled' : null
       imageUrl = style ? `${imageUrl}/${style}` : imageUrl
       imageUrl = style ? `${imageUrl}/${style}` : imageUrl
     }
     }
 
 
     return (
     return (
+      // eslint-disable-next-line react/jsx-props-no-spreading
       <Wrapper {...this.props} data-test-id={this.props['data-test-id'] || 'endpointLogos'}>
       <Wrapper {...this.props} data-test-id={this.props['data-test-id'] || 'endpointLogos'}>
         <Logo
         <Logo
           width={size.w}
           width={size.w}

+ 2 - 1
src/components/atoms/EndpointLogos/package.json

@@ -2,5 +2,6 @@
   "name": "EndpointLogos",
   "name": "EndpointLogos",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./EndpointLogos.jsx"
+  "main":"./EndpointLogos.tsx"
 }
 }
+

+ 9 - 11
src/components/atoms/EndpointLogos/resources/Generic.jsx → src/components/atoms/EndpointLogos/resources/Generic.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import styled from 'styled-components'
 import styled from 'styled-components'
 
 
@@ -23,7 +21,7 @@ import generic64Image from './generic-64.svg'
 import generic128Image from './generic-128.svg'
 import generic128Image from './generic-128.svg'
 import generic128DisabledImage from './generic-128-disabled.svg'
 import generic128DisabledImage from './generic-128-disabled.svg'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   text-align: center;
   text-align: center;
   max-height: 100%;
   max-height: 100%;
   overflow: hidden;
   overflow: hidden;
@@ -32,20 +30,20 @@ const Wrapper = styled.div`
   align-items: center;
   align-items: center;
   letter-spacing: -1px;
   letter-spacing: -1px;
 `
 `
-const Logo = styled.div`
-  ${props => StyleProps.exactWidth(`${props.width}px`)}
-  ${props => StyleProps.exactHeight(`${props.height}px`)}
-  background: url(${props => props.image}) center no-repeat;
+const Logo = styled.div<any>`
+  ${(props: any) => StyleProps.exactWidth(`${props.width}px`)}
+  ${(props: any) => StyleProps.exactHeight(`${props.height}px`)}
+  background: url(${(props: any) => props.image}) center no-repeat;
 `
 `
 
 
 type Props = {
 type Props = {
   name: string,
   name: string,
   size: { w: number, h: number },
   size: { w: number, h: number },
-  disabled: ?boolean,
-  white: ?boolean,
+  disabled: boolean | null | undefined,
+  white: boolean | null | undefined,
 }
 }
 class Generic extends React.Component<Props> {
 class Generic extends React.Component<Props> {
-  render32Generic(white: ?boolean) {
+  render32Generic(white: boolean | null | undefined) {
     return (
     return (
       <Wrapper style={{
       <Wrapper style={{
         fontSize: '14px',
         fontSize: '14px',
@@ -83,7 +81,7 @@ class Generic extends React.Component<Props> {
     )
     )
   }
   }
 
 
-  render128Generic(disabled: ?boolean) {
+  render128Generic(disabled: boolean | null | undefined) {
     return (
     return (
       <Wrapper style={{
       <Wrapper style={{
         fontSize: '22px',
         fontSize: '22px',

+ 14 - 12
src/components/atoms/EndpointLogos/story.jsx → src/components/atoms/EndpointLogos/story.tsx

@@ -12,26 +12,28 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import { storiesOf } from '@storybook/react'
 import styled from 'styled-components'
 import styled from 'styled-components'
 import EndpointLogos from '.'
 import EndpointLogos from '.'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   display: flex;
   flex-wrap: wrap;
   flex-wrap: wrap;
   margin-left: -32px;
   margin-left: -32px;
   margin-top: -32px;
   margin-top: -32px;
-  ${props => props.background ? `background: ${props.background};` : ''}
+  ${(props: any) => (props.background ? `background: ${props.background};` : '')}
 
 
   > div {
   > div {
     margin-left: 32px;
     margin-left: 32px;
     margin-top: 32px;
     margin-top: 32px;
   }
   }
 `
 `
-const wrap = (endpoint, height, disabled = false, white = false) => (
+const wrap = (
+  endpoint: string | null | undefined,
+  height: number | undefined,
+  disabled = false, white = false,
+) => (
   <EndpointLogos
   <EndpointLogos
     endpoint={endpoint}
     endpoint={endpoint}
     height={height}
     height={height}
@@ -40,7 +42,7 @@ const wrap = (endpoint, height, disabled = false, white = false) => (
     baseUrl="http://localhost:3000"
     baseUrl="http://localhost:3000"
   />
   />
 )
 )
-let providers = [
+const providers = [
   'aws',
   'aws',
   'azure',
   'azure',
   'opc',
   'opc',
@@ -55,7 +57,7 @@ let providers = [
 
 
 storiesOf('EndpointLogos', module)
 storiesOf('EndpointLogos', module)
   .add('32px', () => {
   .add('32px', () => {
-    let height = 32
+    const height = 32
     return (
     return (
       <Wrapper>
       <Wrapper>
         {providers.map(p => wrap(p, height))}
         {providers.map(p => wrap(p, height))}
@@ -63,7 +65,7 @@ storiesOf('EndpointLogos', module)
     )
     )
   })
   })
   .add('32px - white', () => {
   .add('32px - white', () => {
-    let height = 32
+    const height = 32
     return (
     return (
       <Wrapper background="#202134">
       <Wrapper background="#202134">
         {providers.map(p => wrap(p, height, false, true))}
         {providers.map(p => wrap(p, height, false, true))}
@@ -71,7 +73,7 @@ storiesOf('EndpointLogos', module)
     )
     )
   })
   })
   .add('42px', () => {
   .add('42px', () => {
-    let height = 42
+    const height = 42
     return (
     return (
       <Wrapper>
       <Wrapper>
         {providers.map(p => wrap(p, height))}
         {providers.map(p => wrap(p, height))}
@@ -79,7 +81,7 @@ storiesOf('EndpointLogos', module)
     )
     )
   })
   })
   .add('64px', () => {
   .add('64px', () => {
-    let height = 64
+    const height = 64
     return (
     return (
       <Wrapper>
       <Wrapper>
         {providers.map(p => wrap(p, height))}
         {providers.map(p => wrap(p, height))}
@@ -87,7 +89,7 @@ storiesOf('EndpointLogos', module)
     )
     )
   })
   })
   .add('128px', () => {
   .add('128px', () => {
-    let height = 128
+    const height = 128
     return (
     return (
       <Wrapper>
       <Wrapper>
         {providers.map(p => wrap(p, height))}
         {providers.map(p => wrap(p, height))}
@@ -95,7 +97,7 @@ storiesOf('EndpointLogos', module)
     )
     )
   })
   })
   .add('128px - disabled', () => {
   .add('128px - disabled', () => {
-    let height = 128
+    const height = 128
     return (
     return (
       <Wrapper>
       <Wrapper>
         {providers.map(p => wrap(p, height, true))}
         {providers.map(p => wrap(p, height, true))}

+ 3 - 2
src/components/atoms/EndpointLogos/test.jsx → src/components/atoms/EndpointLogos/test.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { shallow } from 'enzyme'
 import { shallow } from 'enzyme'
 import TestWrapper from '../../../utils/TestWrapper'
 import TestWrapper from '../../../utils/TestWrapper'
@@ -41,3 +39,6 @@ describe('EndpointLogos Component', () => {
     expect(logo.prop('size').h).toBe(64)
     expect(logo.prop('size').h).toBe(64)
   })
   })
 })
 })
+
+
+

+ 0 - 2
src/components/atoms/Fonts/index.js → src/components/atoms/Fonts/index.ts

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import { css } from 'styled-components'
 import { css } from 'styled-components'
 
 
 import RubikRegular from './Rubik-Regular.woff'
 import RubikRegular from './Rubik-Regular.woff'

+ 8 - 13
src/components/atoms/HorizontalLoading/HorizontalLoading.jsx → src/components/atoms/HorizontalLoading/HorizontalLoading.tsx

@@ -12,19 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import styled from 'styled-components'
 import styled from 'styled-components'
 
 
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   position: relative;
   position: relative;
   height: 2px;
   height: 2px;
   overflow: hidden;
   overflow: hidden;
 `
 `
-const Loader = styled.div`
+const Loader = styled.div<any>`
   width: 8px;
   width: 8px;
   height: 2px;
   height: 2px;
   background: ${Palette.primary};
   background: ${Palette.primary};
@@ -39,14 +37,11 @@ type Props = {
   style?: any,
   style?: any,
   'data-test-id'?: string,
   'data-test-id'?: string,
 }
 }
-class HorizontalLoading extends React.Component<Props> {
-  render() {
-    return (
-      <Wrapper style={this.props.style} data-test-id={this.props['data-test-id'] || 'horizontalLoading'}>
-        <Loader />
-      </Wrapper>
-    )
-  }
-}
+
+const HorizontalLoading = (props: Props) => (
+  <Wrapper style={props.style} data-test-id={props['data-test-id'] || 'horizontalLoading'}>
+    <Loader />
+  </Wrapper>
+)
 
 
 export default HorizontalLoading
 export default HorizontalLoading

+ 2 - 1
src/components/atoms/HorizontalLoading/package.json

@@ -2,5 +2,6 @@
   "name": "HorizontalLoading",
   "name": "HorizontalLoading",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main": "./HorizontalLoading.jsx"
+  "main": "./HorizontalLoading.tsx"
 }
 }
+

+ 0 - 2
src/components/atoms/HorizontalLoading/story.jsx → src/components/atoms/HorizontalLoading/story.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import { storiesOf } from '@storybook/react'
 import HorizontalLoading from './HorizontalLoading'
 import HorizontalLoading from './HorizontalLoading'

+ 6 - 8
src/components/atoms/InfoIcon/InfoIcon.jsx → src/components/atoms/InfoIcon/InfoIcon.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import { observer } from 'mobx-react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 import styled from 'styled-components'
@@ -24,17 +22,17 @@ import questionImage from './images/question.svg'
 import warningImage from './images/warning.svg'
 import warningImage from './images/warning.svg'
 import questionFilledImage from './images/question-filled.svg'
 import questionFilledImage from './images/question-filled.svg'
 
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   ${StyleProps.exactSize('16px')}
   ${StyleProps.exactSize('16px')}
-  background: url('${props => props.warning ? warningImage : props.filled ? questionFilledImage : questionImage}') center no-repeat;
+  background: url('${(props: any) => (props.warning ? warningImage : props.filled ? questionFilledImage : questionImage)}') center no-repeat;
   display: inline-block;
   display: inline-block;
-  margin-left: ${props => props.marginLeft != null ? `${props.marginLeft}px` : '4px'};
-  margin-bottom: ${props => props.marginBottom != null ? `${props.marginBottom}px` : '-4px'};
+  margin-left: ${(props: any) => (props.marginLeft != null ? `${props.marginLeft}px` : '4px')};
+  margin-bottom: ${(props: any) => (props.marginBottom != null ? `${props.marginBottom}px` : '-4px')};
 `
 `
 type Props = {
 type Props = {
   text: string,
   text: string,
-  marginLeft?: ?number,
-  marginBottom?: ?number,
+  marginLeft?: number | null,
+  marginBottom?: number | null,
   className?: string,
   className?: string,
   style?: any,
   style?: any,
   warning?: boolean,
   warning?: boolean,

+ 2 - 1
src/components/atoms/InfoIcon/package.json

@@ -2,5 +2,6 @@
   "name": "InfoIcon",
   "name": "InfoIcon",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./InfoIcon.jsx"
+  "main":"./InfoIcon.tsx"
 }
 }
+

+ 2 - 2
src/components/atoms/InfoIcon/story.jsx → src/components/atoms/InfoIcon/story.tsx

@@ -18,8 +18,8 @@ import InfoIcon from '.'
 
 
 storiesOf('InfoIcon', module)
 storiesOf('InfoIcon', module)
   .add('default', () => (
   .add('default', () => (
-    <InfoIcon />
+    <InfoIcon text="Sample" />
   ))
   ))
   .add('warning', () => (
   .add('warning', () => (
-    <InfoIcon warning />
+    <InfoIcon text="Sample" warning />
   ))
   ))

+ 21 - 13
src/components/atoms/Logo/Logo.jsx → src/components/atoms/Logo/Logo.tsx

@@ -12,8 +12,6 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 */
 
 
-// @flow
-
 import React from 'react'
 import React from 'react'
 import styled, { css } from 'styled-components'
 import styled, { css } from 'styled-components'
 import { Link } from 'react-router-dom'
 import { Link } from 'react-router-dom'
@@ -43,13 +41,15 @@ const smallblackProps = css`
 const Wrapper = styled(Link)`
 const Wrapper = styled(Link)`
   transition: all ${StyleProps.animations.swift};
   transition: all ${StyleProps.animations.swift};
 `
 `
-const Coriolis = styled.div`
-  ${props => props.small ? smallProps : props.smallblack ? smallblackProps : largeProps}
-  ${props => !props.large && !props.small && !props.smallblack ? StyleProps.media.handheld`
-    width: 246px;
-    height: 42px;
-    background: url('${coriolisSmallImage}') center no-repeat;
-  ` : ''}
+const Coriolis = styled.div<any>`
+  ${props => (props.small ? smallProps : props.smallblack ? smallblackProps : largeProps)}
+  ${props => (!props.large && !props.small && !props.smallblack ? css`
+    @media (max-height: 760px) {
+      width: 246px;
+      height: 42px;
+      background: url('${coriolisSmallImage}') center no-repeat;
+    }
+  ` : '')}
 `
 `
 
 
 type Props = {
 type Props = {
@@ -63,11 +63,19 @@ type Props = {
 
 
 class Logo extends React.Component<Props> {
 class Logo extends React.Component<Props> {
   render() {
   render() {
-    let to = this.props.to || ''
+    const to = this.props.to || ''
     return (
     return (
-      <div style={{ transition: `all ${StyleProps.animations.swift}` }} className={this.props.className} ref={ref => { this.props.customRef && ref && this.props.customRef(ref) }}>
-        <Wrapper to={to} >
-          <Coriolis large={this.props.large} small={this.props.small} smallblack={this.props.smallblack} />
+      <div
+        style={{ transition: `all ${StyleProps.animations.swift}` }}
+        className={this.props.className}
+        ref={ref => { if (this.props.customRef && ref) this.props.customRef(ref) }}
+      >
+        <Wrapper to={to}>
+          <Coriolis
+            large={this.props.large}
+            small={this.props.small}
+            smallblack={this.props.smallblack}
+          />
         </Wrapper>
         </Wrapper>
       </div>
       </div>
     )
     )

+ 2 - 1
src/components/atoms/Logo/package.json

@@ -2,5 +2,6 @@
   "name": "Logo",
   "name": "Logo",
   "version": "0.0.0",
   "version": "0.0.0",
   "private": true,
   "private": true,
-  "main":"./Logo.jsx"
+  "main":"./Logo.tsx"
 }
 }
+

+ 0 - 0
src/components/atoms/Logo/story.jsx → src/components/atoms/Logo/story.tsx


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác