Procházet zdrojové kódy

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 před 5 roky
rodič
revize
2c6427f7d1
100 změnil soubory, kde provedl 816 přidání a 1024 odebrání
  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
 *.log
 *.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": [
-    "airbnb"
+    "airbnb-typescript"
   ],
   "env": {
     "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": {
-    "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,
       "never"
     ],
-    "comma-dangle": [
+    "arrow-parens": [
       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-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",
       "always"
     ],
-    "flowtype/space-before-generic-bracket": [
-      "error",
-      "never"
-    ],
-    "flowtype/space-before-type-colon": [
+    "no-throw-literal": "off",
+    "@typescript-eslint/type-annotation-spacing": [
       "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
 *.log
 node_modules
-flow-typed/npm/*
-!flow-typed/npm/module_vx.x.x.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

+ 21 - 11
README.md

@@ -6,30 +6,28 @@ Web  GUI for [coriolis](https://github.com/cloudbase/coriolis)
 
 ## 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
 - 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
 
 - 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
 
 - 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
 
-- 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`
 
@@ -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.
 
-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 = {
 
@@ -31,14 +29,16 @@ const conf: Config = {
 
   // - 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 `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.
   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,
    * 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.
    */
   extraOptionsApiCalls: [
@@ -96,11 +96,13 @@ const conf: Config = {
   // The list of the users to hide in the UI
   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
   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,
 
   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",
-  "version": "1.5.1",
+  "version": "2.0.0",
   "license": "AGPL-3.0",
   "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": {
-    "@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",
-    "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": {
+    "@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",
-    "ansi-to-html": "^0.6.12",
+    "ansi-to-html": "^0.6.14",
     "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",
-    "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",
     "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",
-    "mobx": "^3.6.1",
-    "mobx-react": "^4.4.2",
+    "mobx": "^5.15.4",
+    "mobx-react": "^6.2.2",
     "moment": "^2.18.1",
     "moment-timezone": "^0.5.21",
     "ms-rest-azure": "^2.4.5",
     "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-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-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",
     "require-without-cache": "^0.0.6",
     "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="manifest" href="/manifest.json">
   <meta name="theme-color" content="#ffffff">
-  <script src="env.js"></script>
 </head>
 
 <body>
   <main id="app"></main>
 </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/>.
 */
 
-// @flow
-
+import express from 'express'
 import path from 'path'
 import fs from 'fs'
 import requireWithoutCache from 'require-without-cache'
 
-import type { Config, Services } from '../../src/types/Config'
+import type { Services } from '../../src/@types/Config'
 
 const getBaseUrl = () => {
-  let BASE_URL = process.env.CORIOLIS_URL || ''
+  const BASE_URL = process.env.CORIOLIS_URL || ''
   return BASE_URL.trim().replace(/\/$/, '')
 }
 
 const modServicesUrls = (configServices: Services, servicesMod?: Services): Services => {
-  let services: Services = { ...configServices }
+  const services: any = { ...configServices }
+  const localServicesMod: any = servicesMod
   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())
   })
   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) {
       config.servicesUrls = modServicesUrls(config.servicesUrls)
       res.send(config)
       return
     }
     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 => {
         if (key !== 'servicesUrls') {
           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/>.
 */
 
-// @flow
-
+import express from 'express'
 import path from 'path'
 import fs from 'fs'
 
 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) {
     throw new Error()
   }
@@ -29,33 +28,33 @@ const getModJsonProviders = (jsonPath: string) => {
 const getOptimalLogoHeightKey = (
   availableHeightKeys: string[],
   requestedHeight: number,
-  style: ?string
+  style: string | null,
 ): string => {
   let heightKeys = availableHeightKeys
   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) {
       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
     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
 }
 
-export default (router: express$Router) => {
-  // $FlowIgnore
+export default (router: express.Router) => {
   router.get('/logos/:provider/:size/:style?', (req, res) => {
     const SIZES = [32, 42, 64, 128]
     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) {
       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(', ')}` } })
       return
     }
-    let logoBase = path.join(__dirname, '/resources/providerLogos')
+    const logoBase = path.join(__dirname, '/resources/providerLogos')
     let logoPath = `${logoBase}/${provider}-${size}`
     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) {
       res.sendFile(logoPath)
       return
     }
 
     try {
-      let providersJson = getModJsonProviders(modJsonPath)
-      let providerJson = providersJson[provider]
+      const providersJson = getModJsonProviders(modJsonPath)
+      const providerJson = providersJson[provider]
       if (!providerJson) {
         res.sendFile(logoPath)
         return
       }
-      let providerLogosJson = providerJson.logos
+      const providerLogosJson = providerJson.logos
       if (!providerLogosJson) {
         console.log(`No logos specified in MOD_JSON file for '${provider}' provider`)
         res.sendFile(logoPath)
         return
       }
-      let providerLogosKeys = Object.keys(providerLogosJson)
+      const providerLogosKeys = Object.keys(providerLogosJson)
       if (!providerLogosKeys.length) {
         console.log(`No logo heights specified in MOD_JSON file for '${provider}' provider`)
         res.sendFile(logoPath)
         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) {
         console.log(`No logo path specified in MOD_JSON file for '${provider}' provider`)
         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/>.
 */
 
-// @flow
-
 import express from 'express'
 import bodyParser from 'body-parser'
 
@@ -26,8 +24,7 @@ const router = express.Router()
 
 router.use(bodyParser.json())
 
-// $FlowIgnore
-router.get('/version', (req, res) => {
+router.get('/version', (_, res) => {
   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/>.
 */
 
+import express from 'express'
+
 import MsRest from 'ms-rest-azure'
 import bodyParser from 'body-parser'
 import axios from 'axios'
 
 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()
 
   app.post('/azure-login', jsonParser, (req, res) => {
-    let handleResponse = (err, credentials) => {
+    const handleResponse = (err: any, credentials: any) => {
       if (err) {
         console.log(err)
         res.status(401).send(buildError('Azure API authentication error'))
@@ -36,13 +36,15 @@ module.exports = app => {
         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) {
       MsRest.loginWithUsernamePassword(userCred.username, userCred.password, handleResponse)
     } 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 {
       res.status(401).send(buildError('Azure API authentication error'))
     }
@@ -50,8 +52,8 @@ module.exports = app => {
 
   app.get('/proxy/*', (req, res) => {
     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 => {
       if (req.headers[headerName] != null) {
         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/>.
 */
 
-// @flow
-
 import type { Endpoint } from './Endpoint'
 import type { Instance } from './Instance'
 import type { NetworkMap } from './Network'
@@ -23,7 +21,7 @@ export type VmSize = {
   size?: string,
 }
 
-export type Location = {
+export type AzureLocation = {
   id: string,
   name: string,
 }
@@ -38,7 +36,7 @@ export type VmItem = {
   properties: {
     recommendedSize: string,
     disks: {
-      [string]: {
+      [diskName: string]: {
         recommendedDiskType: string,
       },
     },
@@ -57,9 +55,6 @@ export type Assessment = {
   groupName: string,
   assessmentName: string,
   location: string,
-  properties: {
-    azureLocation: string,
-  },
   project: {
     name: string,
   },
@@ -70,14 +65,14 @@ export type Assessment = {
     azureLocation: string,
     numberOfMachines: string,
   },
-  connectionInfo: { subscription_id: string } & $PropertyType<Endpoint, 'connection_info'>,
+  connectionInfo: { subscription_id: string } & Endpoint['connection_info'],
 }
 
 export type MigrationInfo = {
-  source: ?Endpoint,
+  source: Endpoint | null,
   target: Endpoint,
   selectedInstances: Instance[],
-  fieldValues: { [string]: any },
+  fieldValues: { [fieldValue: string]: any },
   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 = {
   [key: string]: {
     data: any,

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

@@ -1,5 +1,3 @@
-// @flow
-
 type Type = 'source' | 'destination'
 
 type ExtraOption = {
@@ -28,7 +26,7 @@ export type Config = {
   showOpenstackCurrentUserSwitch: boolean,
   useBarbicanSecrets: boolean,
   requestPollTimeout: number,
-  instancesListBackgroundLoading: { default: number, [string]: number },
+  instancesListBackgroundLoading: { default: number, [prop: string]: number },
   extraOptionsApiCalls: ExtraOption[],
   providerSortPriority: { [providerName: string]: number },
   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/>.
 */
 
-// @flow
-
 import type { Disk } from './Instance'
+import type { ProviderTypes } from './Providers'
 
 export type Validation = {
   valid: boolean,
@@ -25,13 +24,14 @@ export type Endpoint = {
   id: string,
   name: string,
   description: string,
-  type: string,
+  type: ProviderTypes,
   created_at: Date,
   connection_info: {
     secret_ref?: string,
     host?: string,
-    [string]: mixed
+    [prop: string]: any
   },
+  [prop: string]: any
 }
 
 export type MultiValidationItem = {
@@ -42,8 +42,7 @@ export type MultiValidationItem = {
 
 export type OptionValues = {
   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 },
 }
 

+ 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/>.
 */
 
-// @flow
-
 import type { Task } from './Task'
 
 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/>.
 */
 
-// @flow
-
 import { OptionsSchemaPlugin } from '../plugins/endpoint'
 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 = {
   name: string,
   type?: string,
   value?: any,
   label?: string,
-  // $FlowIssue
-  enum?: string[] | { id: string, name: string, [string]: mixed }[],
+  enum?: EnumItem[],
   default?: any,
   password?: boolean,
   nullableBoolean?: boolean,
@@ -45,8 +50,13 @@ export type Field = {
 const migrationImageOsTypes = ['windows', 'linux']
 
 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) {
       return 'Yes'
@@ -54,7 +64,7 @@ class FieldHelper {
     if (value === false) {
       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)
     if (!field) {
       fields.forEach(f => {
@@ -74,10 +84,10 @@ class FieldHelper {
         }
       })
     }
-    let findInEnum = (v: any) => {
+    const findInEnum = (v: any) => {
       let valueName = v
       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) {
           valueName = enumObject.name
         } else if (field && LabelDictionary.enumFields.find(f => field && f === field.name)) {
@@ -87,23 +97,25 @@ class FieldHelper {
       return valueName
     }
     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) {
-      let migrImageField = plugin && fields.find(f => f.name === plugin.migrationImageMapFieldName)
+      const migrImageField = plugin && fields
+        .find(f => f.name === plugin.migrationImageMapFieldName)
       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) {
-          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) {
             return imageFieldValueObject.name
           }
         }
       }
     }
-    // $FlowIssue
+
     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/>.
 */
 
-// @flow
-
 export type Nic = {
   id: string,
   network_name: string,
@@ -39,7 +37,7 @@ export type Instance = {
   id: string,
   name: string,
   flavor_name: string,
-  instance_name?: ?string,
+  instance_name?: string | null,
   num_cpu: number,
   memory_mb: number,
   os_type: string,
@@ -50,8 +48,8 @@ export type Instance = {
 }
 
 export type InstanceScript = {
-  global?: ?string,
-  instanceName?: ?string,
+  global?: string | null,
+  instanceName?: string | null,
   scriptContent: 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/>.
 */
 
-// @flow
-
 export type Licence = {
   currentPeriodStart: 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/>.
 */
 
-// @flow
-
 export type Log = {
   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/>.
 */
 
-// @flow
-
 import type { Execution } from './Execution'
 import type { Task } from './Task'
 import type { Instance } from './Instance'
@@ -36,7 +34,27 @@ export type UpdateData = {
   network: NetworkMap[],
   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 = {
   id: string,
   executions: Execution[],
@@ -51,26 +69,12 @@ export type MainItem = {
   destination_endpoint_id: string,
   instances: string[],
   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,
-  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/>.
 */
 
-// @flow
-
 import type { Nic } from './Instance'
 
 export type SecurityGroup = string | {
@@ -30,5 +28,5 @@ export type Network = {
 export type NetworkMap = {
   sourceNic: Nic,
   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/>.
 */
 
-// @flow
-
 export type AlertInfoLevel = 'success' | 'error' | 'info'
 
 export type AlertInfoOptions = {
@@ -24,7 +22,7 @@ export type AlertInfoOptions = {
 }
 
 export type AlertInfo = {
-  options?: ?AlertInfoOptions,
+  options?: AlertInfoOptions | null,
   message: string,
   title?: 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/>.
 */
 
-// @flow
-
 export type Project = {
   id: 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/>.
 */
 
-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/>.
 */
 
-// @flow
+export type ScheduleFieldName = 'hour' | 'minute' | 'month' | 'dow' | 'dom'
 
 export type ScheduleInfo = {
   hour?: number,
@@ -24,7 +24,7 @@ export type ScheduleInfo = {
 
 export type Schedule = {
   id?: string,
-  enabled?: ?boolean,
+  enabled?: boolean | null,
   schedule?: ScheduleInfo,
   expiration_date?: Date,
   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/>.
 */
 
-// @flow
-
 // export type Schema = {
 //   properties: {
-//     [string]: {
+//     [prop: string]: {
 //       name: string,
 //     }
 //   },
@@ -26,7 +24,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 export type SchemaProperties = {
   properties: {
-    [string]: {
+    [prop: string]: {
       type: 'array',
       items: {
         type: string,
@@ -44,7 +42,7 @@ export type SchemaProperties = {
 }
 
 export type SchemaDefinitions = {
-  [string]: SchemaProperties,
+  [prop: string]: SchemaProperties,
 }
 
 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/>.
 */
 
-// @flow
-
 export type ProgressUpdate = {
   message: string,
   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/>.
 */
 
-// @flow
-
 import type { Project } from './Project'
 
 export type User = {
@@ -26,9 +24,10 @@ export type User = {
   enabled?: boolean,
   project_id?: string,
   domain_id?: string,
-  isAdmin?: ?boolean,
+  isAdmin?: boolean | null,
   password?: string,
   extra?: any,
+  token?: string
 }
 
 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/>.
 */
 
-// @flow
-
 import type { Instance } from './Instance'
 import type { NetworkMap } from './Network'
 import type { Endpoint } from './Endpoint'
 
 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 = {

+ 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/>.
 */
 
-// @flow
-
+import { hot } from 'react-hot-loader/root'
 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 Fonts from './atoms/Fonts'
@@ -48,8 +47,8 @@ import Palette from './styleUtils/Palette'
 import StyleProps from './styleUtils/StyleProps'
 import configLoader from '../utils/Config'
 
-injectGlobal`
-  ${Fonts}
+const GlobalStyle = createGlobalStyle`
+ ${Fonts}
   html, body, main {
     height: 100%;
     display: flex;
@@ -66,7 +65,8 @@ injectGlobal`
     -moz-osx-font-smoothing: grayscale;
   }
 `
-const Wrapper = styled.div`
+
+const Wrapper = styled.div<any>`
   height: 100%;
   min-height: 0;
   display: flex;
@@ -84,10 +84,13 @@ class App extends React.Component<{}, State> {
   state = {
     isConfigReady: false,
   }
+
   awaitingRefresh: boolean = false
 
-  async componentWillMount() {
-    observe(userStore, 'loggedUser', () => { this.setState({}) })
+  async componentDidMount() {
+    observe(userStore, 'loggedUser', () => {
+      this.setState({})
+    })
     await configLoader.load()
     userStore.tokenLogin()
     this.setState({ isConfigReady: true })
@@ -105,20 +108,22 @@ class App extends React.Component<{}, State> {
       subtitle: string,
       showAuthAnimation?: 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) {
         return renderMessagePage({
           path,
@@ -131,12 +136,12 @@ class App extends React.Component<{}, State> {
       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)) {
         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) {
         return renderRoute(actualPath, component, exact)
       }
@@ -166,34 +171,37 @@ class App extends React.Component<{}, State> {
 
     return (
       <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 />
         <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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
@@ -21,22 +19,22 @@ import styled from 'styled-components'
 import Palette from '../../styleUtils/Palette'
 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 === 'up' ? 'transform: rotate(-90deg);' : ''}
   ${props.orientation === 'down' ? 'transform: rotate(90deg);' : ''}
 `
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   width: 16px;
   height: 16px;
   display: flex;
   justify-content: center;
   align-items: center;
-  cursor: ${props => props.useDefaultCursor || props.disabled ? 'default' : 'pointer'};
+  cursor: ${props => (props.useDefaultCursor || props.disabled ? 'default' : 'pointer')};
   opacity: ${props => props.opacity};
   transition: all ${StyleProps.animations.swift};
   ${props => getOrientation(props)}
@@ -65,6 +63,7 @@ class Arrow extends React.Component<Props> {
     color = this.props.disabled ? Palette.grayscale[0] : color
     return (
       <Wrapper
+        // eslint-disable-next-line react/jsx-props-no-spreading
         {...this.props}
         dangerouslySetInnerHTML={
           { __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">
     <!-- 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/>.
 */
 
-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"
 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 -->

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

@@ -2,5 +2,6 @@
   "name": "Arrow",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import * as React from 'react'
 import styled, { css } from 'styled-components'
 
@@ -23,7 +21,7 @@ import TextInput from '../TextInput'
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.width) {
     return props.width - 2
   }
@@ -35,32 +33,32 @@ const getWidth = props => {
   return StyleProps.inputSizes.regular.width - 2
 }
 
-const getBorder = props => {
+const getBorder = (props: any) => {
   if (props.embedded) {
     return css`border: none;`
   }
   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;
   align-items: center;
   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)}
   border-radius: ${StyleProps.borderRadius};
-  cursor: ${props => props.disabled ? 'default' : 'pointer'};
+  cursor: ${props => (props.disabled ? 'default' : 'pointer')};
   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;
   top: 8px;
   right: 8px;
@@ -73,7 +71,7 @@ const Arrow = styled.div`
 type Props = {
   value: string,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   onChange: (value: string) => void,
   disabled?: boolean,
   disabledLoading?: boolean,
@@ -81,7 +79,7 @@ type Props = {
   large?: boolean,
   onFocus?: () => void,
   onBlur?: () => void,
-  onInputKeyDown?: (e: SyntheticKeyboardEvent<HTMLInputElement>) => void,
+  onInputKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
   highlight?: boolean,
   embedded?: boolean,
 }
@@ -89,14 +87,14 @@ type State = {
   textInputFocus: boolean,
 }
 class AutocompleteInput extends React.Component<Props, State> {
-  state = {
+  state: State = {
     textInputFocus: false,
   }
 
-  textInputRef: HTMLElement
+  textInputRef: HTMLElement | undefined
 
   render() {
-    let disabled = this.props.disabled || this.props.disabledLoading
+    const disabled = this.props.disabled || this.props.disabledLoading
     return (
       <Wrapper
         large={this.props.large}
@@ -106,11 +104,11 @@ class AutocompleteInput extends React.Component<Props, State> {
         disabled={disabled}
         disabledLoading={this.props.disabledLoading}
         embedded={this.props.embedded}
-        innerRef={e => {
+        ref={(e: HTMLElement) => {
           if (this.props.customRef) {
             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"
           lineHeight="30px"
           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}
         />
         <Arrow
           data-test-id="acInput-arrow"
           disabled={disabled}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
-          onClick={() => { this.textInputRef.focus() }}
+          onClick={() => { if (this.textInputRef) this.textInputRef.focus() }}
         />
       </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",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 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/>.
 */
 
-// @flow
-
 import React from 'react'
 import sinon from 'sinon'
 import { shallow } from 'enzyme'
@@ -23,7 +21,7 @@ import AutocompleteInput from '.'
 type Props = {
   value: string,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   onChange: (value: string) => void,
   onClick?: () => void,
   disabled?: boolean,
@@ -34,12 +32,13 @@ type Props = {
 }
 
 const wrap = (props: Props) => new TW(shallow(
-  <AutocompleteInput {...props} />
+  // eslint-disable-next-line react/jsx-props-no-spreading
+  <AutocompleteInput {...props} />,
 ), 'acInput')
 
 describe('AutocompleteInput Component', () => {
   it('renders input with correct data', () => {
-    let wrapper = wrap({
+    const wrapper = wrap({
       value: 'value',
       onChange: () => { },
     })
@@ -49,8 +48,8 @@ describe('AutocompleteInput Component', () => {
   })
 
   it('dispatches click', () => {
-    let onClick = sinon.spy()
-    let wrapper = wrap({
+    const onClick = sinon.spy()
+    const wrapper = wrap({
       value: 'value',
       onChange: () => { },
       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/>.
 */
 
-// @flow
-
 import React from 'react'
 import styled from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
-const backgroundColor = (props) => {
+const backgroundColor = (props: any) => {
   if (props.hollow) {
     if (props.transparent) {
       return 'transparent'
@@ -35,14 +33,14 @@ const backgroundColor = (props) => {
   }
   return Palette.primary
 }
-const disabledBackgroundColor = props => {
+const disabledBackgroundColor = (props: any) => {
   if (props.secondary && props.hollow) {
     return Palette.grayscale[7]
   }
   return backgroundColor(props)
 }
 
-const hoverBackgroundColor = (props) => {
+const hoverBackgroundColor = (props: any) => {
   if (props.disabled && props.secondary && props.hollow) {
     return Palette.grayscale[7]
   }
@@ -61,7 +59,7 @@ const hoverBackgroundColor = (props) => {
   return Palette.primary
 }
 
-const border = (props) => {
+const border = (props: any) => {
   if (props.hollow) {
     if (props.secondary) {
       return `border: 1px solid ${Palette.grayscale[3]};`
@@ -73,14 +71,14 @@ const border = (props) => {
   }
   return ''
 }
-const disabledBorder = props => {
+const disabledBorder = (props: any) => {
   if (props.secondary && props.hollow) {
     return 'border: none;'
   }
   return border(props)
 }
 
-const color = (props) => {
+const color = (props: any) => {
   if (props.hollow) {
     if (props.secondary) {
       return props.disabled ? Palette.grayscale[3] : Palette.black
@@ -92,7 +90,7 @@ const color = (props) => {
   }
   return 'white'
 }
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.width) {
     return props.width
   }
@@ -116,13 +114,13 @@ const StyledButton = styled.button`
   font-size: inherit;
   transition: background-color ${StyleProps.animations.swift}, opacity ${StyleProps.animations.swift};
   &:disabled {
-    opacity: ${props => props.secondary ? 1 : 0.7};
+    opacity: ${(props: any) => (props.secondary ? 1 : 0.7)};
     cursor: not-allowed;
     background-color: ${props => disabledBackgroundColor(props)};
     ${props => disabledBorder(props)}
   }
   &: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)};
   }
   &: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

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

@@ -2,5 +2,6 @@
   "name": "Button",
   "version": "0.0.0",
   "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 { storiesOf, action } from '@storybook/react'
+import { storiesOf } from '@storybook/react'
 import Button from '.'
 
 storiesOf('Button', module)
   .add('primary', () => (
-    <Button onClick={action('clicked')}>Hello</Button>
+    <Button>Hello</Button>
   ))
   .add('secondary', () => (
-    <Button secondary onClick={action('clicked')}>Hello</Button>
+    <Button secondary>Hello</Button>
   ))
   .add('alert', () => (
-    <Button alert onClick={action('clicked')}>Hello</Button>
+    <Button alert>Hello</Button>
   ))
   .add('hollow primary', () => (
-    <Button hollow onClick={action('clicked')}>Hello</Button>
+    <Button hollow>Hello</Button>
   ))
   .add('hollow secondary', () => (
-    <Button hollow secondary onClick={action('clicked')}>Hello</Button>
+    <Button hollow secondary>Hello</Button>
   ))
   .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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
@@ -40,3 +38,6 @@ describe('Button Component', () => {
     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/>.
 */
 
-// @flow
-
 import * as React from 'react'
 import { observer } from 'mobx-react'
 import styled, { css } from 'styled-components'
@@ -23,17 +21,17 @@ import StyleProps from '../../styleUtils/StyleProps'
 
 import checkmarkImage from './images/checkmark.svg'
 
-const CheckmarkImage = styled.div`
+const CheckmarkImage = styled.div<any>`
   width: 10px;
   height: 7px;
   background: url('${checkmarkImage}') no-repeat center;
   transform: scale(0);
   transition: transform 250ms cubic-bezier(0, 1.4, 1, 1);
 `
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   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;
   align-items: center;
   ${StyleProps.exactSize('16px')}
@@ -41,13 +39,13 @@ const Wrapper = styled.div`
   border-radius: 3px;
   background: white;
   transition: all ${StyleProps.animations.swift};
-  ${props => props.checked ? css`
+  ${(props: any) => (props.checked ? css`
     border-color: ${Palette.primary};
     background: ${Palette.primary};
     ${CheckmarkImage} {
       transform: scale(1);
     }
-  ` : ''}
+  ` : '')}
   :focus {
     border: 1px solid ${Palette.primary};
     outline: none;
@@ -60,8 +58,8 @@ type Props = {
   disabled?: boolean,
   onChange?: (checked: boolean) => void,
   '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
 class Checkbox extends React.Component<Props> {
@@ -73,7 +71,7 @@ class Checkbox extends React.Component<Props> {
     this.props.onChange(!this.props.checked)
   }
 
-  handleKeyDown(e: SyntheticKeyboardEvent<HTMLDivElement>) {
+  handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
     if (e.key !== ' ') {
       return
     }
@@ -90,7 +88,7 @@ class Checkbox extends React.Component<Props> {
         checked={this.props.checked}
         disabled={this.props.disabled}
         tabIndex={0}
-        onKeyDown={e => { this.handleKeyDown(e) }}
+        onKeyDown={(e: React.KeyboardEvent<HTMLDivElement>) => { this.handleKeyDown(e) }}
         onMouseDown={this.props.onMouseDown}
         onMouseUp={this.props.onMouseUp}
       >

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

@@ -2,5 +2,6 @@
   "name": "Checkbox",
   "version": "0.0.0",
   "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 '.'
 
 class Wrapper extends React.Component {
-  constructor() {
-    super()
-    this.state = { checked: false }
-  }
+  state = { checked: false }
 
-  handleChange(checked) {
+  handleChange(checked: boolean) {
     this.setState({ checked })
   }
 
   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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
@@ -41,3 +39,6 @@ describe('Checkbox Component', () => {
     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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
@@ -36,6 +34,7 @@ const Wrapper = styled.span`
 class CopyButton extends React.Component<{}> {
   render() {
     return (
+      // eslint-disable-next-line react/jsx-props-no-spreading
       <Wrapper {...this.props} />
     )
   }

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

@@ -2,5 +2,6 @@
   "name": "CopyButton",
   "version": "0.0.0",
   "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 '.'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import CopyButton from '.'
@@ -27,3 +25,6 @@ describe('CopyButton Component', () => {
     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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
@@ -26,7 +24,7 @@ const CopyButtonStyled = styled(CopyButton)`
   background-position-y: 4px;
   margin-left: 4px;
 `
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   word-break: break-word;
 
@@ -37,19 +35,22 @@ const Wrapper = styled.div`
 
 type Props = {
   'data-test-id'?: string,
-  value: string,
+  value: string | null | undefined,
   onCopy?: (value: string) => void,
   useDangerousHtml?: boolean,
 }
 @observer
 class CopyMultineValue extends React.Component<Props> {
   handleCopy() {
-    let value = this.props.value
+    let { value } = this.props
+    if (!value) {
+      return
+    }
     if (this.props.useDangerousHtml) {
       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 (succesful) {
@@ -58,9 +59,9 @@ class CopyMultineValue extends React.Component<Props> {
   }
 
   render() {
-    let text = this.props.value
+    let text: React.ReactNode = this.props.value
     if (this.props.useDangerousHtml) {
-      text = <span dangerouslySetInnerHTML={{ __html: text }} />
+      text = <span dangerouslySetInnerHTML={{ __html: text as string }} />
     }
 
     return (

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

@@ -2,5 +2,6 @@
   "name": "CopyMultilineValue",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
@@ -35,3 +33,6 @@ describe('CopyMultilineValue Component', () => {
     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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
@@ -22,16 +20,16 @@ import CopyButton from '../CopyButton'
 import DomUtils from '../../../utils/DomUtils'
 import notificationStore from '../../../stores/NotificationStore'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   cursor: pointer;
   display: flex;
   &:hover > span:last-child {
     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;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -48,10 +46,10 @@ type Props = {
 }
 @observer
 class CopyValue extends React.Component<Props> {
-  handleCopyIdClick(e: Event) {
+  handleCopyIdClick(e: React.MouseEvent<HTMLDivElement>) {
     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 (succesful) {
@@ -64,16 +62,17 @@ class CopyValue extends React.Component<Props> {
   render() {
     return (
       <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'}
       >
         <Value
           data-test-id="copyValue-value"
           width={this.props.width}
           maxWidth={this.props.maxWidth}
-        >{this.props.value}</Value>
+        >{this.props.value}
+        </Value>
         <CopyButton />
       </Wrapper>
     )

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

@@ -2,5 +2,6 @@
   "name": "CopyValue",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
@@ -36,3 +34,6 @@ describe('CopyValue Component', () => {
     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/>.
 */
 
-// @flow
-
 import React from 'react'
 import styled, { css } from 'styled-components'
 
-import arrowImage from './images/arrow.js'
+import arrowImage from './images/arrow'
 
 import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
-const getLabelColor = props => {
+const getLabelColor = (props: any) => {
   if (props.disabled) {
     return Palette.grayscale[3]
   }
@@ -33,18 +31,18 @@ const getLabelColor = props => {
 
   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;
   text-overflow: ellipsis;
   white-space: nowrap;
   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) {
     return 'white'
   }
@@ -63,7 +61,7 @@ const getBackgroundColor = props => {
 
   return 'white'
 }
-const getArrowColor = props => {
+const getArrowColor = (props: any) => {
   if (props.disabled) {
     return Palette.grayscale[3]
   }
@@ -78,7 +76,7 @@ const getArrowColor = props => {
 
   return Palette.black
 }
-const getWidth = props => {
+const getWidth = (props: any) => {
   if (props.large) {
     return StyleProps.inputSizes.large.width - 2
   }
@@ -87,7 +85,7 @@ const getWidth = props => {
   }
   return StyleProps.inputSizes.regular.width - 2
 }
-const borderColor = props => {
+const borderColor = (props: any) => {
   if (props.highlight) {
     return Palette.alert
   }
@@ -105,7 +103,7 @@ const borderColor = props => {
   }
   return Palette.grayscale[3]
 }
-const backgroundHover = props => {
+const backgroundHover = (props: any) => {
   if (props.disabled || props.embedded) {
     return ''
   }
@@ -115,35 +113,35 @@ const backgroundHover = props => {
   return Palette.primary
 }
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   align-items: center;
   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-radius: ${StyleProps.borderRadius};
-  cursor: ${props => props.disabled ? 'default' : 'pointer'};
+  cursor: ${(props: any) => (props.disabled ? 'default' : 'pointer')};
   transition: all ${StyleProps.animations.swift};
   background: ${props => getBackgroundColor(props)};
-  ${props => props.embedded ? css`
+  ${(props: any) => (props.embedded ? css`
     border: 0;
     width: calc(100% + 8px);
-  ` : ''}
+  ` : '')}
 
-  #dropdown-arrow-image {stroke: ${props => getArrowColor(props)};}
+  #dropdown-arrow-image {stroke: ${(props: any) => getArrowColor(props)};}
   &: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} {
-    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;
   right: 8px;
   top: 8px;
@@ -157,7 +155,7 @@ type Props = {
   value: string,
   onClick?: (event: Event) => void,
   customRef?: (ref: HTMLElement) => void,
-  innerRef?: (ref: HTMLElement) => void,
+  ref?: (ref: HTMLElement) => void,
   arrowRef?: (ref: HTMLElement) => void,
   className?: string,
   disabled?: boolean,
@@ -168,39 +166,47 @@ type Props = {
   secondary?: boolean,
   centered?: boolean,
   outline?: boolean,
+  primary?: boolean,
+  width?: number,
+  useBold?: boolean,
+  onMouseDown?: () => void,
+  onMouseUp?: () => void,
 }
 class DropdownButton extends React.Component<Props> {
   render() {
-    let disabled = this.props.disabled || this.props.disabledLoading
+    const disabled = this.props.disabled || this.props.disabledLoading
     return (
       <Wrapper
         data-test-id={this.props['data-test-id'] || 'dropdownButton'}
+        // eslint-disable-next-line react/jsx-props-no-spreading
         {...this.props}
         disabled={disabled}
         disabledLoading={this.props.disabledLoading}
-        innerRef={e => {
+        ref={(e: HTMLElement) => {
           if (this.props.customRef) {
             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)
         }}
       >
         <Label
+          // eslint-disable-next-line react/jsx-props-no-spreading
           {...this.props}
           onClick={() => { }}
-          innerRef={() => { }}
+          ref={() => { }}
           data-test-id="dropdownButton-value"
           disabled={disabled}
         >
           {this.props.value}
         </Label>
         <Arrow
+          // eslint-disable-next-line react/jsx-props-no-spreading
           {...this.props}
-          innerRef={ref => { if (this.props.arrowRef) this.props.arrowRef(ref) }}
+          ref={(ref: HTMLElement) => { if (this.props.arrowRef) this.props.arrowRef(ref) }}
           onClick={() => { }}
           data-test-id=""
           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",
   "version": "0.0.0",
   "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 { storiesOf, action } from '@storybook/react'
+import { storiesOf } from '@storybook/react'
 import DropdownButton from '.'
 
 storiesOf('DropdownButton', module)
   .add('default', () => (
-    <DropdownButton value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton value="Dropdown Button" />
   ))
   .add('primary', () => (
-    <DropdownButton primary value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton primary value="Dropdown Button" />
   ))
   .add('disabled', () => (
-    <DropdownButton disabled value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton disabled value="Dropdown Button" />
   ))
   .add('disabled loading', () => (
-    <DropdownButton disabledLoading value="Dropdown Button" onClick={action('clicked')} />
+    <DropdownButton disabledLoading value="Dropdown Button" />
   ))
   .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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import sinon from 'sinon'
@@ -42,3 +40,6 @@ describe('DropdownButton Component', () => {
     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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled, { css } from 'styled-components'
 
 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;
   align-items: center;
   justify-content: center;
   width: ${props => props.width}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;
 `
 const widthHeights = [
@@ -40,12 +38,14 @@ const PROVIDER_LOGOS = [
   'azure', 'openstack', 'opc', 'oracle_vm', 'vmware_vsphere', 'aws', 'oci', 'hyper-v', 'scvmm',
 ]
 type Props = {
-  endpoint?: ?string,
+  endpoint?: string | null,
   height: number,
   disabled?: boolean,
   white?: boolean,
   baseUrl?: string,
+  onClick?: () => void
   'data-test-id'?: string,
+  style?: React.CSSProperties
 }
 @observer
 class EndpointLogos extends React.Component<Props> {
@@ -66,21 +66,22 @@ class EndpointLogos extends React.Component<Props> {
   }
 
   render() {
-    let size = widthHeights.find(wh => wh.h === this.props.height)
+    const size = widthHeights.find(wh => wh.h === this.props.height)
 
     if (!size) {
       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) {
       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
     }
 
     return (
+      // eslint-disable-next-line react/jsx-props-no-spreading
       <Wrapper {...this.props} data-test-id={this.props['data-test-id'] || 'endpointLogos'}>
         <Logo
           width={size.w}

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

@@ -2,5 +2,6 @@
   "name": "EndpointLogos",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import React from 'react'
 import styled from 'styled-components'
 
@@ -23,7 +21,7 @@ import generic64Image from './generic-64.svg'
 import generic128Image from './generic-128.svg'
 import generic128DisabledImage from './generic-128-disabled.svg'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   text-align: center;
   max-height: 100%;
   overflow: hidden;
@@ -32,20 +30,20 @@ const Wrapper = styled.div`
   align-items: center;
   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 = {
   name: string,
   size: { w: number, h: number },
-  disabled: ?boolean,
-  white: ?boolean,
+  disabled: boolean | null | undefined,
+  white: boolean | null | undefined,
 }
 class Generic extends React.Component<Props> {
-  render32Generic(white: ?boolean) {
+  render32Generic(white: boolean | null | undefined) {
     return (
       <Wrapper style={{
         fontSize: '14px',
@@ -83,7 +81,7 @@ class Generic extends React.Component<Props> {
     )
   }
 
-  render128Generic(disabled: ?boolean) {
+  render128Generic(disabled: boolean | null | undefined) {
     return (
       <Wrapper style={{
         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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import styled from 'styled-components'
 import EndpointLogos from '.'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   display: flex;
   flex-wrap: wrap;
   margin-left: -32px;
   margin-top: -32px;
-  ${props => props.background ? `background: ${props.background};` : ''}
+  ${(props: any) => (props.background ? `background: ${props.background};` : '')}
 
   > div {
     margin-left: 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
     endpoint={endpoint}
     height={height}
@@ -40,7 +42,7 @@ const wrap = (endpoint, height, disabled = false, white = false) => (
     baseUrl="http://localhost:3000"
   />
 )
-let providers = [
+const providers = [
   'aws',
   'azure',
   'opc',
@@ -55,7 +57,7 @@ let providers = [
 
 storiesOf('EndpointLogos', module)
   .add('32px', () => {
-    let height = 32
+    const height = 32
     return (
       <Wrapper>
         {providers.map(p => wrap(p, height))}
@@ -63,7 +65,7 @@ storiesOf('EndpointLogos', module)
     )
   })
   .add('32px - white', () => {
-    let height = 32
+    const height = 32
     return (
       <Wrapper background="#202134">
         {providers.map(p => wrap(p, height, false, true))}
@@ -71,7 +73,7 @@ storiesOf('EndpointLogos', module)
     )
   })
   .add('42px', () => {
-    let height = 42
+    const height = 42
     return (
       <Wrapper>
         {providers.map(p => wrap(p, height))}
@@ -79,7 +81,7 @@ storiesOf('EndpointLogos', module)
     )
   })
   .add('64px', () => {
-    let height = 64
+    const height = 64
     return (
       <Wrapper>
         {providers.map(p => wrap(p, height))}
@@ -87,7 +89,7 @@ storiesOf('EndpointLogos', module)
     )
   })
   .add('128px', () => {
-    let height = 128
+    const height = 128
     return (
       <Wrapper>
         {providers.map(p => wrap(p, height))}
@@ -95,7 +97,7 @@ storiesOf('EndpointLogos', module)
     )
   })
   .add('128px - disabled', () => {
-    let height = 128
+    const height = 128
     return (
       <Wrapper>
         {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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { shallow } from 'enzyme'
 import TestWrapper from '../../../utils/TestWrapper'
@@ -41,3 +39,6 @@ describe('EndpointLogos Component', () => {
     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/>.
 */
 
-// @flow
-
 import { css } from 'styled-components'
 
 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/>.
 */
 
-// @flow
-
 import React from 'react'
 import styled from 'styled-components'
 
 import Palette from '../../styleUtils/Palette'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   position: relative;
   height: 2px;
   overflow: hidden;
 `
-const Loader = styled.div`
+const Loader = styled.div<any>`
   width: 8px;
   height: 2px;
   background: ${Palette.primary};
@@ -39,14 +37,11 @@ type Props = {
   style?: any,
   '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

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

@@ -2,5 +2,6 @@
   "name": "HorizontalLoading",
   "version": "0.0.0",
   "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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 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/>.
 */
 
-// @flow
-
 import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
@@ -24,17 +22,17 @@ import questionImage from './images/question.svg'
 import warningImage from './images/warning.svg'
 import questionFilledImage from './images/question-filled.svg'
 
-const Wrapper = styled.div`
+const Wrapper = styled.div<any>`
   ${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;
-  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 = {
   text: string,
-  marginLeft?: ?number,
-  marginBottom?: ?number,
+  marginLeft?: number | null,
+  marginBottom?: number | null,
   className?: string,
   style?: any,
   warning?: boolean,

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

@@ -2,5 +2,6 @@
   "name": "InfoIcon",
   "version": "0.0.0",
   "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)
   .add('default', () => (
-    <InfoIcon />
+    <InfoIcon text="Sample" />
   ))
   .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/>.
 */
 
-// @flow
-
 import React from 'react'
 import styled, { css } from 'styled-components'
 import { Link } from 'react-router-dom'
@@ -43,13 +41,15 @@ const smallblackProps = css`
 const Wrapper = styled(Link)`
   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 = {
@@ -63,11 +63,19 @@ type Props = {
 
 class Logo extends React.Component<Props> {
   render() {
-    let to = this.props.to || ''
+    const to = this.props.to || ''
     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>
       </div>
     )

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

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

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


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů