├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.css ├── index.html └── manifest.json ├── screenshot1.png ├── screenshot2.png ├── src ├── App.tsx ├── ReduxRoot.tsx ├── actions │ ├── dashboard.ts │ └── index.ts ├── components │ ├── PipelineState.tsx │ ├── PipelineTable.tsx │ └── index.ts ├── configureStore.ts ├── index.tsx ├── model │ └── model.ts ├── pages │ ├── DashboardPage.tsx │ └── LoginPage.tsx ├── reducers │ ├── createReducer.ts │ ├── dashboard.ts │ └── index.ts └── withRoot.tsx ├── tsconfig.json ├── tsconfig.test.json ├── tslint.json └── typings └── misc.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 innFactory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple dashboard for monitoring your aws codepipelines 2 | 3 | example 4 | example 5 | 6 | ## How to use 7 | 8 | Download or clone this repo 9 | 10 | ```bash 11 | git clone https://github.com/innFactory/aws-codepipeline-dashboard 12 | cd create-react-app-material-typescript-redux 13 | ``` 14 | 15 | Install it and run: 16 | 17 | ```bash 18 | npm i 19 | npm start 20 | ``` 21 | 22 | Got to http://localhost:3000/ 23 | 24 | ## Todo List 25 | - [x] Show states of codepipelines 26 | - [x] Refresh codepipelines every 8-20 seconds 27 | - [ ] Add pretty animations for each state 28 | - [ ] Support for custom grid sizes 29 | - [ ] Add theming 30 | 31 | ## Contributors 32 | 33 | * [Anton Spöck](https://github.com/spoeck) 34 | 35 | Powered by [innFactory](https://innfactory.de/) 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app-material-typescript-redux", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "aws-sdk": "^2.233.1", 7 | "localforage": "^1.7.1", 8 | "material-ui": "1.0.0-beta.44", 9 | "material-ui-icons": "^1.0.0-beta.36", 10 | "npm": "^5.7.1", 11 | "react": "16.2.0", 12 | "react-dom": "16.3.2", 13 | "react-grid-layout": "^0.16.6", 14 | "react-redux": "^5.0.7", 15 | "react-router": "^4.2.0", 16 | "react-scripts-ts": "latest", 17 | "redux": "^3.7.2", 18 | "redux-logger": "^3.0.6", 19 | "redux-persist": "^5.9.1", 20 | "redux-thunk": "^2.2.0" 21 | }, 22 | "devDependencies": { 23 | "@types/history": "^4.6.2", 24 | "@types/jest": "22.1.0", 25 | "@types/node": "^9.4.0", 26 | "@types/react": "^16.3.2", 27 | "@types/react-dom": "^16.0.5", 28 | "@types/react-grid-layout": "^0.16.4", 29 | "@types/react-redux": "^5.0.19", 30 | "@types/react-router": "^4.0.18", 31 | "@types/redux-logger": "^3.0.5", 32 | "@types/webpack-env": "^1.13.5", 33 | "csstype": "^2.4.2", 34 | "redux-devtools-extension": "^2.13.2", 35 | "typescript": "2.8.3" 36 | }, 37 | "scripts": { 38 | "start": "react-scripts-ts start", 39 | "build": "react-scripts-ts build", 40 | "test": "react-scripts-ts test --env=jsdom", 41 | "eject": "react-scripts-ts eject" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/aws-codepipeline-dashboard/29fa8731eaada9f0db1348f83ee925ad0a04f7ca/public/favicon.ico -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | html, body, #app, #root, #app>div { 2 | height: 100% !important; 3 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 24 | 25 | My page 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/aws-codepipeline-dashboard/29fa8731eaada9f0db1348f83ee925ad0a04f7ca/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/innFactory/aws-codepipeline-dashboard/29fa8731eaada9f0db1348f83ee925ad0a04f7ca/screenshot2.png -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import withStyles, { WithStyles, StyleRulesCallback } from 'material-ui/styles/withStyles'; 3 | import withRoot from './withRoot'; 4 | import LoginPage from './pages/LoginPage'; 5 | import DashboardPage from './pages/DashboardPage'; 6 | import { Router, Route, RouteComponentProps } from 'react-router'; 7 | import { createBrowserHistory } from 'history'; 8 | import { RootState } from './reducers/index'; 9 | import { connect } from 'react-redux'; 10 | 11 | export namespace App { 12 | export interface Props extends RouteComponentProps { 13 | } 14 | 15 | export interface State { 16 | mobileOpen: boolean; 17 | } 18 | } 19 | 20 | const history = createBrowserHistory(); 21 | 22 | class App extends React.Component { 23 | 24 | state = { 25 | mobileOpen: true, 26 | }; 27 | 28 | routes = ( 29 |
30 | 31 | 32 | 33 |
34 | ); 35 | 36 | render() { 37 | 38 | return ( 39 | 40 |
41 |
42 | {this.routes} 43 |
44 |
45 |
46 | ); 47 | } 48 | } 49 | 50 | const styles: StyleRulesCallback = theme => ({ 51 | root: { 52 | width: '100%', 53 | height: '100%', 54 | zIndex: 1, 55 | overflow: 'hidden', 56 | }, 57 | appFrame: { 58 | position: 'relative', 59 | display: 'flex', 60 | width: '100%', 61 | height: '100%', 62 | }, 63 | appBar: { 64 | zIndex: theme.zIndex.drawer + 1, 65 | position: 'absolute', 66 | }, 67 | content: { 68 | backgroundColor: theme.palette.background.default, 69 | width: '100%', 70 | }, 71 | }); 72 | 73 | function mapStateToProps(state: RootState) { 74 | return { 75 | }; 76 | } 77 | 78 | export default (withRoot(withStyles(styles)<{}>(connect(mapStateToProps)(App)))); 79 | -------------------------------------------------------------------------------- /src/ReduxRoot.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import App from './App'; 3 | import { 4 | applyMiddleware, 5 | } from 'redux'; 6 | import thunk from 'redux-thunk'; 7 | import { createLogger } from 'redux-logger'; 8 | import { Provider } from 'react-redux'; 9 | import { composeWithDevTools } from 'redux-devtools-extension'; 10 | import { PersistGate } from 'redux-persist/integration/react'; 11 | import configureStore from './configureStore'; 12 | 13 | const logger = (createLogger as any)(); 14 | 15 | var middleware = applyMiddleware(logger, thunk); 16 | 17 | if (process.env.NODE_ENV === 'development') { 18 | middleware = composeWithDevTools(middleware); 19 | } 20 | 21 | const { persistor, store } = configureStore(); 22 | 23 | class ReduxRoot extends React.Component { 24 | 25 | state = { 26 | mobileOpen: true, 27 | }; 28 | 29 | render() { 30 | 31 | return ( 32 | 33 | Loading...

} persistor={persistor}> 34 | 35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | export default ReduxRoot; 42 | -------------------------------------------------------------------------------- /src/actions/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { Action, ActionType, Credentials, PipelineState } from '../model/model'; 2 | const AWS = require('aws-sdk'); 3 | let codepipeline = new AWS.CodePipeline(); 4 | 5 | export function setCredentials(credentials: Credentials): Action { 6 | 7 | return { 8 | type: ActionType.SET_CREDENTIALS, 9 | payload: credentials 10 | }; 11 | } 12 | 13 | export function setPipelinesToObserve(pipelines: any[]): Action { 14 | 15 | return { 16 | type: ActionType.SET_PIPELINES_TO_OBSERVE, 17 | payload: pipelines 18 | }; 19 | } 20 | 21 | export function authenticate() { 22 | 23 | return (dispatch: Function, getState: Function) => auth(getState); 24 | } 25 | 26 | function auth(getState: Function) { 27 | console.log('auth'); 28 | AWS.config = new AWS.Config(); 29 | AWS.config.region = getState().credentials.region; 30 | AWS.config.update(getState().credentials); 31 | codepipeline = new AWS.CodePipeline(); 32 | } 33 | 34 | export function fetchAllPipelines() { 35 | return (dispatch: Function, getState: Function) => { 36 | codepipeline.listPipelines({}, (e: any, d: any) => { 37 | if (e) { 38 | console.log(e); 39 | setTimeout(() => auth(getState), 3000); 40 | setTimeout(() => fetchAllPipelines(), 4000); 41 | 42 | } else { 43 | dispatch({ type: ActionType.SET_ALL_PIPELINES, payload: d.pipelines }); 44 | } 45 | }); 46 | }; 47 | } 48 | 49 | export function reloadPipelineState(pipelineName: string, callback: Function) { 50 | 51 | return (dispatch: Function, getState: Function) => { 52 | codepipeline.getPipelineState({ name: pipelineName }, (e: any, d: any) => { 53 | if (e) { 54 | console.log(e); 55 | setTimeout(() => auth(getState), 3000); 56 | } else { 57 | console.log(d); 58 | 59 | let failed = false; 60 | let inProgress = false; 61 | d.stageStates.forEach((state: any) => { 62 | if (!failed && !inProgress) { 63 | const pipelineState: PipelineState = { 64 | pipelineName: d.pipelineName, 65 | state: state.latestExecution.status, 66 | stageName: state.stageName, 67 | }; 68 | 69 | callback(pipelineState); 70 | } 71 | if (state.latestExecution.status === 'Failed') { 72 | failed = true; 73 | } else if (state.latestExecution.status === 'InProgress') { 74 | inProgress = true; 75 | } 76 | }); 77 | } 78 | }); 79 | }; 80 | } -------------------------------------------------------------------------------- /src/actions/index.ts: -------------------------------------------------------------------------------- 1 | import * as DashboardActions from './dashboard'; 2 | 3 | export const ActionCreators = Object.assign({}, DashboardActions); 4 | -------------------------------------------------------------------------------- /src/components/PipelineState.tsx: -------------------------------------------------------------------------------- 1 | import { Paper, Typography } from 'material-ui'; 2 | import { LinearProgress } from 'material-ui/Progress'; 3 | import withStyles, { StyleRulesCallback, WithStyles } from 'material-ui/styles/withStyles'; 4 | import * as React from 'react'; 5 | import * as DashboardActions from '../actions/dashboard'; 6 | import { PipelineState as PS } from '../model/model'; 7 | 8 | export namespace PipelineState { 9 | export interface Props { 10 | dashboardActions: typeof DashboardActions; 11 | pipeline: any; 12 | } 13 | 14 | export interface State { 15 | isLoading: boolean; 16 | pipelineState: PS; 17 | } 18 | } 19 | const defaultState: PS = { pipelineName: '', state: 'UNKNOWN', stageName: '' }; 20 | class PipelineState extends React.Component { 21 | 22 | state = { 23 | isLoading: true, 24 | pipelineState: defaultState 25 | }; 26 | 27 | componentDidMount() { 28 | const { dashboardActions, pipeline } = this.props; 29 | const interval = getRndInteger(8, 20) * 1000; 30 | 31 | dashboardActions.reloadPipelineState(pipeline.name, (d: PS) => { 32 | this.setState({ pipelineState: d, isLoading: false }); 33 | }); 34 | setInterval(() => 35 | this.setState({ isLoading: true }, () => dashboardActions.reloadPipelineState(pipeline.name, (d: PS) => { 36 | this.setState({ pipelineState: d, isLoading: false }); 37 | // tslint:disable-next-line:align 38 | })), interval); 39 | } 40 | 41 | render() { 42 | const { classes } = this.props; 43 | const { isLoading } = this.state; 44 | return ( 45 | 46 |
47 | {isLoading && } 48 |
49 |
50 | {this.renderName()} 51 | {this.renderStage()} 52 | {this.renderState()} 53 |
54 |
55 | ); 56 | } 57 | 58 | renderName() { 59 | const { pipelineState } = this.state; 60 | 61 | const name = pipelineState ? pipelineState.pipelineName : ''; 62 | 63 | if (name) { 64 | return ({name}); 65 | } else { return null; } 66 | } 67 | 68 | renderStage() { 69 | const { pipelineState } = this.state; 70 | 71 | const name = pipelineState ? pipelineState.stageName : ''; 72 | 73 | if (name) { 74 | return ( 75 | 79 | {name === 'UNKNOWN' ? 'Loading' : name} 80 | ); 81 | } else { return null; } 82 | } 83 | 84 | renderState() { 85 | const { pipelineState } = this.state; 86 | 87 | const state = pipelineState ? pipelineState.state : ''; 88 | 89 | if (state) { 90 | return ( 91 | 95 | {state === 'UNKNOWN' ? 'Loading ...' : state} 96 | ); 97 | } else { return null; } 98 | } 99 | 100 | getBackgroundColor() { 101 | switch (this.state.pipelineState.state) { 102 | case 'Failed': return '#fc9faa'; // #f7cdd2 103 | case 'InProgress': return '#a6c1ed'; 104 | case 'Succeeded': return '#b5f2b9'; 105 | default: return null; 106 | } 107 | } 108 | 109 | } 110 | 111 | function getRndInteger(min: number, max: number) { 112 | return Math.floor(Math.random() * (max - min)) + min; 113 | } 114 | 115 | const styles: StyleRulesCallback = theme => ({ 116 | container: { 117 | width: '100%', 118 | height: '100%', 119 | }, 120 | progressBarWrapper: { 121 | height: 10 122 | }, 123 | textWrapper: { 124 | display: 'flex', 125 | flexDirection: 'column', 126 | height: '100%', 127 | padding: 10, 128 | paddingBottom: 20 129 | }, 130 | stage: { 131 | flexGrow: 10, 132 | paddingBottom: 10, 133 | }, 134 | state: { 135 | paddingBottom: 0 136 | } 137 | }); 138 | 139 | export default withStyles(styles)(PipelineState); 140 | -------------------------------------------------------------------------------- /src/components/PipelineTable.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Table, { TableBody, TableCell, TableHead, TableRow } from 'material-ui/Table'; 3 | import withStyles, { WithStyles, StyleRulesCallback } from 'material-ui/styles/withStyles'; 4 | import Checkbox from 'material-ui/Checkbox/Checkbox'; 5 | import * as DashboardActions from '../actions/dashboard'; 6 | 7 | export namespace PipelineTable { 8 | export interface Props { 9 | allPipelines: any[]; 10 | pipelinesToObserve: any[]; 11 | dashboardActions: typeof DashboardActions; 12 | } 13 | } 14 | 15 | class PipelineTable extends React.Component { 16 | 17 | constructor(props?: (WithStyles & PipelineTable.Props), context?: any) { 18 | super(props as any, context); 19 | } 20 | 21 | onRowClick(pipeline: any) { 22 | const { dashboardActions, pipelinesToObserve } = this.props; 23 | 24 | const index = pipelinesToObserve.findIndex(p => p.name === pipeline.name); 25 | 26 | console.log(index); 27 | 28 | if (index < 0) { 29 | dashboardActions.setPipelinesToObserve([...pipelinesToObserve, pipeline]); 30 | } else { 31 | let tempArray = [...pipelinesToObserve]; 32 | tempArray.splice(index, 1); 33 | dashboardActions.setPipelinesToObserve(tempArray); 34 | } 35 | } 36 | 37 | render() { 38 | const { classes, pipelinesToObserve, allPipelines } = this.props; 39 | 40 | return ( 41 | 42 | 43 | 44 | Display 45 | Pipeline 46 | 47 | 48 | 49 | {allPipelines.map(p => { 50 | return ( 51 | this.onRowClick(p)} 55 | > 56 | 57 | pi.name === p.name) >= 0} 59 | /> 60 | 61 | {p.name} 62 | 63 | ); 64 | })} 65 | 66 |
67 | ); 68 | } 69 | } 70 | 71 | const styles: StyleRulesCallback = theme => ({ 72 | table: { 73 | width: '100%', 74 | }, 75 | }); 76 | 77 | export default withStyles(styles)(PipelineTable); -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | import PipelineTable from './PipelineTable'; 2 | 3 | export { 4 | PipelineTable 5 | }; 6 | 7 | export default PipelineTable; -------------------------------------------------------------------------------- /src/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { persistStore, persistReducer } from 'redux-persist'; 3 | import * as localforage from 'localforage'; 4 | import rootReducer from './reducers'; 5 | import thunk from 'redux-thunk'; 6 | import { createLogger } from 'redux-logger'; 7 | import { composeWithDevTools } from 'redux-devtools-extension'; 8 | 9 | const persistConfig = { 10 | key: 'root', 11 | storage: localforage, 12 | }; 13 | 14 | const logger = (createLogger as any)(); 15 | 16 | var middleware = applyMiddleware(logger, thunk); 17 | 18 | if (process.env.NODE_ENV === 'development') { 19 | middleware = composeWithDevTools(middleware); 20 | } 21 | 22 | const persistedReducer = persistReducer(persistConfig, rootReducer); 23 | 24 | export default () => { 25 | let store = createStore(persistedReducer, {}, middleware); 26 | let persistor = persistStore(store); 27 | return { store, persistor }; 28 | }; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import ReduxRoot from './ReduxRoot'; 4 | 5 | const rootEl = document.getElementById('root'); 6 | ReactDOM.render(, rootEl); 7 | 8 | if (module.hot) { 9 | module.hot.accept('./ReduxRoot', () => { 10 | const NextApp = require('./ReduxRoot').default; 11 | ReactDOM.render( 12 | , 13 | rootEl 14 | ); 15 | }); 16 | } -------------------------------------------------------------------------------- /src/model/model.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Credentials { 3 | accessKeyId: string; 4 | secretAccessKey: string; 5 | region: string; 6 | } 7 | 8 | export enum ActionType { 9 | SET_CREDENTIALS, 10 | SET_PIPELINES_TO_OBSERVE, 11 | AUTHENTICATE, 12 | SET_ALL_PIPELINES 13 | } 14 | 15 | export interface Action { 16 | type: ActionType; 17 | payload: T; 18 | } 19 | 20 | export interface PipelineState { 21 | pipelineName: string; 22 | stageName: string | 'ALL'; 23 | state: 'InProgress' | 'Succeeded' | 'Failed' | 'UNKNOWN'; 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/DashboardPage.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'material-ui'; 2 | import SettingsIcon from 'material-ui-icons/Settings'; 3 | import Dialog, { DialogActions, DialogContent, DialogTitle } from 'material-ui/Dialog'; 4 | import withStyles, { StyleRulesCallback, WithStyles } from 'material-ui/styles/withStyles'; 5 | import * as React from 'react'; 6 | import { Responsive as ResponsiveGridLayout } from 'react-grid-layout'; 7 | import { connect } from 'react-redux'; 8 | import { RouteComponentProps } from 'react-router'; 9 | import { bindActionCreators } from 'redux'; 10 | import * as DashboardActions from '../actions/dashboard'; 11 | import { PipelineTable } from '../components'; 12 | import PipelineState from '../components/PipelineState'; 13 | import { RootState } from '../reducers/index'; 14 | 15 | export namespace DashboardPage { 16 | export interface Props extends RouteComponentProps { 17 | pipelinesToObserve: any[]; 18 | allPipelines: any[]; 19 | dashboardActions: typeof DashboardActions; 20 | } 21 | 22 | export interface State { 23 | showDialog: boolean; 24 | layout: Array; 25 | } 26 | } 27 | 28 | class DashboardPage extends React.Component { 29 | state = { 30 | showDialog: false, 31 | height: 150, 32 | layout: new Array(), 33 | }; 34 | 35 | componentDidMount() { 36 | this.props.dashboardActions.fetchAllPipelines(); 37 | } 38 | 39 | render() { 40 | 41 | const { classes } = this.props; 42 | 43 | return ( 44 |
47 | {this.renderGrid()} 48 | {this.renderDialog()} 49 | 57 |
58 | ); 59 | } 60 | 61 | renderPipelineState() { 62 | 63 | return this.props.pipelinesToObserve.map((p: any, i: number) => { 64 | var layout = { 65 | i: p.name, 66 | minW: 1, 67 | maxW: 3, 68 | minH: 1, 69 | maxH: 3, 70 | x: 0, 71 | y: i, 72 | w: 2, 73 | h: 1, 74 | isResizable: true 75 | }; 76 | return ( 77 |
82 | 87 |
88 | ); 89 | }); 90 | } 91 | 92 | renderGrid() { 93 | return ( 94 | 103 | {this.renderPipelineState()} 104 | 105 | ); 106 | } 107 | 108 | renderDialog() { 109 | const { pipelinesToObserve, dashboardActions, allPipelines } = this.props; 110 | 111 | return ( 112 | e.stopPropagation()} 115 | onClose={() => this.setState({ showDialog: false })} 116 | > 117 | Select Pipelines 118 | 119 | 124 | 125 | 126 | 129 | 130 | 131 | ); 132 | } 133 | } 134 | 135 | const styles: StyleRulesCallback = theme => ({ 136 | root: { 137 | padding: 10, 138 | display: 'flex', 139 | flexDirection: 'column', 140 | height: '100%', 141 | width: '100%' 142 | }, 143 | 144 | fab: { 145 | position: 'absolute', 146 | bottom: theme.spacing.unit * 2, 147 | right: theme.spacing.unit * 2, 148 | }, 149 | }); 150 | 151 | function mapStateToProps(state: RootState) { 152 | return { 153 | pipelinesToObserve: state.pipelinesToObserve, 154 | allPipelines: state.allPipelines 155 | }; 156 | } 157 | 158 | function mapDispatchToProps(dispatch: any) { 159 | return { 160 | dashboardActions: bindActionCreators(DashboardActions as any, dispatch) 161 | }; 162 | } 163 | 164 | export default (withStyles(styles)<{}>(connect(mapStateToProps, mapDispatchToProps)(DashboardPage))); 165 | -------------------------------------------------------------------------------- /src/pages/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Typography from 'material-ui/Typography'; 3 | import withStyles, { WithStyles, StyleRulesCallback } from 'material-ui/styles/withStyles'; 4 | import { RootState } from '../reducers'; 5 | import { connect } from 'react-redux'; 6 | import { RouteComponentProps } from 'react-router'; 7 | import { Paper, TextField, Button } from 'material-ui'; 8 | import * as DashboardActions from '../actions/dashboard'; 9 | import { bindActionCreators } from 'redux'; 10 | import { Credentials } from '../model/model'; 11 | 12 | export namespace LoginPage { 13 | export interface Props extends RouteComponentProps { 14 | credentials: Credentials; 15 | dashboardActions: typeof DashboardActions; 16 | } 17 | 18 | export interface State { 19 | accessKeyId: string; 20 | secretAccessKey: string; 21 | region: string; 22 | } 23 | } 24 | 25 | class LoginPage extends React.Component { 26 | 27 | state = { 28 | accessKeyId: '', 29 | secretAccessKey: '', 30 | region: '' 31 | }; 32 | 33 | static getDerivedStateFromProps(nextProps: Readonly) { 34 | let accessKeyId = nextProps.credentials.accessKeyId ? nextProps.credentials.accessKeyId : ''; 35 | let secretAccessKey = nextProps.credentials.secretAccessKey ? nextProps.credentials.secretAccessKey : ''; 36 | let region = nextProps.credentials.region ? nextProps.credentials.region : ''; 37 | 38 | return { accessKeyId, secretAccessKey, region }; 39 | } 40 | 41 | render() { 42 | 43 | const { classes } = this.props; 44 | const { accessKeyId, secretAccessKey, region } = this.state; 45 | 46 | return ( 47 |
48 | 49 | AWS Credentials 50 | this.setState({ accessKeyId: v.target.value })} 56 | /> 57 | this.setState({ secretAccessKey: v.target.value })} 64 | /> 65 | this.setState({ region: v.target.value })} 70 | onKeyPress={(e) => e.key === 'Enter' && this.login()} 71 | /> 72 |
73 | 74 |
75 |
76 |
77 | ); 78 | } 79 | 80 | login() { 81 | 82 | const { accessKeyId, secretAccessKey, region } = this.state; 83 | 84 | this.props.dashboardActions.setCredentials({ accessKeyId, secretAccessKey, region }); 85 | this.props.dashboardActions.authenticate(); 86 | this.props.history.push('/dashboard'); 87 | } 88 | } 89 | 90 | const styles: StyleRulesCallback = theme => ({ 91 | root: { 92 | display: 'flex', 93 | justifyContent: 'center', 94 | alignItems: 'center', 95 | height: '100%' 96 | }, 97 | 98 | paper: { 99 | padding: 20, 100 | }, 101 | 102 | buttonContainer: { 103 | justifyContent: 'flex-end', 104 | display: 'flex', 105 | width: '100%', 106 | marginTop: 20 107 | } 108 | }); 109 | 110 | function mapStateToProps(state: RootState) { 111 | return { 112 | credentials: state.credentials 113 | }; 114 | } 115 | 116 | function mapDispatchToProps(dispatch: any) { 117 | return { 118 | dashboardActions: bindActionCreators(DashboardActions as any, dispatch) 119 | }; 120 | } 121 | 122 | export default (withStyles(styles)<{}>(connect(mapStateToProps, mapDispatchToProps)(LoginPage))); -------------------------------------------------------------------------------- /src/reducers/createReducer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by toni on 12.03.2017. 3 | */ 4 | import { Action } from '../model/model'; 5 | 6 | export default function createReducer(initialState: Object, handlers: Object) { 7 | return function reducer(state: Object = initialState, action: Action) { 8 | if (handlers.hasOwnProperty(action.type)) { 9 | return handlers[action.type](state, action); 10 | } else { 11 | return state; 12 | } 13 | }; 14 | } -------------------------------------------------------------------------------- /src/reducers/dashboard.ts: -------------------------------------------------------------------------------- 1 | import createReducer from './createReducer'; 2 | import { Action, ActionType, Credentials } from '../model/model'; 3 | 4 | export const credentials = createReducer({ accessKeyId: '', secretAccesKey: '', region: '' }, { 5 | [ActionType.SET_CREDENTIALS](state: Credentials, action: Action) { 6 | return action.payload; 7 | }, 8 | }); 9 | 10 | export const pipelinesToObserve = createReducer([], { 11 | [ActionType.SET_PIPELINES_TO_OBSERVE](state: any[], action: Action) { 12 | return action.payload; 13 | }, 14 | }); 15 | 16 | export const allPipelines = createReducer([], { 17 | [ActionType.SET_ALL_PIPELINES](state: any[], action: Action) { 18 | return action.payload; 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import { combineReducers } from 'redux'; 3 | import * as dashboardReducder from './dashboard'; 4 | import { Credentials } from '../model/model'; 5 | 6 | export interface RootState { 7 | pipelinesToObserve: any[]; 8 | allPipelines: any[]; 9 | credentials: Credentials; 10 | } 11 | 12 | export default combineReducers({ 13 | ...dashboardReducder 14 | }); -------------------------------------------------------------------------------- /src/withRoot.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles'; 3 | import CssBaseline from 'material-ui/CssBaseline'; 4 | 5 | // A theme with custom primary and secondary color. 6 | // It's optional. 7 | const theme = createMuiTheme({ 8 | palette: { 9 | primary: { 10 | light: '#e5e5e5', 11 | main: '#727272', 12 | dark: '#363839', 13 | contrastText: '#fff', 14 | }, 15 | secondary: { 16 | light: '#ff5e50', 17 | main: '#e41e26', 18 | dark: '#a90000', 19 | contrastText: '#fff', 20 | }, 21 | }, 22 | }); 23 | 24 | function withRoot(Component: React.ComponentType) { 25 | function WithRoot(props: object) { 26 | // MuiThemeProvider makes the theme available down the React tree 27 | // thanks to React context. 28 | return ( 29 | 30 | {/* Reboot kickstart an elegant, consistent, and simple baseline to build upon. */} 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | return WithRoot; 38 | } 39 | 40 | export default withRoot; 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "es5", 6 | "lib": ["es6", "dom"], 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "jsx": "react", 10 | "moduleResolution": "node", 11 | "rootDir": "src", 12 | "forceConsistentCasingInFileNames": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "noUnusedLocals": true 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "build", 23 | "scripts", 24 | "acceptance-tests", 25 | "webpack", 26 | "jest", 27 | "src/setupTests.ts" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-react" 4 | ], 5 | "rules": { 6 | "align": [ 7 | true, 8 | "parameters", 9 | "arguments", 10 | "statements" 11 | ], 12 | "ban": false, 13 | "class-name": true, 14 | "comment-format": [ 15 | true, 16 | "check-space" 17 | ], 18 | "curly": true, 19 | "eofline": false, 20 | "forin": true, 21 | "indent": [ 22 | true, 23 | "spaces", 24 | 4 25 | ], 26 | "interface-name": [ 27 | true, 28 | "never-prefix" 29 | ], 30 | "jsdoc-format": true, 31 | "jsx-boolean-value": false, 32 | "jsx-no-lambda": false, 33 | "jsx-no-multiline-js": false, 34 | "label-position": true, 35 | "max-line-length": [ 36 | true, 37 | 120 38 | ], 39 | "member-ordering": [ 40 | true, 41 | { 42 | "order": [ 43 | "public-before-private", 44 | "static-before-instance", 45 | "variables-before-functions" 46 | ] 47 | } 48 | ], 49 | "no-any": false, 50 | "no-arg": true, 51 | "no-bitwise": true, 52 | "no-console": [ 53 | true, 54 | "error", 55 | "debug", 56 | "info", 57 | "time", 58 | "timeEnd", 59 | "trace" 60 | ], 61 | "no-consecutive-blank-lines": true, 62 | "no-construct": true, 63 | "no-debugger": true, 64 | "no-duplicate-variable": true, 65 | "no-empty": true, 66 | "no-eval": true, 67 | "no-shadowed-variable": true, 68 | "no-string-literal": true, 69 | "no-switch-case-fall-through": true, 70 | "no-trailing-whitespace": false, 71 | "no-unused-expression": true, 72 | "no-use-before-declare": false, 73 | "one-line": [ 74 | true, 75 | "check-catch", 76 | "check-else", 77 | "check-open-brace", 78 | "check-whitespace" 79 | ], 80 | "quotemark": [ 81 | true, 82 | "single", 83 | "jsx-double" 84 | ], 85 | "radix": true, 86 | "semicolon": [ 87 | true, 88 | "always", 89 | "ignore-bound-class-methods" 90 | ], 91 | "switch-default": true, 92 | "trailing-comma": [ 93 | false 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef": [ 100 | true, 101 | "parameter", 102 | "property-declaration" 103 | ], 104 | "typedef-whitespace": [ 105 | true, 106 | { 107 | "call-signature": "nospace", 108 | "index-signature": "nospace", 109 | "parameter": "nospace", 110 | "property-declaration": "nospace", 111 | "variable-declaration": "nospace" 112 | } 113 | ], 114 | "variable-name": [ 115 | true, 116 | "ban-keywords", 117 | "check-format", 118 | "allow-leading-underscore", 119 | "allow-pascal-case" 120 | ], 121 | "whitespace": [ 122 | true, 123 | "check-branch", 124 | "check-decl", 125 | "check-module", 126 | "check-operator", 127 | "check-separator", 128 | "check-type", 129 | "check-typecast" 130 | ] 131 | } 132 | } -------------------------------------------------------------------------------- /typings/misc.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'jss-preset-default'; 2 | declare module 'react-jss/*'; 3 | --------------------------------------------------------------------------------