From 7fa40e472eb6a5c7b340c482e55499b3e739b001 Mon Sep 17 00:00:00 2001 From: HenningThiemann <32903255+HenningThiemann@users.noreply.github.com> Date: Thu, 26 Mar 2020 08:11:08 +0100 Subject: [PATCH] Add typescript suppport * first files migrated to typescript * switched all components to typescript * moved typescript interfaces to separate files --- .github/workflows/nodejs.yml | 1 + .gitignore | 4 + README.md | 30 +-- auth.js | 26 --- auth.ts | 40 ++++ components/Badge.tsx | 12 + components/CircleProgress.tsx | 71 ++++++ components/Counter.tsx | 9 + components/Dashboard.tsx | 48 ++++ components/ErrorIcon.tsx | 15 ++ components/Link.tsx | 6 + components/LoadingIndicator.tsx | 56 +++++ components/{table.js => Table.tsx} | 8 +- components/Widget.tsx | 48 ++++ components/badge.js | 12 - components/circle-progress.js | 47 ---- components/counter.js | 10 - components/dashboard.js | 41 ---- components/error-icon.js | 14 -- components/link.js | 6 - components/loading-indicator.js | 54 ----- components/widget.js | 39 ---- .../bitbucket/BitbucketPullRequestCount.tsx | 78 +++++++ .../widgets/bitbucket/bitbucket-model.ts | 15 ++ .../widgets/bitbucket/pull-request-count.js | 76 ------- components/widgets/datetime/DateTime.tsx | 54 +++++ components/widgets/datetime/datetime-model.ts | 7 + components/widgets/datetime/index.js | 48 ---- .../elasticsearch/ElasticsearchHitCount.tsx | 67 ++++++ .../elasticsearch/elasticsearch-model.ts | 14 ++ components/widgets/elasticsearch/hit-count.js | 66 ------ .../widgets/github/GitHubIssueCount.tsx | 72 ++++++ components/widgets/github/github-model.ts | 13 ++ components/widgets/github/issue-count.js | 66 ------ .../widgets/jenkins/JenkinsBuildBuration.tsx | 137 ++++++++++++ .../widgets/jenkins/JenkinsJobHealth.tsx | 125 +++++++++++ .../widgets/jenkins/JenkinsJobStatus.tsx | 126 +++++++++++ components/widgets/jenkins/build-duration.js | 122 ----------- components/widgets/jenkins/jenkins-model.ts | 53 +++++ components/widgets/jenkins/job-health.js | 121 ----------- components/widgets/jenkins/job-status.js | 120 ---------- components/widgets/jira/JiraIssueCount.tsx | 64 ++++++ .../widgets/jira/JiraSprintDaysRemaining.tsx | 80 +++++++ components/widgets/jira/issue-count.js | 66 ------ components/widgets/jira/jira-model.ts | 27 +++ .../widgets/jira/sprint-days-remaining.js | 76 ------- .../PageSpeedInsideScore.tsx | 79 +++++++ .../PageSpeedInsightsStats.tsx | 153 +++++++++++++ .../pagespeed-insights/pagespeed-model.ts | 39 ++++ .../widgets/pagespeed-insights/score.js | 72 ------ .../widgets/pagespeed-insights/stats.js | 121 ----------- components/widgets/sonarqube/SonarQube.tsx | 205 ++++++++++++++++++ components/widgets/sonarqube/index.js | 182 ---------------- .../widgets/sonarqube/sonarqube-model.ts | 13 ++ .../widgets/title/{index.js => Title.tsx} | 6 +- lib/auth-model.ts | 4 + lib/auth.js | 13 -- lib/auth.ts | 16 ++ next-env.d.ts | 2 + next.config.js | 12 +- package.json | 11 +- pages/MyDocument.tsx | 31 +++ pages/_document.js | 24 -- pages/index.js | 82 ------- pages/index.tsx | 92 ++++++++ styles/dark-theme.js | 32 --- styles/dark-theme.ts | 36 +++ styles/light-theme.js | 32 --- styles/light-theme.ts | 36 +++ styles/theme-model.ts | 15 ++ tsconfig.json | 29 +++ yarn.lock | 33 +++ 72 files changed, 2060 insertions(+), 1600 deletions(-) delete mode 100644 auth.js create mode 100644 auth.ts create mode 100644 components/Badge.tsx create mode 100644 components/CircleProgress.tsx create mode 100644 components/Counter.tsx create mode 100644 components/Dashboard.tsx create mode 100644 components/ErrorIcon.tsx create mode 100644 components/Link.tsx create mode 100644 components/LoadingIndicator.tsx rename components/{table.js => Table.tsx} (76%) create mode 100644 components/Widget.tsx delete mode 100644 components/badge.js delete mode 100644 components/circle-progress.js delete mode 100644 components/counter.js delete mode 100644 components/dashboard.js delete mode 100644 components/error-icon.js delete mode 100644 components/link.js delete mode 100644 components/loading-indicator.js delete mode 100644 components/widget.js create mode 100644 components/widgets/bitbucket/BitbucketPullRequestCount.tsx create mode 100644 components/widgets/bitbucket/bitbucket-model.ts delete mode 100644 components/widgets/bitbucket/pull-request-count.js create mode 100644 components/widgets/datetime/DateTime.tsx create mode 100644 components/widgets/datetime/datetime-model.ts delete mode 100644 components/widgets/datetime/index.js create mode 100644 components/widgets/elasticsearch/ElasticsearchHitCount.tsx create mode 100644 components/widgets/elasticsearch/elasticsearch-model.ts delete mode 100644 components/widgets/elasticsearch/hit-count.js create mode 100644 components/widgets/github/GitHubIssueCount.tsx create mode 100644 components/widgets/github/github-model.ts delete mode 100644 components/widgets/github/issue-count.js create mode 100644 components/widgets/jenkins/JenkinsBuildBuration.tsx create mode 100644 components/widgets/jenkins/JenkinsJobHealth.tsx create mode 100644 components/widgets/jenkins/JenkinsJobStatus.tsx delete mode 100644 components/widgets/jenkins/build-duration.js create mode 100644 components/widgets/jenkins/jenkins-model.ts delete mode 100644 components/widgets/jenkins/job-health.js delete mode 100644 components/widgets/jenkins/job-status.js create mode 100644 components/widgets/jira/JiraIssueCount.tsx create mode 100644 components/widgets/jira/JiraSprintDaysRemaining.tsx delete mode 100644 components/widgets/jira/issue-count.js create mode 100644 components/widgets/jira/jira-model.ts delete mode 100644 components/widgets/jira/sprint-days-remaining.js create mode 100644 components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx create mode 100644 components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx create mode 100644 components/widgets/pagespeed-insights/pagespeed-model.ts delete mode 100644 components/widgets/pagespeed-insights/score.js delete mode 100644 components/widgets/pagespeed-insights/stats.js create mode 100644 components/widgets/sonarqube/SonarQube.tsx delete mode 100644 components/widgets/sonarqube/index.js create mode 100644 components/widgets/sonarqube/sonarqube-model.ts rename components/widgets/title/{index.js => Title.tsx} (63%) create mode 100644 lib/auth-model.ts delete mode 100644 lib/auth.js create mode 100644 lib/auth.ts create mode 100644 next-env.d.ts create mode 100644 pages/MyDocument.tsx delete mode 100644 pages/_document.js delete mode 100644 pages/index.js create mode 100644 pages/index.tsx delete mode 100644 styles/dark-theme.js create mode 100644 styles/dark-theme.ts delete mode 100644 styles/light-theme.js create mode 100644 styles/light-theme.ts create mode 100644 styles/theme-model.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 89aa9c27..40ee8d2c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -21,6 +21,7 @@ jobs: run: | yarn install yarn build + yarn run format yarn test env: CI: true diff --git a/.gitignore b/.gitignore index f615fae0..74e2c71f 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,7 @@ typings/ # Next.js directory .next + +# IntelliJ files +/.idea/ +*.iml diff --git a/README.md b/README.md index 4536dda0..e17bd802 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ For an example, see [pages/index.js](./pages/index.js). ## Available Widgets -### [DateTime](./components/widgets/datetime/index.js) +### [DateTime](components/widgets/datetime/DateTime.tsx) #### Example @@ -116,7 +116,7 @@ import DateTime from '../components/widgets/datetime' * `interval`: Refresh interval in milliseconds (Default: `10000`) -### [Jenkins Job Status](./components/widgets/jenkins/job-status.js) +### [Jenkins Job Status](components/widgets/jenkins/JenkinsJobStatus.tsx) #### Example @@ -142,7 +142,7 @@ For Jenkins multibranch projects add `branch` to the object. * `jobs`: List of all jobs * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [Jenkins Job Health](./components/widgets/jenkins/job-health.js) +### [Jenkins Job Health](components/widgets/jenkins/JenkinsJobHealth.tsx) #### Example @@ -169,7 +169,7 @@ For Jenkins multibranch projects add `branch` to the object. * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [Jenkins Build Duration](./components/widgets/jenkins/build-duration.js) +### [Jenkins Build Duration](components/widgets/jenkins/JenkinsBuildBuration.tsx) #### Example @@ -195,7 +195,7 @@ For Jenkins multibranch projects add `branch` to the object. * `jobs`: List of all jobs * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [JIRA Issue Count](./components/widgets/jira/issue-count.js) +### [JIRA Issue Count](components/widgets/jira/JiraIssueCount.tsx) #### Example @@ -219,7 +219,7 @@ For Jenkins multibranch projects add `branch` to the object. * `query`: JIRA search query (`jql`) * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [JIRA Sprint Days Remaining](./components/widgets/jira/sprint-days-remaining.js) +### [JIRA Sprint Days Remaining](components/widgets/jira/JiraSprintDaysRemaining.tsx) #### Example @@ -241,7 +241,7 @@ import JiraSprintDaysRemaining from '../components/widgets/jira/sprint-days-rema * `boardId`: JIRA board id * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [Bitbucket PullRequest Count](./components/widgets/bitbucket/pull-request-count.js) +### [Bitbucket PullRequest Count](components/widgets/bitbucket/BitbucketPullRequestCount.tsx) #### Example @@ -267,7 +267,7 @@ import BitbucketPullRequestCount from '../components/widgets/bitbucket/pull-requ * `users`: Bitbucket user slugs as an array * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [PageSpeed Insights Score](./components/widgets/pagespeed-insights/score.js) +### [PageSpeed Insights Score](components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx) #### Example @@ -286,7 +286,7 @@ import PageSpeedInsightsScore from '../components/widgets/pagespeed-insights/sco * Acceptable values: `desktop` | `mobile` * `filterThirdPartyResources`: Indicates if third party resources should be filtered out (Default: `false`) -### [PageSpeed Insights Stats](./components/widgets/pagespeed-insights/stats.js) +### [PageSpeed Insights Stats](components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx) #### Example @@ -305,7 +305,7 @@ import PageSpeedInsightsStats from '../components/widgets/pagespeed-insights/sta * Acceptable values: `desktop` | `mobile` * `filterThirdPartyResources`: Indicates if third party resources should be filtered out (Default: `false`) -### [SonarQube](./components/widgets/sonarqube/index.js) +### [SonarQube](components/widgets/sonarqube/SonarQube.tsx) #### Example @@ -326,7 +326,7 @@ import SonarQube from '../components/widgets/sonarqube' * `componentKey`: SonarQube project key * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [Elasticsearch Hit Count](./components/widgets/elasticsearch/hit-count.js) +### [Elasticsearch Hit Count](components/widgets/elasticsearch/ElasticsearchHitCount.tsx) #### Example @@ -350,7 +350,7 @@ import ElasticsearchHitCount from '../components/widgets/elasticsearch/hit-count * `query`: Elasticsearch query * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [GitHub Issue Count](./components/widgets/github/issue-count.js) +### [GitHub Issue Count](components/widgets/github/GitHubIssueCount.tsx) #### Example @@ -371,7 +371,7 @@ import GitHubIssueCount from '../components/widgets/github/issue-count' * `repository`: Name of the repository * `authKey`: Credential key, defined in [auth.js](./auth.js) -### [Title](./components/widgets/title/index.js) +### [Title](components/widgets/title/Title.tsx) #### Example @@ -383,7 +383,7 @@ import Title from '../components/widgets/title' ## Available Themes -### [light](./styles/light-theme.js) +### [light](styles/light-theme.ts) #### Example @@ -399,7 +399,7 @@ import lightTheme from '../styles/light-theme' ![dashboard-light](https://cloud.githubusercontent.com/assets/457834/26214930/8c065dce-3bfe-11e7-9da0-2d6ebba2dfb8.png) -### [dark](./styles/dark-theme.js) +### [dark](styles/dark-theme.ts) #### Example diff --git a/auth.js b/auth.js deleted file mode 100644 index b659852c..00000000 --- a/auth.js +++ /dev/null @@ -1,26 +0,0 @@ -export default { - bitbucket: { - username: process.env.BITBUCKET_USER, - password: process.env.BITBUCKET_PASS - }, - elasticsearch: { - username: process.env.ELASTICSEARCH_USER, - password: process.env.ELASTICSEARCH_PASS - }, - jenkins: { - username: process.env.JENKINS_USER, - password: process.env.JENKINS_PASS - }, - jira: { - username: process.env.JIRA_USER, - password: process.env.JIRA_PASS - }, - sonarqube: { - username: process.env.SONARQUBE_USER, - password: process.env.SONARQUBE_PASS - }, - github: { - username: process.env.GITHUB_USER, - password: process.env.GITHUB_PASS - } -} diff --git a/auth.ts b/auth.ts new file mode 100644 index 00000000..34f662d3 --- /dev/null +++ b/auth.ts @@ -0,0 +1,40 @@ +import { IAuthModel } from "./lib/auth-model"; + +const bitbucket: IAuthModel = { + username: process.env.BITBUCKET_USER, + password: process.env.BITBUCKET_PASS, +}; + +const elasticsearch: IAuthModel = { + username: process.env.ELASTICSEARCH_USER, + password: process.env.ELASTICSEARCH_PASS, +}; + +const jenkins: IAuthModel = { + username: process.env.JENKINS_USER, + password: process.env.JENKINS_PASS, +}; + +const jira: IAuthModel = { + username: process.env.JIRA_USER, + password: process.env.JIRA_PASS, +}; + +const sonarqube: IAuthModel = { + username: process.env.SONARQUBE_USER, + password: process.env.SONARQUBE_PASS, +}; + +const github: IAuthModel = { + username: process.env.GITHUB_USER, + password: process.env.GITHUB_PASS, +}; + +export default { + bitbucket: bitbucket, + elasticsearch: elasticsearch, + jenkins: jenkins, + jira: jira, + sonarqube: sonarqube, + github: github, +}; diff --git a/components/Badge.tsx b/components/Badge.tsx new file mode 100644 index 00000000..f03cfb78 --- /dev/null +++ b/components/Badge.tsx @@ -0,0 +1,12 @@ +import styled from "styled-components"; +import { size } from "polished"; + +export default styled.span` + ${size("1.75em")} + background-color: transparent; + border-radius: 50%; + color: ${(props) => props.theme.palette.textInvertColor}; + display: inline-block; + line-height: 1.75em; + text-align: center; +`; diff --git a/components/CircleProgress.tsx b/components/CircleProgress.tsx new file mode 100644 index 00000000..89199532 --- /dev/null +++ b/components/CircleProgress.tsx @@ -0,0 +1,71 @@ +import styled from "styled-components"; +import { size } from "polished"; +import { Component } from "react"; +import React from "react"; + +const Svg = styled.svg` + ${size("14em")} + fill: transparent; + margin: auto; +`; + +const Circle = styled.circle` + stroke-linecap: round; + stroke-width: 10; + transform: translate(100px, 100px) rotate(-89.9deg); + transition: stroke-dashoffset 0.3s linear; + + &.background { + stroke: ${(props) => props.theme.palette.borderColor}; + } + + &.progress { + stroke: ${(props) => props.theme.palette.primaryColor}; + } +`; + +const Text = styled.text` + fill: ${(props) => props.theme.palette.textColor}; + font-size: 4em; + text-anchor: middle; +`; + +export interface ICircleProgressProps { + max: number; + radius: number; + unit: string; + value: number; +} + +export default class CircleProgress extends Component< + ICircleProgressProps, + {} +> { + static defaultProps = { + max: 100, + radius: 90, + unit: "", + }; + + render() { + const { radius, max, value, unit } = this.props; + const strokeDasharray = 2 * radius * Math.PI; + const strokeDashoffset = ((max - value) / max) * strokeDasharray; + + return ( + + + + + {value} + {unit} + + + ); + } +} diff --git a/components/Counter.tsx b/components/Counter.tsx new file mode 100644 index 00000000..9f1c3cbe --- /dev/null +++ b/components/Counter.tsx @@ -0,0 +1,9 @@ +import styled from "styled-components"; +import React from "react"; + +const Counter = styled.div` + font-size: 4em; + color: ${(props) => props.theme.palette.accentColor}; +`; + +export default ({ value }) => {value}; diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx new file mode 100644 index 00000000..776acba1 --- /dev/null +++ b/components/Dashboard.tsx @@ -0,0 +1,48 @@ +import Head from "next/head"; +import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; +import { normalize } from "polished"; +import React, { Component } from "react"; + +const GlobalStyle = createGlobalStyle` + ${normalize()} + + html { + font-family: 'Roboto', sans-serif; + } +`; + +const Container = styled.main` + align-content: center; + align-items: center; + background-color: ${(props) => props.theme.palette.backgroundColor}; + color: ${(props) => props.theme.palette.textColor}; + display: flex; + flex-flow: row wrap; + justify-content: center; + min-height: 100vh; +`; + +export default class Dashboard extends Component< + { theme: any; title: string }, + {} +> { + render() { + const { theme, title, children } = this.props; + return ( + + + + {title} + + + + {children} + + + + ); + } +} diff --git a/components/ErrorIcon.tsx b/components/ErrorIcon.tsx new file mode 100644 index 00000000..26416062 --- /dev/null +++ b/components/ErrorIcon.tsx @@ -0,0 +1,15 @@ +import styled from "styled-components"; +import { size } from "polished"; +import React from "react"; + +const Svg = styled.svg` + ${size("5em")} + fill: ${(props) => props.theme.palette.errorColor}; +`; + +export default () => ( + + + + +); diff --git a/components/Link.tsx b/components/Link.tsx new file mode 100644 index 00000000..309142f5 --- /dev/null +++ b/components/Link.tsx @@ -0,0 +1,6 @@ +import styled from "styled-components"; + +export default styled.a` + text-decoration: none; + color: ${(props) => props.theme.palette.textColor}; +`; diff --git a/components/LoadingIndicator.tsx b/components/LoadingIndicator.tsx new file mode 100644 index 00000000..f0394069 --- /dev/null +++ b/components/LoadingIndicator.tsx @@ -0,0 +1,56 @@ +import styled, { keyframes } from "styled-components"; +import React, { Component } from "react"; + +const rotation: string = keyframes` + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(270deg); + } +`; + +const turn: string = keyframes` + 0% { + stroke-dashoffset: 187; + } + + 50% { + stroke-dashoffset: 46.75; + transform: rotate(135deg); + } + + 100% { + stroke-dashoffset: 187; + transform: rotate(450deg); + } +`; + +const Svg = styled.svg` + animation: ${rotation} 1.4s linear infinite; + height: ${(props) => props.size}; + width: ${(props) => props.size}; +`; + +const Circle = styled.circle` + animation: ${turn} 1.4s ease-in-out infinite; + fill: none; + stroke: ${(props) => props.theme.palette.primaryColor}; + stroke-dasharray: 187; + stroke-dashoffset: 0; + stroke-linecap: round; + stroke-width: 6; + transform-origin: center; +`; + +export default class LoadingIndicator extends Component<{ size: string }, {}> { + render() { + const svgSize = this.props.size === "small" ? "1.75em" : "5em"; + return ( + + + + ); + } +} diff --git a/components/table.js b/components/Table.tsx similarity index 76% rename from components/table.js rename to components/Table.tsx index fca7177e..cd338a9b 100644 --- a/components/table.js +++ b/components/Table.tsx @@ -1,13 +1,13 @@ -import styled from 'styled-components' +import styled from "styled-components"; export default styled.table` border-spacing: 0.75em; -` +`; export const Th = styled.th` text-align: right; -` +`; export const Td = styled.td` text-align: left; -` +`; diff --git a/components/Widget.tsx b/components/Widget.tsx new file mode 100644 index 00000000..f26e8f92 --- /dev/null +++ b/components/Widget.tsx @@ -0,0 +1,48 @@ +import styled from "styled-components"; +import { size } from "polished"; +import LoadingIndicator from "./LoadingIndicator"; +import ErrorIcon from "./ErrorIcon"; +import { Component } from "react"; +import React from "react"; + +const Container = styled.div` + ${size("20em")} + align-items: center; + background-color: ${(props) => props.theme.palette.canvasColor}; + border: 1px solid ${(props) => props.theme.palette.borderColor}; + display: flex; + flex-direction: column; + justify-content: center; + margin: 1em; + padding: 1em; +`; + +const Title = styled.h1` + text-align: center; +`; + +export default class Widget extends Component< + { error: boolean; loading: boolean; title: string }, + {} +> { + render() { + const { loading, children, error, title } = this.props; + + let content; + + if (loading) { + content = ; + } else if (error) { + content = ; + } else { + content =
{children}
; + } + + return ( + + {title ? {title} : ""} + {content} + + ); + } +} diff --git a/components/badge.js b/components/badge.js deleted file mode 100644 index 07a718d3..00000000 --- a/components/badge.js +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components' -import { size } from 'polished' - -export default styled.span` - ${size('1.75em')} - background-color: transparent; - border-radius: 50%; - color: ${props => props.theme.palette.textInvertColor}; - display: inline-block; - line-height: 1.75em; - text-align: center; -` diff --git a/components/circle-progress.js b/components/circle-progress.js deleted file mode 100644 index 4a57bd4f..00000000 --- a/components/circle-progress.js +++ /dev/null @@ -1,47 +0,0 @@ -import styled from 'styled-components' -import { size } from 'polished' - -const Svg = styled.svg` - ${size('14em')} - fill: transparent; - margin: auto; -` - -const Circle = styled.circle` - stroke-linecap: round; - stroke-width: 10; - transform: translate(100px, 100px) rotate(-89.9deg); - transition: stroke-dashoffset 0.3s linear; - - &.background { - stroke: ${props => props.theme.palette.borderColor}; - } - - &.progress { - stroke: ${props => props.theme.palette.primaryColor}; - } -` - -const Text = styled.text` - fill: ${props => props.theme.palette.textColor}; - font-size: 4em; - text-anchor: middle; -` - -export default ({ max = 100, radius = 90, unit = '', value }) => { - const strokeDasharray = 2 * radius * Math.PI - const strokeDashoffset = ((max - value) / max) * strokeDasharray - - return ( - - - - {value}{unit} - - ) -} diff --git a/components/counter.js b/components/counter.js deleted file mode 100644 index 49f1d957..00000000 --- a/components/counter.js +++ /dev/null @@ -1,10 +0,0 @@ -import styled from 'styled-components' - -const Counter = styled.div` - font-size: 4em; - color: ${props => props.theme.palette.accentColor}; -` - -export default ({ value }) => ( - {value} -) diff --git a/components/dashboard.js b/components/dashboard.js deleted file mode 100644 index ff5d231d..00000000 --- a/components/dashboard.js +++ /dev/null @@ -1,41 +0,0 @@ -import Head from 'next/head' -import styled, { createGlobalStyle, ThemeProvider } from 'styled-components' -import { normalize } from 'polished' - -const GlobalStyle = createGlobalStyle` - ${normalize()} - - html { - font-family: 'Roboto', sans-serif; - } -` - -const Container = styled.main` - align-content: center; - align-items: center; - background-color: ${props => props.theme.palette.backgroundColor}; - color: ${props => props.theme.palette.textColor}; - display: flex; - flex-flow: row wrap; - justify-content: center; - min-height: 100vh; -` - -export default ({ children, theme, title = 'Dashboard' }) => ( - - - - {title} - - - - - {children} - - - - -) diff --git a/components/error-icon.js b/components/error-icon.js deleted file mode 100644 index b9280426..00000000 --- a/components/error-icon.js +++ /dev/null @@ -1,14 +0,0 @@ -import styled from 'styled-components' -import { size } from 'polished' - -const Svg = styled.svg` - ${size('5em')} - fill: ${props => props.theme.palette.errorColor}; -` - -export default () => ( - - - - -) diff --git a/components/link.js b/components/link.js deleted file mode 100644 index 2892a951..00000000 --- a/components/link.js +++ /dev/null @@ -1,6 +0,0 @@ -import styled from 'styled-components' - -export default styled.a` - text-decoration: none; - color: ${props => props.theme.palette.textColor}; -` diff --git a/components/loading-indicator.js b/components/loading-indicator.js deleted file mode 100644 index 286d46ea..00000000 --- a/components/loading-indicator.js +++ /dev/null @@ -1,54 +0,0 @@ -import styled, { keyframes } from 'styled-components' - -const rotation = keyframes` - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(270deg); - } -` - -const turn = keyframes` - 0% { - stroke-dashoffset: 187; - } - - 50% { - stroke-dashoffset: 46.75; - transform: rotate(135deg); - } - - 100% { - stroke-dashoffset: 187; - transform: rotate(450deg); - } -` - -const Svg = styled.svg` - animation: ${rotation} 1.4s linear infinite; - height: ${props => props.size}; - width: ${props => props.size}; -` - -const Circle = styled.circle` - animation: ${turn} 1.4s ease-in-out infinite; - fill: none; - stroke: ${props => props.theme.palette.primaryColor}; - stroke-dasharray: 187; - stroke-dashoffset: 0; - stroke-linecap: round; - stroke-width: 6; - transform-origin: center; -` - -export default ({ size = 'medium' }) => { - const svgSize = size === 'small' ? '1.75em' : '5em' - - return ( - - - - ) -} diff --git a/components/widget.js b/components/widget.js deleted file mode 100644 index 0f23cef7..00000000 --- a/components/widget.js +++ /dev/null @@ -1,39 +0,0 @@ -import styled from 'styled-components' -import { size } from 'polished' -import LoadingIndicator from './loading-indicator' -import ErrorIcon from './error-icon' - -const Container = styled.div` - ${size('20em')} - align-items: center; - background-color: ${props => props.theme.palette.canvasColor}; - border: 1px solid ${props => props.theme.palette.borderColor}; - display: flex; - flex-direction: column; - justify-content: center; - margin: 1em; - padding: 1em; -` - -const Title = styled.h1` - text-align: center; -` - -export default ({ children, error = false, loading = false, title = '' }) => { - let content - - if (loading) { - content = - } else if (error) { - content = - } else { - content =
{children}
- } - - return ( - - {title ? {title} : ''} - {content} - - ) -} diff --git a/components/widgets/bitbucket/BitbucketPullRequestCount.tsx b/components/widgets/bitbucket/BitbucketPullRequestCount.tsx new file mode 100644 index 00000000..eba3056d --- /dev/null +++ b/components/widgets/bitbucket/BitbucketPullRequestCount.tsx @@ -0,0 +1,78 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Counter from "../../Counter"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IBitbucketPullRequestCountProps, + IBitbucketPullRequestCountState, +} from "./bitbucket-model"; + +export default class BitbucketPullRequestCount extends Component< + IBitbucketPullRequestCountProps, + IBitbucketPullRequestCountState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "Bitbucket PR Count", + users: [], + }; + + state = { + count: 0, + error: false, + loading: true, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, url, project, repository, users } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const res = await fetch( + `${url}/rest/api/1.0/projects/${project}/repos/${repository}/pull-requests?limit=100`, + opts + ); + const json = await res.json(); + + let count: number; + if (users.length) { + count = json.values.filter((el) => users.includes(el.user.slug)).length; + } else { + count = json.size; + } + + this.setState({ count, error: false, loading: false }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { count, error, loading } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/bitbucket/bitbucket-model.ts b/components/widgets/bitbucket/bitbucket-model.ts new file mode 100644 index 00000000..a49dde93 --- /dev/null +++ b/components/widgets/bitbucket/bitbucket-model.ts @@ -0,0 +1,15 @@ +export interface IBitbucketPullRequestCountProps { + interval: number; + title: string; + users: Array; + authKey: string; + url: string; + project: string; + repository: string; +} + +export interface IBitbucketPullRequestCountState { + count: number; + error: boolean; + loading: boolean; +} diff --git a/components/widgets/bitbucket/pull-request-count.js b/components/widgets/bitbucket/pull-request-count.js deleted file mode 100644 index 97810186..00000000 --- a/components/widgets/bitbucket/pull-request-count.js +++ /dev/null @@ -1,76 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number, array } from 'yup' -import Widget from '../../widget' -import Counter from '../../counter' -import { basicAuthHeader } from '../../../lib/auth' - -const schema = object().shape({ - url: string().url().required(), - project: string().required(), - repository: string().required(), - interval: number(), - title: string(), - users: array().of(string()), - authKey: string() -}) - -export default class BitbucketPullRequestCount extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'Bitbucket PR Count', - users: [] - } - - state = { - count: 0, - error: false, - loading: true - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, url, project, repository, users } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const res = await fetch(`${url}/rest/api/1.0/projects/${project}/repos/${repository}/pull-requests?limit=100`, opts) - const json = await res.json() - - let count - if (users.length) { - count = json.values.filter((el) => users.includes(el.user.slug)).length - } else { - count = json.size - } - - this.setState({ count, error: false, loading: false }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { count, error, loading } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/datetime/DateTime.tsx b/components/widgets/datetime/DateTime.tsx new file mode 100644 index 00000000..85a9df53 --- /dev/null +++ b/components/widgets/datetime/DateTime.tsx @@ -0,0 +1,54 @@ +import React, { Component } from "react"; +import tinytime from "tinytime"; +import styled from "styled-components"; +import Widget from "../../Widget"; +import { IDateTimeProps, IDateTimeState } from "./datetime-model"; + +const TimeItem = styled.div` + font-size: 4em; + text-align: center; +`; + +const DateItem = styled.div` + font-size: 1.5em; + text-align: center; +`; + +export default class DateTime extends Component< + IDateTimeProps, + IDateTimeState +> { + static defaultProps = { + interval: 1000 * 10, + }; + + state = { + date: new Date(), + }; + + interval: any = 0; + + componentDidMount() { + const { interval } = this.props; + + this.interval = setInterval(() => this.updateTime(), interval); + } + + updateTime() { + this.setState({ date: new Date() }); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + render() { + const { date } = this.state; + return ( + + {tinytime("{H}:{mm}").render(date)} + {tinytime("{DD}.{Mo}.{YYYY}").render(date)} + + ); + } +} diff --git a/components/widgets/datetime/datetime-model.ts b/components/widgets/datetime/datetime-model.ts new file mode 100644 index 00000000..b6f6d64f --- /dev/null +++ b/components/widgets/datetime/datetime-model.ts @@ -0,0 +1,7 @@ +export interface IDateTimeProps { + interval: number; +} + +export interface IDateTimeState { + date: Date; +} diff --git a/components/widgets/datetime/index.js b/components/widgets/datetime/index.js deleted file mode 100644 index 15b49db5..00000000 --- a/components/widgets/datetime/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import { Component } from 'react' -import tinytime from 'tinytime' -import styled from 'styled-components' -import Widget from '../../widget' - -const TimeItem = styled.div` - font-size: 4em; - text-align: center; -` - -const DateItem = styled.div` - font-size: 1.5em; - text-align: center; -` - -export default class DateTime extends Component { - static defaultProps = { - interval: 1000 * 10 - } - - state = { - date: new Date() - } - - componentDidMount () { - const { interval } = this.props - - this.interval = setInterval(() => this.updateTime(), interval) - } - - updateTime () { - this.setState({ date: new Date() }) - } - - componentWillUnmount () { - clearInterval(this.interval) - } - - render () { - const { date } = this.state - return ( - - {tinytime('{H}:{mm}').render(date)} - {tinytime('{DD}.{Mo}.{YYYY}').render(date)} - - ) - } -} diff --git a/components/widgets/elasticsearch/ElasticsearchHitCount.tsx b/components/widgets/elasticsearch/ElasticsearchHitCount.tsx new file mode 100644 index 00000000..664eb8a7 --- /dev/null +++ b/components/widgets/elasticsearch/ElasticsearchHitCount.tsx @@ -0,0 +1,67 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Counter from "../../Counter"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IElasticsearchHitCountProps, + IElasticsearchHitCountState, +} from "./elasticsearch-model"; + +export default class ElasticsearchHitCount extends Component< + IElasticsearchHitCountProps, + IElasticsearchHitCountState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "Elasticsearch Hit Count", + }; + + state = { + count: 0, + error: false, + loading: true, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, index, query, url } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const res = await fetch(`${url}/${index}/_search?q=${query}`, opts); + const json = await res.json(); + + this.setState({ count: json.hits.total, error: false, loading: false }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { count, error, loading } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/elasticsearch/elasticsearch-model.ts b/components/widgets/elasticsearch/elasticsearch-model.ts new file mode 100644 index 00000000..34480ae8 --- /dev/null +++ b/components/widgets/elasticsearch/elasticsearch-model.ts @@ -0,0 +1,14 @@ +export interface IElasticsearchHitCountProps { + interval: number; + title: string; + authKey: string; + index: string; + query: string; + url: string; +} + +export interface IElasticsearchHitCountState { + count: number; + error: boolean; + loading: boolean; +} diff --git a/components/widgets/elasticsearch/hit-count.js b/components/widgets/elasticsearch/hit-count.js deleted file mode 100644 index dbce320d..00000000 --- a/components/widgets/elasticsearch/hit-count.js +++ /dev/null @@ -1,66 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number } from 'yup' -import Widget from '../../widget' -import Counter from '../../counter' -import { basicAuthHeader } from '../../../lib/auth' - -const schema = object().shape({ - url: string().url().required(), - index: string().required(), - query: string().required(), - interval: number(), - title: string() -}) - -export default class ElasticsearchHitCount extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'Elasticsearch Hit Count' - } - - state = { - count: 0, - error: false, - loading: true - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, index, query, url } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const res = await fetch(`${url}/${index}/_search?q=${query}`, opts) - const json = await res.json() - - this.setState({ count: json.hits.total, error: false, loading: false }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { count, error, loading } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/github/GitHubIssueCount.tsx b/components/widgets/github/GitHubIssueCount.tsx new file mode 100644 index 00000000..77082efa --- /dev/null +++ b/components/widgets/github/GitHubIssueCount.tsx @@ -0,0 +1,72 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Counter from "../../Counter"; +import { basicAuthHeader } from "../../../lib/auth"; +import { IGitHubIssueCountProps, IGitHubIssueCountState } from "./github-model"; + +export default class GitHubIssueCount extends Component< + IGitHubIssueCountProps, + IGitHubIssueCountState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "GitHub Issue Count", + }; + + state = { + count: 0, + error: false, + loading: true, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, owner, repository } = this.props; + const opts = + authKey && authKey != "" ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const res = await fetch( + `https://api.github.com/repos/${owner}/${repository}`, + opts + ); + const json = await res.json(); + + this.setState({ + count: json.open_issues_count, + error: false, + loading: false, + }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { count, error, loading } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/github/github-model.ts b/components/widgets/github/github-model.ts new file mode 100644 index 00000000..40cd04a9 --- /dev/null +++ b/components/widgets/github/github-model.ts @@ -0,0 +1,13 @@ +export interface IGitHubIssueCountProps { + interval: number; + title: string; + authKey: string; + owner: string; + repository: string; +} + +export interface IGitHubIssueCountState { + count: number; + error: boolean; + loading: boolean; +} diff --git a/components/widgets/github/issue-count.js b/components/widgets/github/issue-count.js deleted file mode 100644 index 3594ddad..00000000 --- a/components/widgets/github/issue-count.js +++ /dev/null @@ -1,66 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number } from 'yup' -import Widget from '../../widget' -import Counter from '../../counter' -import { basicAuthHeader } from '../../../lib/auth' - -const schema = object().shape({ - owner: string().required(), - repository: string().required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class GitHubIssueCount extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'GitHub Issue Count' - } - - state = { - count: 0, - error: false, - loading: true - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, owner, repository } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const res = await fetch(`https://api.github.com/repos/${owner}/${repository}`, opts) - const json = await res.json() - - this.setState({ count: json.open_issues_count, error: false, loading: false }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { count, error, loading } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/jenkins/JenkinsBuildBuration.tsx b/components/widgets/jenkins/JenkinsBuildBuration.tsx new file mode 100644 index 00000000..1bc1ec9e --- /dev/null +++ b/components/widgets/jenkins/JenkinsBuildBuration.tsx @@ -0,0 +1,137 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import styled from "styled-components"; + +import Widget from "../../Widget"; +import Link from "../../Link"; +import Table, { Td, Th } from "../../Table"; +import LoadingIndicator from "../../LoadingIndicator"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IJenkinsBuild, + IJenkinsBuildBurationProps, + IJenkinsBuildBurationState, +} from "./jenkins-model"; + +const Kpi = styled.span` + color: ${(props) => props.theme.palette.primaryColor}; + font-weight: 700; + font-size: 20px; +`; + +export default class JenkinsBuildDuration extends Component< + IJenkinsBuildBurationProps, + IJenkinsBuildBurationState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "Build Duration", + }; + + state = { + loading: true, + error: false, + builds: [], + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + formatTime(ms: number) { + const s = ms / 1000; + + if (s > 60) { + const min: number = Math.floor(s / 60); + let minSec: number = Math.round(s - min * 60); + const minSecString: string = + minSec.toString().length === 1 ? `0${minSec}` : minSec.toString(); + + return ( + <> + + {min}:{minSecString} + {" "} + min + + ); + } + + return ( + <> + {Math.round(s)} sec + + ); + } + + async fetchInformation() { + const { authKey, jobs, url } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const builds: Array = await Promise.all( + jobs.map(async (job) => { + const branch = job.branch ? `job/${job.branch}/` : ""; + const res = await fetch( + `${url}/job/${job.path}/${branch}lastBuild/api/json`, + opts + ); + const json = await res.json(); + + return { + name: job.label, + url: json.url, + duration: json.duration, + }; + }) + ); + + this.setState({ error: false, loading: false, builds }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { loading, error, builds } = this.state; + const { title } = this.props; + + return ( + + + + {builds && + builds.map((build: IJenkinsBuild) => ( + + + + + ))} + +
{build.name} + + {build.duration ? ( + this.formatTime(build.duration) + ) : ( + + )} + +
+
+ ); + } +} diff --git a/components/widgets/jenkins/JenkinsJobHealth.tsx b/components/widgets/jenkins/JenkinsJobHealth.tsx new file mode 100644 index 00000000..7dedb7b9 --- /dev/null +++ b/components/widgets/jenkins/JenkinsJobHealth.tsx @@ -0,0 +1,125 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import styled from "styled-components"; + +import Widget from "../../Widget"; +import Link from "../../Link"; +import Table, { Td, Th } from "../../Table"; +import LoadingIndicator from "../../LoadingIndicator"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IJenkinsBuild, + IJenkinsJobHealthProps, + IJenkinsJobHealthState, +} from "./jenkins-model"; + +const jenkinsKpiColor = ({ theme, value }) => { + if (value < 70) return theme.palette.errorColor; + if (value >= 70 && value < 90) return theme.palette.warnColor; + return theme.palette.successColor; +}; + +const Kpi = styled.span` + color: ${jenkinsKpiColor}; + font-weight: 700; + font-size: 20px; +`; + +export default class JenkinsJobHealth extends Component< + IJenkinsJobHealthProps, + IJenkinsJobHealthState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "Job Health", + }; + + state = { + loading: true, + error: false, + builds: [], + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, jobs, url } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const builds: Array = await Promise.all( + jobs.map(async (job) => { + const branch = job.branch ? `job/${job.branch}/` : ""; + const res = await fetch( + `${url}/job/${job.path}/${branch}api/json`, + opts + ); + const json = await res.json(); + + return { + name: job.label, + url: json.url, + health: json.healthReport, + duration: 0, + }; + }) + ); + + this.setState({ error: false, loading: false, builds }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + renderHealth(build) { + return build.map((b, index, array) => ( + + {b.score} + {index < array.length - 1 && / } + + )); + } + + render() { + const { loading, error, builds } = this.state; + const { title } = this.props; + + return ( + + + + {builds && + builds.map((build) => ( + + + + + ))} + +
{build.name} + {build.health ? ( + this.renderHealth(build.health) + ) : ( + + )} +
+
+ ); + } +} diff --git a/components/widgets/jenkins/JenkinsJobStatus.tsx b/components/widgets/jenkins/JenkinsJobStatus.tsx new file mode 100644 index 00000000..e8b868bc --- /dev/null +++ b/components/widgets/jenkins/JenkinsJobStatus.tsx @@ -0,0 +1,126 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import styled from "styled-components"; +import Widget from "../../Widget"; +import Table, { Td, Th } from "../../Table"; +import Badge from "../../Badge"; +import LoadingIndicator from "../../LoadingIndicator"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IJenkinsBuild, + IJenkinsJobStatusProps, + IJenkinsJobStatusState, +} from "./jenkins-model"; + +const jenkinsBadgeColor = ({ theme, status }) => { + switch (status) { + case "FAILURE": + return theme.palette.errorColor; + case "UNSTABLE": + return theme.palette.warnColor; + case "SUCCESS": + return theme.palette.successColor; + case "ABORTED": + case "NOT_BUILT": + return theme.palette.disabledColor; + default: + // null = 'In Progress' + return "transparent"; + } +}; + +const JenkinsBadge = styled(Badge)` + background-color: ${jenkinsBadgeColor}; +`; + +export default class JenkinsJobStatus extends Component< + IJenkinsJobStatusProps, + IJenkinsJobStatusState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "Job Status", + }; + + state = { + loading: true, + error: false, + builds: [], + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, jobs, url } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const builds: Array = await Promise.all( + jobs.map(async (job) => { + const branch = job.branch ? `job/${job.branch}/` : ""; + const res = await fetch( + `${url}/job/${job.path}/${branch}lastBuild/api/json`, + opts + ); + const json = await res.json(); + + return { + name: job.label, + url: json.url, + result: json.result, + duration: 0, + }; + }) + ); + + this.setState({ error: false, loading: false, builds }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { loading, error, builds } = this.state; + const { title } = this.props; + + return ( + + + + {builds && + builds.map((build) => ( + + + + + ))} + +
{build.name} + + {build.result ? ( + + ) : ( + + )} + +
+
+ ); + } +} diff --git a/components/widgets/jenkins/build-duration.js b/components/widgets/jenkins/build-duration.js deleted file mode 100644 index 9c1465d4..00000000 --- a/components/widgets/jenkins/build-duration.js +++ /dev/null @@ -1,122 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import styled from 'styled-components' -import { object, string, array, number } from 'yup' - -import Widget from '../../widget' -import Link from '../../link' -import Table, { Th, Td } from '../../table' -import LoadingIndicator from '../../loading-indicator' -import { basicAuthHeader } from '../../../lib/auth' - -const Kpi = styled.span` - color: ${props => props.theme.palette.primaryColor}; - font-weight: 700; - font-size: 20px; -` - -const schema = object().shape({ - url: string().url().required(), - jobs: array(object({ - label: string().required(), - path: string().required(), - branch: string() - })).required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class JenkinsBuildDuration extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'Build Duration' - } - - state = { - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - formatTime (ms) { - const s = ms / 1000 - - if (s > 60) { - const min = Math.floor(s / 60) - let minSec = Math.round(s - (min * 60)) - minSec = minSec.toString().length === 1 ? `0${minSec}` : minSec - - return <>{min}:{minSec} min - } - - return <>{Math.round(s)} sec - } - - async fetchInformation () { - const { authKey, jobs, url } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const builds = await Promise.all( - jobs.map(async job => { - const branch = job.branch ? `job/${job.branch}/` : '' - const res = await fetch(`${url}/job/${job.path}/${branch}lastBuild/api/json`, opts) - const json = await res.json() - - return { - name: job.label, - url: json.url, - duration: json.duration - } - }) - ) - - this.setState({ error: false, loading: false, builds }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { loading, error, builds } = this.state - const { title } = this.props - - return ( - - - - {builds && builds.map(build => ( - - - - - ))} - -
{build.name} - - { - build.duration - ? this.formatTime(build.duration) - : - } - -
-
- ) - } -} diff --git a/components/widgets/jenkins/jenkins-model.ts b/components/widgets/jenkins/jenkins-model.ts new file mode 100644 index 00000000..6726f260 --- /dev/null +++ b/components/widgets/jenkins/jenkins-model.ts @@ -0,0 +1,53 @@ +export interface IJenkinsJob { + label: string; + path: string; + branch: string; +} + +export interface IJenkinsBuild { + name: string; + url: string; + duration: number; +} + +export interface IJenkinsBuildBurationProps { + url: string; + jobs: Array; + interval: number; + title: string; + authKey: string; +} + +export interface IJenkinsBuildBurationState { + loading: boolean; + error: boolean; + builds: Array; +} + +export interface IJenkinsJobHealthProps { + url: string; + jobs: Array; + interval: number; + title: string; + authKey: string; +} + +export interface IJenkinsJobHealthState { + loading: boolean; + error: boolean; + builds: Array; +} + +export interface IJenkinsJobStatusProps { + url: string; + jobs: Array; + interval: number; + title: string; + authKey: string; +} + +export interface IJenkinsJobStatusState { + loading: boolean; + error: boolean; + builds: Array; +} diff --git a/components/widgets/jenkins/job-health.js b/components/widgets/jenkins/job-health.js deleted file mode 100644 index f75c177d..00000000 --- a/components/widgets/jenkins/job-health.js +++ /dev/null @@ -1,121 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import styled from 'styled-components' -import { object, string, array, number } from 'yup' - -import Widget from '../../widget' -import Link from '../../link' -import Table, { Th, Td } from '../../table' -import LoadingIndicator from '../../loading-indicator' -import { basicAuthHeader } from '../../../lib/auth' - -const jenkinsKpiColor = ({ theme, value }) => { - if (value < 70) return theme.palette.errorColor - if (value >= 70 && value < 90) return theme.palette.warnColor - return theme.palette.successColor -} - -const Kpi = styled.span` - color: ${jenkinsKpiColor}; - font-weight: 700; - font-size: 20px; -` - -const schema = object().shape({ - url: string().url().required(), - jobs: array(object({ - label: string().required(), - path: string().required(), - branch: string() - })).required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class JenkinsJobHealth extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'Job Health' - } - - state = { - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, jobs, url } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const builds = await Promise.all( - jobs.map(async job => { - const branch = job.branch ? `job/${job.branch}/` : '' - const res = await fetch(`${url}/job/${job.path}/${branch}api/json`, opts) - const json = await res.json() - - return { - name: job.label, - url: json.url, - health: json.healthReport - } - }) - ) - - this.setState({ error: false, loading: false, builds }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - renderHealth (build) { - return build.map((b, index, array) => ( - - {b.score} - {index < array.length - 1 && / } - - )) - } - - render () { - const { loading, error, builds } = this.state - const { title } = this.props - - return ( - - - - {builds && builds.map(build => ( - - - - - ))} - -
{build.name} - { - build.health - ? this.renderHealth(build.health) - : - } -
-
- ) - } -} diff --git a/components/widgets/jenkins/job-status.js b/components/widgets/jenkins/job-status.js deleted file mode 100644 index 03f0d04c..00000000 --- a/components/widgets/jenkins/job-status.js +++ /dev/null @@ -1,120 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import styled from 'styled-components' -import { object, string, array, number } from 'yup' -import Widget from '../../widget' -import Table, { Th, Td } from '../../table' -import Badge from '../../badge' -import LoadingIndicator from '../../loading-indicator' -import { basicAuthHeader } from '../../../lib/auth' - -const jenkinsBadgeColor = ({ theme, status }) => { - switch (status) { - case 'FAILURE': - return theme.palette.errorColor - case 'UNSTABLE': - return theme.palette.warnColor - case 'SUCCESS': - return theme.palette.successColor - case 'ABORTED': - case 'NOT_BUILT': - return theme.palette.disabledColor - default: // null = 'In Progress' - return 'transparent' - } -} -const JenkinsBadge = styled(Badge)` - background-color: ${jenkinsBadgeColor}; -` - -const schema = object().shape({ - url: string().url().required(), - jobs: array(object({ - label: string().required(), - path: string().required(), - branch: string() - })).required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class JenkinsJobStatus extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'Job Status' - } - - state = { - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, jobs, url } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const builds = await Promise.all( - jobs.map(async job => { - const branch = job.branch ? `job/${job.branch}/` : '' - const res = await fetch(`${url}/job/${job.path}/${branch}lastBuild/api/json`, opts) - const json = await res.json() - - return { - name: job.label, - url: json.url, - result: json.result - } - }) - ) - - this.setState({ error: false, loading: false, builds }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { loading, error, builds } = this.state - const { title } = this.props - - return ( - - - - {builds && builds.map(build => ( - - - - - ))} - -
{build.name} - - { - build.result - ? - : - } - -
-
- ) - } -} diff --git a/components/widgets/jira/JiraIssueCount.tsx b/components/widgets/jira/JiraIssueCount.tsx new file mode 100644 index 00000000..3069ae7d --- /dev/null +++ b/components/widgets/jira/JiraIssueCount.tsx @@ -0,0 +1,64 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Counter from "../../Counter"; +import { basicAuthHeader } from "../../../lib/auth"; +import { IJiraIssueCountProps, IJiraIssueCountState } from "./jira-model"; + +export default class JiraIssueCount extends Component< + IJiraIssueCountProps, + IJiraIssueCountState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "JIRA Issue Count", + }; + + state = { + count: 0, + error: false, + loading: true, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, url, query } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const res = await fetch(`${url}/rest/api/2/search?jql=${query}`, opts); + const json = await res.json(); + + this.setState({ count: json.total, error: false, loading: false }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { count, error, loading } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/jira/JiraSprintDaysRemaining.tsx b/components/widgets/jira/JiraSprintDaysRemaining.tsx new file mode 100644 index 00000000..5337032e --- /dev/null +++ b/components/widgets/jira/JiraSprintDaysRemaining.tsx @@ -0,0 +1,80 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Counter from "../../Counter"; +import { basicAuthHeader } from "../../../lib/auth"; +import { + IJiraSprintDaysRemainingProps, + IJiraSprintDaysRemainingState, +} from "./jira-model"; + +export default class JiraSprintDaysRemaining extends Component< + IJiraSprintDaysRemainingProps, + IJiraSprintDaysRemainingState +> { + static defaultProps = { + interval: 1000 * 60 * 60, + title: "JIRA Sprint Days Remaining", + }; + + state = { + days: 0, + error: false, + loading: true, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + calculateDays(date) { + const currentDate = new Date(); + const endDate = new Date(date); + const timeDiff = endDate.getTime() - currentDate.getTime(); + const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)); + + return diffDays < 0 ? 0 : diffDays; + } + + async fetchInformation() { + const { authKey, boardId, url } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + try { + const res = await fetch( + `${url}/rest/agile/1.0/board/${boardId}/sprint?state=active`, + opts + ); + const json = await res.json(); + const days = this.calculateDays(json.values[0].endDate); + + this.setState({ days, error: false, loading: false }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { days, error, loading } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/jira/issue-count.js b/components/widgets/jira/issue-count.js deleted file mode 100644 index e4335528..00000000 --- a/components/widgets/jira/issue-count.js +++ /dev/null @@ -1,66 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number } from 'yup' -import Widget from '../../widget' -import Counter from '../../counter' -import { basicAuthHeader } from '../../../lib/auth' - -const schema = object().shape({ - url: string().url().required(), - query: string().required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class JiraIssueCount extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'JIRA Issue Count' - } - - state = { - count: 0, - error: false, - loading: true - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, url, query } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const res = await fetch(`${url}/rest/api/2/search?jql=${query}`, opts) - const json = await res.json() - - this.setState({ count: json.total, error: false, loading: false }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { count, error, loading } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/jira/jira-model.ts b/components/widgets/jira/jira-model.ts new file mode 100644 index 00000000..454020a8 --- /dev/null +++ b/components/widgets/jira/jira-model.ts @@ -0,0 +1,27 @@ +export interface IJiraIssueCountProps { + url: string; + query: string; + interval: number; + title: string; + authKey: string; +} + +export interface IJiraIssueCountState { + count: number; + error: boolean; + loading: boolean; +} + +export interface IJiraSprintDaysRemainingProps { + url: string; + boardId: number; + interval: number; + title: string; + authKey: string; +} + +export interface IJiraSprintDaysRemainingState { + days: number; + error: boolean; + loading: boolean; +} diff --git a/components/widgets/jira/sprint-days-remaining.js b/components/widgets/jira/sprint-days-remaining.js deleted file mode 100644 index a0feb217..00000000 --- a/components/widgets/jira/sprint-days-remaining.js +++ /dev/null @@ -1,76 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number } from 'yup' -import Widget from '../../widget' -import Counter from '../../counter' -import { basicAuthHeader } from '../../../lib/auth' - -const schema = object().shape({ - url: string().url().required(), - boardId: number().required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class JiraSprintDaysRemaining extends Component { - static defaultProps = { - interval: 1000 * 60 * 60, - title: 'JIRA Sprint Days Remaining' - } - - state = { - days: 0, - error: false, - loading: true - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - calculateDays (date) { - const currentDate = new Date() - const endDate = new Date(date) - const timeDiff = endDate.getTime() - currentDate.getTime() - const diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)) - - return diffDays < 0 ? 0 : diffDays - } - - async fetchInformation () { - const { authKey, boardId, url } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - try { - const res = await fetch(`${url}/rest/agile/1.0/board/${boardId}/sprint?state=active`, opts) - const json = await res.json() - const days = this.calculateDays(json.values[0].endDate) - - this.setState({ days, error: false, loading: false }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { days, error, loading } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx b/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx new file mode 100644 index 00000000..f66c9d98 --- /dev/null +++ b/components/widgets/pagespeed-insights/PageSpeedInsideScore.tsx @@ -0,0 +1,79 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import CircleProgress from "../../CircleProgress"; +import Widget from "../../Widget"; +import { + IPageSpeedInsideScoreProps, + IPageSpeedInsideScoreState, +} from "./pagespeed-model"; + +export default class PageSpeedInsightsScore extends Component< + IPageSpeedInsideScoreProps, + IPageSpeedInsideScoreState +> { + static defaultProps = { + filterThirdPartyResources: false, + interval: 1000 * 60 * 60 * 12, + strategy: "desktop", + title: "PageSpeed Score", + }; + + state = { + score: 0, + loading: true, + error: false, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { url, filterThirdPartyResources, strategy } = this.props; + + const searchParams = [ + `url=${url}`, + `filter_third_party_resources=${filterThirdPartyResources}`, + `strategy=${strategy}`, + ].join("&"); + + try { + const res = await fetch( + `https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}` + ); + const json = await res.json(); + + this.setState({ + error: false, + loading: false, + score: json.ruleGroups.SPEED.score, + }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { error, loading, score } = this.state; + const { title } = this.props; + return ( + + + + ); + } +} diff --git a/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx b/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx new file mode 100644 index 00000000..34ac6cbb --- /dev/null +++ b/components/widgets/pagespeed-insights/PageSpeedInsightsStats.tsx @@ -0,0 +1,153 @@ +import React, { Component } from "react"; +import fetch from "isomorphic-unfetch"; +import Table, { Td, Th } from "../../Table"; +import Widget from "../../Widget"; +import { + IPageSpeedInsightsStatsProps, + IPageSpeedInsightsStatsState, + IPageSpeedStats, +} from "./pagespeed-model"; + +export default class PageSpeedInsightsStats extends Component< + IPageSpeedInsightsStatsProps, + IPageSpeedInsightsStatsState +> { + static defaultProps = { + filterThirdPartyResources: false, + interval: 1000 * 60 * 60 * 12, + strategy: "desktop", + title: "PageSpeed Stats", + }; + + state = { + stats: { + requestSize: 0, + requestCount: 0, + cssCount: 0, + cssSize: 0, + htmlSize: 0, + imageSize: 0, + javascriptCount: 0, + javascriptSize: 0, + otherSize: 0, + }, + loading: true, + error: false, + }; + + timeout: any = 0; + + static bytesToKilobytes(bytes: number): number { + return bytes > 0 ? Number.parseFloat((bytes / 1024).toFixed(1)) : 0; + } + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { url, filterThirdPartyResources, strategy } = this.props; + + const searchParams = [ + `url=${url}`, + `filter_third_party_resources=${filterThirdPartyResources}`, + `strategy=${strategy}`, + ].join("&"); + + try { + const res = await fetch( + `https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}` + ); + const json = await res.json(); + + const pageStats = json.pageStats; + const stats: IPageSpeedStats = { + cssCount: pageStats.numberCssResources || 0, + cssSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.cssResponseBytes + ), + htmlSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.htmlResponseBytes + ), + imageSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.imageResponseBytes + ), + javascriptCount: pageStats.numberJsResources || 0, + javascriptSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.javascriptResponseBytes + ), + requestCount: pageStats.numberResources || 0, + requestSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.totalRequestBytes + ), + otherSize: PageSpeedInsightsStats.bytesToKilobytes( + pageStats.otherResponseBytes + ), + }; + + this.setState({ error: false, loading: false, stats }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + render() { + const { error, loading, stats } = this.state; + const { title } = this.props; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Request + {stats.requestSize} KB ({stats.requestCount}) +
JavaScript + {stats.javascriptSize} KB ({stats.javascriptCount}) +
CSS + {stats.cssSize} KB ({stats.cssCount}) +
HTML{stats.htmlSize} KB
Image{stats.imageSize} KB
Other{stats.otherSize} KB
+
+ ); + } +} diff --git a/components/widgets/pagespeed-insights/pagespeed-model.ts b/components/widgets/pagespeed-insights/pagespeed-model.ts new file mode 100644 index 00000000..a6a87e1e --- /dev/null +++ b/components/widgets/pagespeed-insights/pagespeed-model.ts @@ -0,0 +1,39 @@ +export interface IPageSpeedInsideScoreProps { + url: string; + filterThirdPartyResources: boolean; + interval: number; + strategy: string; + title: string; +} + +export interface IPageSpeedInsideScoreState { + score: number; + loading: boolean; + error: boolean; +} + +export interface IPageSpeedInsightsStatsProps { + url: string; + filterThirdPartyResources: boolean; + interval: number; + strategy: string; + title: string; +} + +export interface IPageSpeedInsightsStatsState { + stats: IPageSpeedStats; + loading: boolean; + error: boolean; +} + +export interface IPageSpeedStats { + requestSize: number; + requestCount: number; + cssCount: number; + cssSize: number; + htmlSize: number; + imageSize: number; + javascriptCount: number; + javascriptSize: number; + otherSize: number; +} diff --git a/components/widgets/pagespeed-insights/score.js b/components/widgets/pagespeed-insights/score.js deleted file mode 100644 index 47a8ca6d..00000000 --- a/components/widgets/pagespeed-insights/score.js +++ /dev/null @@ -1,72 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, number, boolean } from 'yup' -import CircleProgress from '../../circle-progress' -import Widget from '../../widget' - -const schema = object().shape({ - url: string().url().required(), - filterThirdPartyResources: boolean(), - interval: number(), - strategy: string(), - title: string() -}) - -export default class PageSpeedInsightsScore extends Component { - static defaultProps = { - filterThirdPartyResources: false, - interval: 1000 * 60 * 60 * 12, - strategy: 'desktop', - title: 'PageSpeed Score' - } - - state = { - score: 0, - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { url, filterThirdPartyResources, strategy } = this.props - - const searchParams = [ - `url=${url}`, - `filter_third_party_resources=${filterThirdPartyResources}`, - `strategy=${strategy}` - ].join('&') - - try { - const res = await fetch(`https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`) - const json = await res.json() - - this.setState({ error: false, loading: false, score: json.ruleGroups.SPEED.score }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { error, loading, score } = this.state - const { title } = this.props - return ( - - - - ) - } -} diff --git a/components/widgets/pagespeed-insights/stats.js b/components/widgets/pagespeed-insights/stats.js deleted file mode 100644 index e92813d0..00000000 --- a/components/widgets/pagespeed-insights/stats.js +++ /dev/null @@ -1,121 +0,0 @@ -import { Component } from 'react' -import fetch from 'isomorphic-unfetch' -import { object, string, boolean, number } from 'yup' -import Table, { Th, Td } from '../../table' -import Widget from '../../widget' - -const schema = object().shape({ - url: string().url().required(), - filterThirdPartyResources: boolean(), - interval: number(), - strategy: string(), - title: string() -}) - -export default class PageSpeedInsightsStats extends Component { - static defaultProps = { - filterThirdPartyResources: false, - interval: 1000 * 60 * 60 * 12, - strategy: 'desktop', - title: 'PageSpeed Stats' - } - - state = { - stats: {}, - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - bytesToKilobytes (bytes) { - return bytes > 0 ? (bytes / 1024).toFixed(1) : 0 - } - - async fetchInformation () { - const { url, filterThirdPartyResources, strategy } = this.props - - const searchParams = [ - `url=${url}`, - `filter_third_party_resources=${filterThirdPartyResources}`, - `strategy=${strategy}` - ].join('&') - - try { - const res = await fetch(`https://www.googleapis.com/pagespeedonline/v2/runPagespeed?${searchParams}`) - const json = await res.json() - - const pageStats = json.pageStats - const stats = { - cssCount: pageStats.numberCssResources || 0, - cssSize: this.bytesToKilobytes(pageStats.cssResponseBytes), - htmlSize: this.bytesToKilobytes(pageStats.htmlResponseBytes), - imageSize: this.bytesToKilobytes(pageStats.imageResponseBytes), - javascriptCount: pageStats.numberJsResources || 0, - javascriptSize: this.bytesToKilobytes(pageStats.javascriptResponseBytes), - requestCount: pageStats.numberResources || 0, - requestSize: this.bytesToKilobytes(pageStats.totalRequestBytes), - otherSize: this.bytesToKilobytes(pageStats.otherResponseBytes) - } - - this.setState({ error: false, loading: false, stats }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - render () { - const { error, loading, stats } = this.state - const { title } = this.props - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Request{stats.requestSize} KB ({stats.requestCount})
JavaScript{stats.javascriptSize} KB ({stats.javascriptCount})
CSS{stats.cssSize} KB ({stats.cssCount})
HTML{stats.htmlSize} KB
Image{stats.imageSize} KB
Other{stats.otherSize} KB
-
- ) - } -} diff --git a/components/widgets/sonarqube/SonarQube.tsx b/components/widgets/sonarqube/SonarQube.tsx new file mode 100644 index 00000000..322cce04 --- /dev/null +++ b/components/widgets/sonarqube/SonarQube.tsx @@ -0,0 +1,205 @@ +import React, { Component } from "react"; +import styled from "styled-components"; +import fetch from "isomorphic-unfetch"; +import Widget from "../../Widget"; +import Table, { Td, Th } from "../../Table"; +import Badge from "../../Badge"; +import { basicAuthHeader } from "../../../lib/auth"; +import { ISonarQubeProps, ISonarQubeState } from "./sonarqube-model"; + +const alertColor = ({ theme, children }) => { + switch (children) { + case "ERROR": + return theme.palette.errorColor; + case "WARN": + return theme.palette.warnColor; + default: + // OK + return theme.palette.successColor; + } +}; +const Alert = styled.span` + color: ${alertColor}; +`; + +const sonarBadgeColor = ({ theme, children }) => { + switch (children) { + case "A": + return theme.palette.successColor; + case "B": + return theme.palette.successSecondaryColor; + case "C": + return theme.palette.warnColor; + case "D": + return theme.palette.warnSecondaryColor; + case "E": + return theme.palette.errorColor; + default: + return "transparent"; + } +}; +const SonarBadge = styled(Badge)` + background-color: ${sonarBadgeColor}; +`; + +export default class SonarQube extends Component< + ISonarQubeProps, + ISonarQubeState +> { + static defaultProps = { + interval: 1000 * 60 * 5, + title: "SonarQube", + }; + + state = { + measures: [], + loading: true, + error: false, + }; + + timeout: any = 0; + + componentDidMount() { + this.fetchInformation().catch((err) => { + console.error(`${err.name} @ ${this.constructor.name}`, err.errors); + this.setState({ error: true, loading: false }); + }); + } + + componentWillUnmount() { + clearTimeout(this.timeout); + } + + async fetchInformation() { + const { authKey, url, componentKey } = this.props; + const opts = authKey ? { headers: basicAuthHeader(authKey) } : {}; + + // https://docs.sonarqube.org/display/SONAR/Metric+Definitions + const metricKeys = [ + "alert_status", + "reliability_rating", + "bugs", + "security_rating", + "vulnerabilities", + "sqale_rating", + "code_smells", + "coverage", + "duplicated_lines_density", + ].join(","); + + try { + const res = await fetch( + `${url}/api/measures/component?componentKey=${componentKey}&metricKeys=${metricKeys}`, + opts + ); + const json = await res.json(); + + this.setState({ + error: false, + loading: false, + measures: json.component.measures, + }); + } catch (error) { + this.setState({ error: true, loading: false }); + } finally { + this.timeout = setTimeout( + () => this.fetchInformation(), + this.props.interval + ); + } + } + + getMetricValue = (measures, metricKey: string) => { + const result = measures.filter((measure) => measure.metric === metricKey); + return result.length ? result[0].value : ""; + }; + + getRatingValue = (measures, metricKey: string) => { + const value = this.getMetricValue(measures, metricKey); + + switch (value) { + case "1.0": + return "A"; + case "2.0": + return "B"; + case "3.0": + return "C"; + case "4.0": + return "D"; + case "5.0": + return "E"; + } + + return "?"; + }; + + render() { + const { error, loading, measures } = this.state; + const { title } = this.props; + + const alertStatus = this.getMetricValue(measures, "alert_status"); + const reliabilityRating = this.getRatingValue( + measures, + "reliability_rating" + ); + const bugs = this.getMetricValue(measures, "bugs"); + const securityRating = this.getRatingValue(measures, "security_rating"); + const vulnerabilities = this.getMetricValue(measures, "vulnerabilities"); + const sqaleRating = this.getRatingValue(measures, "sqale_rating"); + const codeSmells = this.getMetricValue(measures, "code_smells"); + const coverage = this.getMetricValue(measures, "coverage"); + const duplicatedLinesDensity = this.getMetricValue( + measures, + "duplicated_lines_density" + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Quality Gate: + {alertStatus} +
Reliability: + {reliabilityRating}{" "} + ({bugs}) +
Security: + {securityRating}{" "} + ({vulnerabilities}) +
Maintainability: + {sqaleRating}{" "} + ({codeSmells}) +
Coverage:{coverage}%
Duplications:{duplicatedLinesDensity}%
+
+ ); + } +} diff --git a/components/widgets/sonarqube/index.js b/components/widgets/sonarqube/index.js deleted file mode 100644 index 73f11cda..00000000 --- a/components/widgets/sonarqube/index.js +++ /dev/null @@ -1,182 +0,0 @@ -import { Component } from 'react' -import styled from 'styled-components' -import fetch from 'isomorphic-unfetch' -import { object, string, number } from 'yup' -import Widget from '../../widget' -import Table, { Th, Td } from '../../table' -import Badge from '../../badge' -import { basicAuthHeader } from '../../../lib/auth' - -const alertColor = ({ theme, children }) => { - switch (children) { - case 'ERROR': - return theme.palette.errorColor - case 'WARN': - return theme.palette.warnColor - default: // OK - return theme.palette.successColor - } -} -const Alert = styled.span` - color: ${alertColor}; -` - -const sonarBadgeColor = ({ theme, children }) => { - switch (children) { - case 'A': - return theme.palette.successColor - case 'B': - return theme.palette.successSecondaryColor - case 'C': - return theme.palette.warnColor - case 'D': - return theme.palette.warnSecondaryColor - case 'E': - return theme.palette.errorColor - default: - return 'transparent' - } -} -const SonarBadge = styled(Badge)` - background-color: ${sonarBadgeColor}; -` - -const schema = object().shape({ - url: string().url().required(), - componentKey: string().required(), - interval: number(), - title: string(), - authKey: string() -}) - -export default class SonarQube extends Component { - static defaultProps = { - interval: 1000 * 60 * 5, - title: 'SonarQube' - } - - state = { - measures: [], - loading: true, - error: false - } - - componentDidMount () { - schema.validate(this.props) - .then(() => this.fetchInformation()) - .catch((err) => { - console.error(`${err.name} @ ${this.constructor.name}`, err.errors) - this.setState({ error: true, loading: false }) - }) - } - - componentWillUnmount () { - clearTimeout(this.timeout) - } - - async fetchInformation () { - const { authKey, url, componentKey } = this.props - const opts = authKey ? { headers: basicAuthHeader(authKey) } : {} - - // https://docs.sonarqube.org/display/SONAR/Metric+Definitions - const metricKeys = [ - 'alert_status', 'reliability_rating', 'bugs', 'security_rating', - 'vulnerabilities', 'sqale_rating', 'code_smells', 'coverage', - 'duplicated_lines_density' - ].join(',') - - try { - const res = await fetch(`${url}/api/measures/component?componentKey=${componentKey}&metricKeys=${metricKeys}`, opts) - const json = await res.json() - - this.setState({ error: false, loading: false, measures: json.component.measures }) - } catch (error) { - this.setState({ error: true, loading: false }) - } finally { - this.timeout = setTimeout(() => this.fetchInformation(), this.props.interval) - } - } - - getMetricValue = (measures, metricKey) => { - const result = measures.filter(measure => measure.metric === metricKey) - return result.length ? result[0].value : '' - } - - getRatingValue = (measures, metricKey) => { - const value = this.getMetricValue(measures, metricKey) - - switch (value) { - case '1.0': - return 'A' - case '2.0': - return 'B' - case '3.0': - return 'C' - case '4.0': - return 'D' - case '5.0': - return 'E' - } - - return '?' - } - - render () { - const { error, loading, measures } = this.state - const { title } = this.props - - const alertStatus = this.getMetricValue(measures, 'alert_status') - const reliabilityRating = this.getRatingValue(measures, 'reliability_rating') - const bugs = this.getMetricValue(measures, 'bugs') - const securityRating = this.getRatingValue(measures, 'security_rating') - const vulnerabilities = this.getMetricValue(measures, 'vulnerabilities') - const sqaleRating = this.getRatingValue(measures, 'sqale_rating') - const codeSmells = this.getMetricValue(measures, 'code_smells') - const coverage = this.getMetricValue(measures, 'coverage') - const duplicatedLinesDensity = this.getMetricValue(measures, 'duplicated_lines_density') - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Quality Gate:{alertStatus}
Reliability: - {reliabilityRating} ({bugs}) -
Security: - {securityRating} ({vulnerabilities}) -
Maintainability: - {sqaleRating} ({codeSmells}) -
Coverage:{coverage}%
Duplications:{duplicatedLinesDensity}%
-
- ) - } -} diff --git a/components/widgets/sonarqube/sonarqube-model.ts b/components/widgets/sonarqube/sonarqube-model.ts new file mode 100644 index 00000000..3d15293f --- /dev/null +++ b/components/widgets/sonarqube/sonarqube-model.ts @@ -0,0 +1,13 @@ +export interface ISonarQubeProps { + url: string; + componentKey: string; + interval: number; + title: string; + authKey: string; +} + +export interface ISonarQubeState { + measures: Array; + loading: boolean; + error: boolean; +} diff --git a/components/widgets/title/index.js b/components/widgets/title/Title.tsx similarity index 63% rename from components/widgets/title/index.js rename to components/widgets/title/Title.tsx index 268dc813..40cfd0d7 100644 --- a/components/widgets/title/index.js +++ b/components/widgets/title/Title.tsx @@ -1,4 +1,4 @@ -import styled from 'styled-components' +import styled from "styled-components"; const Title = styled.h1` flex: 1 1 100%; @@ -6,6 +6,6 @@ const Title = styled.h1` margin: 0; padding: 0; text-align: center; -` +`; -export default Title +export default Title; diff --git a/lib/auth-model.ts b/lib/auth-model.ts new file mode 100644 index 00000000..f940c50a --- /dev/null +++ b/lib/auth-model.ts @@ -0,0 +1,4 @@ +export interface IAuthModel { + username: string; + password: string; +} diff --git a/lib/auth.js b/lib/auth.js deleted file mode 100644 index 5456d9f0..00000000 --- a/lib/auth.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Base64 } from 'js-base64' -import auth from '../auth' - -export const basicAuthHeader = (key) => { - const credentials = auth[key] - - if (credentials) { - const credential = Base64.encode(`${credentials.username}:${credentials.password}`) - return { Authorization: `Basic ${credential}` } - } - - throw new ReferenceError(`No credentials found with key '${key}' in auth.js`) -} diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 00000000..64a6110f --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,16 @@ +import { Base64 } from "js-base64"; +import auth from "../auth"; +import { IAuthModel } from "./auth-model"; + +export const basicAuthHeader = (key: string) => { + const credentials: IAuthModel = auth[key]; + + if (credentials) { + const credential: string = Base64.encode( + `${credentials.username}:${credentials.password}` + ); + return { Authorization: `Basic ${credential}` }; + } + + throw new ReferenceError(`No credentials found with key '${key}' in auth.js`); +}; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 00000000..7b7aa2c7 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/next.config.js b/next.config.js index e2cc2b56..c31edce6 100644 --- a/next.config.js +++ b/next.config.js @@ -1,10 +1,8 @@ -const Dotenv = require('dotenv-webpack') +const Dotenv = require("dotenv-webpack"); module.exports = { webpack: (config) => { - config.plugins.push( - new Dotenv({ path: './.env' }) - ) - return config - } -} + config.plugins.push(new Dotenv({ path: "./.env" })); + return config; + }, +}; diff --git a/package.json b/package.json index fda4d7d2..94c4b975 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,14 @@ "name": "dashboard", "version": "1.0.0", "description": "Your Team Dashboard", - "main": "pages/index.js", + "main": "pages/index.tsx", "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start", - "lint": "standard --verbose | snazzy && stylelint '**/*.js'", + "lint": "prettier --check *.js **/*.tsx **/**/*.tsx **/**/*.ts", + "format": "prettier --write *.js **/*.tsx **/**/*.tsx **/**/*.ts", "test": "npm run lint" }, "repository": { @@ -26,6 +27,8 @@ }, "homepage": "https://github.com/danielbayerlein/dashboard#readme", "dependencies": { + "@types/node": "^13.9.2", + "@types/react": "^16.9.25", "babel-plugin-styled-components": "^1.10.7", "dotenv-webpack": "^1.7.0", "isomorphic-unfetch": "^3.0.0", @@ -36,7 +39,9 @@ "react-dom": "^16.13.1", "styled-components": "^4.4.1", "tinytime": "^0.2.6", - "yup": "^0.28.1" + "typescript": "^3.8.3", + "yup": "^0.28.1", + "prettier": "^2.0.1" }, "devDependencies": { "babel-eslint": "^10.1.0", diff --git a/pages/MyDocument.tsx b/pages/MyDocument.tsx new file mode 100644 index 00000000..00b01870 --- /dev/null +++ b/pages/MyDocument.tsx @@ -0,0 +1,31 @@ +import Document from "next/document"; +import { ServerStyleSheet } from "styled-components"; +import React from "react"; + +export default class MyDocument extends Document { + static async getInitialProps(ctx) { + const sheet = new ServerStyleSheet(); + const originalRenderPage = ctx.renderPage; + + try { + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: (App) => (props) => + sheet.collectStyles(), + }); + + const initialProps = await Document.getInitialProps(ctx); + return { + ...initialProps, + styles: ( + <> + {initialProps.styles} + {sheet.getStyleElement()} + + ), + }; + } finally { + sheet.seal(); + } + } +} diff --git a/pages/_document.js b/pages/_document.js deleted file mode 100644 index 542898c9..00000000 --- a/pages/_document.js +++ /dev/null @@ -1,24 +0,0 @@ -import Document from 'next/document' -import { ServerStyleSheet } from 'styled-components' - -export default class MyDocument extends Document { - static async getInitialProps (ctx) { - const sheet = new ServerStyleSheet() - const originalRenderPage = ctx.renderPage - - try { - ctx.renderPage = () => - originalRenderPage({ - enhanceApp: App => props => sheet.collectStyles() - }) - - const initialProps = await Document.getInitialProps(ctx) - return { - ...initialProps, - styles: <>{initialProps.styles}{sheet.getStyleElement()} - } - } finally { - sheet.seal() - } - } -} diff --git a/pages/index.js b/pages/index.js deleted file mode 100644 index c8637a16..00000000 --- a/pages/index.js +++ /dev/null @@ -1,82 +0,0 @@ -import Dashboard from '../components/dashboard' - -// Widgets -import DateTime from '../components/widgets/datetime' -import PageSpeedInsightsScore from '../components/widgets/pagespeed-insights/score' -import PageSpeedInsightsStats from '../components/widgets/pagespeed-insights/stats' -import JiraIssueCount from '../components/widgets/jira/issue-count' -import SonarQube from '../components/widgets/sonarqube' -import JenkinsJobStatus from '../components/widgets/jenkins/job-status' -import JenkinsJobHealth from '../components/widgets/jenkins/job-health' -import JenkinsBuildDuration from '../components/widgets/jenkins/build-duration' -import BitbucketPullRequestCount from '../components/widgets/bitbucket/pull-request-count' -import ElasticsearchHitCount from '../components/widgets/elasticsearch/hit-count' -import GitHubIssueCount from '../components/widgets/github/issue-count' - -// Theme -import lightTheme from '../styles/light-theme' -// import darkTheme from '../styles/dark-theme' - -export default () => ( - - - - - - - - - - - - - - - - - - - - - - - -) diff --git a/pages/index.tsx b/pages/index.tsx new file mode 100644 index 00000000..40196fb5 --- /dev/null +++ b/pages/index.tsx @@ -0,0 +1,92 @@ +import React from "react"; + +import Dashboard from "../components/Dashboard"; + +// Widgets +import DateTime from "../components/widgets/datetime/DateTime"; +import PageSpeedInsightsScore from "../components/widgets/pagespeed-insights/PageSpeedInsideScore"; +import PageSpeedInsightsStats from "../components/widgets/pagespeed-insights/PageSpeedInsightsStats"; +import JiraIssueCount from "../components/widgets/jira/JiraIssueCount"; +import SonarQube from "../components/widgets/sonarqube/SonarQube"; +import JenkinsJobStatus from "../components/widgets/jenkins/JenkinsJobStatus"; +import JenkinsJobHealth from "../components/widgets/jenkins/JenkinsJobHealth"; +import JenkinsBuildDuration from "../components/widgets/jenkins/JenkinsBuildBuration"; +import BitbucketPullRequestCount from "../components/widgets/bitbucket/BitbucketPullRequestCount"; +import ElasticsearchHitCount from "../components/widgets/elasticsearch/ElasticsearchHitCount"; +import GitHubIssueCount from "../components/widgets/github/GitHubIssueCount"; + +// Theme +import lightTheme from "../styles/light-theme"; +// import darkTheme from '../styles/dark-theme' + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/styles/dark-theme.js b/styles/dark-theme.js deleted file mode 100644 index f76f3ffd..00000000 --- a/styles/dark-theme.js +++ /dev/null @@ -1,32 +0,0 @@ -const colors = { - grey400: '#bdbdbd', - grey700: '#616161', - grey800: '#424242', - grey: '#303030', - white: '#ffffff', - cyan500: '#00bcd4', - pinkA200: '#ff4081', - red500: '#f44336', - amber500: '#ffc107', - green500: '#4caf50', - orange500: '#ff9800', - lime500: '#cddc39' -} - -export default { - palette: { - backgroundColor: colors.grey, - borderColor: colors.grey700, - textColor: colors.white, - textInvertColor: colors.grey, - canvasColor: colors.grey800, - primaryColor: colors.cyan500, - accentColor: colors.pinkA200, - errorColor: colors.red500, - warnColor: colors.amber500, - warnSecondaryColor: colors.orange500, - successColor: colors.green500, - successSecondaryColor: colors.lime500, - disabledColor: colors.grey400 - } -} diff --git a/styles/dark-theme.ts b/styles/dark-theme.ts new file mode 100644 index 00000000..13ae4f9a --- /dev/null +++ b/styles/dark-theme.ts @@ -0,0 +1,36 @@ +import { IThemePalette } from "./theme-model"; + +const colors = { + grey400: "#bdbdbd", + grey700: "#616161", + grey800: "#424242", + grey: "#303030", + white: "#ffffff", + cyan500: "#00bcd4", + pinkA200: "#ff4081", + red500: "#f44336", + amber500: "#ffc107", + green500: "#4caf50", + orange500: "#ff9800", + lime500: "#cddc39", +}; + +const palette: IThemePalette = { + backgroundColor: colors.grey, + borderColor: colors.grey700, + textColor: colors.white, + textInvertColor: colors.grey, + canvasColor: colors.grey800, + primaryColor: colors.cyan500, + accentColor: colors.pinkA200, + errorColor: colors.red500, + warnColor: colors.amber500, + warnSecondaryColor: colors.orange500, + successColor: colors.green500, + successSecondaryColor: colors.lime500, + disabledColor: colors.grey400, +}; + +export default { + palette: palette, +}; diff --git a/styles/light-theme.js b/styles/light-theme.js deleted file mode 100644 index 0585f1d1..00000000 --- a/styles/light-theme.js +++ /dev/null @@ -1,32 +0,0 @@ -const colors = { - grey50: '#fafafa', - grey200: '#eeeeee', - grey400: '#bdbdbd', - grey900: '#212121', - white: '#ffffff', - cyan500: '#00bcd4', - pinkA200: '#ff4081', - redA700: '#d50000', - amberA700: '#ffab00', - greenA700: '#00c853', - lightGreenA700: '#64dd17', - orangeA700: '#ff6d00' -} - -export default { - palette: { - backgroundColor: colors.grey50, - borderColor: colors.grey200, - textColor: colors.grey900, - textInvertColor: colors.grey50, - canvasColor: colors.white, - primaryColor: colors.cyan500, - accentColor: colors.pinkA200, - errorColor: colors.redA700, - warnColor: colors.amberA700, - warnSecondaryColor: colors.orangeA700, - successColor: colors.greenA700, - successSecondaryColor: colors.lightGreenA700, - disabledColor: colors.grey400 - } -} diff --git a/styles/light-theme.ts b/styles/light-theme.ts new file mode 100644 index 00000000..654aa623 --- /dev/null +++ b/styles/light-theme.ts @@ -0,0 +1,36 @@ +import { IThemePalette } from "./theme-model"; + +const colors = { + grey50: "#fafafa", + grey200: "#eeeeee", + grey400: "#bdbdbd", + grey900: "#212121", + white: "#ffffff", + cyan500: "#00bcd4", + pinkA200: "#ff4081", + redA700: "#d50000", + amberA700: "#ffab00", + greenA700: "#00c853", + lightGreenA700: "#64dd17", + orangeA700: "#ff6d00", +}; + +const palette: IThemePalette = { + backgroundColor: colors.grey50, + borderColor: colors.grey200, + textColor: colors.grey900, + textInvertColor: colors.grey50, + canvasColor: colors.white, + primaryColor: colors.cyan500, + accentColor: colors.pinkA200, + errorColor: colors.redA700, + warnColor: colors.amberA700, + warnSecondaryColor: colors.orangeA700, + successColor: colors.greenA700, + successSecondaryColor: colors.lightGreenA700, + disabledColor: colors.grey400, +}; + +export default { + palette: palette, +}; diff --git a/styles/theme-model.ts b/styles/theme-model.ts new file mode 100644 index 00000000..3bb8189e --- /dev/null +++ b/styles/theme-model.ts @@ -0,0 +1,15 @@ +export interface IThemePalette { + backgroundColor: string; + borderColor: string; + textColor: string; + textInvertColor: string; + canvasColor: string; + primaryColor: string; + accentColor: string; + errorColor: string; + warnColor: string; + warnSecondaryColor: string; + successColor: string; + successSecondaryColor: string; + disabledColor: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..48ca14af --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "exclude": [ + "node_modules" + ], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ] +} diff --git a/yarn.lock b/yarn.lock index a81d4c2a..f0a4af7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1184,6 +1184,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^13.9.2": + version "13.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" + integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -1194,6 +1199,19 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/react@^16.9.25": + version "16.9.25" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.25.tgz#6ae2159b40138c792058a23c3c04fd3db49e929e" + integrity sha512-Dlj2V72cfYLPNscIG3/SMUOzhzj7GK3bpSrfefwt2YT9GLynvLCCZjbhyF6VsT0q0+aRACRX03TDJGb7cA0cqg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/unist@*", "@types/unist@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6" @@ -2699,6 +2717,11 @@ cssnano-simple@1.0.0: cssnano-preset-simple "^1.0.0" postcss "^7.0.18" +csstype@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098" + integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q== + cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -6521,6 +6544,11 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prettier@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.1.tgz#3f00ac71263be34684b2b2c8d7e7f63737592dac" + integrity sha512-piXGBcY1zoFOG0MvHpNE5reAGseLmaCRifQ/fmfF49BcYkInEs/naD/unxGNAeOKFA5+JxVrPyMvMlpzcd20UA== + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -8125,6 +8153,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + ua-parser-js@^0.7.18: version "0.7.18" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"