├── .dockerignore ├── .npmrc ├── version.json ├── src ├── favicon.ico ├── acme │ ├── header │ │ ├── spinnakerHeader.less │ │ ├── header.module.ts │ │ ├── Feedback.tsx │ │ └── spinnakerHeader.html │ ├── datasource │ │ └── repos │ │ │ ├── repos.module.ts │ │ │ ├── repos.states.ts │ │ │ ├── repos.dataSource.ts │ │ │ ├── Repo.tsx │ │ │ └── SpinnakerRepos.tsx │ ├── cloudprovider │ │ ├── cloudprovider.module.ts │ │ ├── instance.detailsTemplate.html │ │ └── serverGroup.detailsTemplate.html │ ├── help │ │ └── help.overrides.ts │ ├── acme.module.ts │ └── exceptionHandler.delegate.ts ├── app.ts └── index.deck ├── webpack.config.js ├── .babelrc ├── .editorconfig ├── test └── helpers │ ├── custom-matchers.d.ts │ └── customMatchers.ts ├── Dockerfile.slim ├── jsconfig.json ├── Dockerfile ├── .gitignore ├── karma-shim.js ├── settings.gradle ├── start.sh ├── tsconfig.json ├── .eslintrc ├── README.md ├── karma.conf.js ├── tslint.json ├── settings.js ├── webpack.common.js ├── package.json └── LICENSE.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | always-auth = false 3 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "n/a", 3 | "created": 1461949989729 4 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/deck-customized/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/acme/header/spinnakerHeader.less: -------------------------------------------------------------------------------- 1 | .spinnaker-header { 2 | background-color: red !important; 3 | } 4 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const webpackCommon = require('./webpack.common'); 3 | module.exports = webpackCommon(false); 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["last 2 versions"] 6 | } 7 | }] 8 | ], 9 | "plugins": ["angularjs-annotate"] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /test/helpers/custom-matchers.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace jasmine { 4 | interface Matchers { 5 | textMatch(match: string): boolean; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile.slim: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | 3 | COPY build/webpack /opt/deck/html/ 4 | 5 | COPY docker /opt/deck/docker 6 | 7 | WORKDIR /opt/deck 8 | 9 | RUN docker/setup-apache2.sh 10 | 11 | CMD docker/run-apache2.sh 12 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "purpose": "This file is used to support Microsoft Visual Studio Code users. See http://blogs.msdn.com/b/vscode/archive/2015/07/06/vs-code-es6.aspx for more details.", 3 | "compilerOptions": { 4 | "target": "ES6", 5 | "module": "commonjs" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/acme/datasource/repos/repos.module.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import { ACME_REPOS_STATES } from './repos.states'; 3 | import { ACME_REPOS_DATASOURCE } from './repos.dataSource'; 4 | 5 | export const ACME_REPOS = 'spinnaker.acme.datasource'; 6 | module(ACME_REPOS, [ 7 | ACME_REPOS_STATES, 8 | ACME_REPOS_DATASOURCE, 9 | ]); 10 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | 3 | import { CORE_MODULE } from '@spinnaker/core'; 4 | import { AMAZON_MODULE } from '@spinnaker/amazon'; 5 | import { GOOGLE_MODULE } from '@spinnaker/google'; 6 | import { ACME_MODULE } from './acme/acme.module'; 7 | 8 | module('acme.spinnaker', [ 9 | CORE_MODULE, 10 | AMAZON_MODULE, 11 | GOOGLE_MODULE, 12 | ACME_MODULE, 13 | ]); 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8 2 | 3 | COPY . deck/ 4 | 5 | WORKDIR deck 6 | 7 | RUN docker/setup-apache2.sh && \ 8 | GRADLE_USER_HOME=cache ./gradlew build -PskipTests && \ 9 | mkdir -p /opt/deck/html/ && \ 10 | cp build/webpack/* /opt/deck/html/ && \ 11 | cd .. && \ 12 | rm -rf deck 13 | 14 | COPY docker /opt/deck/docker 15 | 16 | WORKDIR /opt/deck 17 | 18 | CMD /opt/deck/docker/run-apache2.sh 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS Generated Files # 2 | *.swp 3 | .DS_Store 4 | ehthumbs.db 5 | Icon? 6 | Thumbs.db 7 | 8 | # IDE Files 9 | *.ipr 10 | *.iws 11 | *.iml 12 | .idea/ 13 | 14 | # Node Files # 15 | npm-debug.log 16 | node_modules/ 17 | test/google/node_modules/ 18 | 19 | # Build Files 20 | .cache-loader/ 21 | .awcache/ 22 | .gradle/ 23 | .happypack/ 24 | build/ 25 | dist/ 26 | lib/ 27 | transpiled/ 28 | 29 | # Test Results 30 | test-results.xml 31 | 32 | #gradle wrapper symlinks 33 | /gradle 34 | /gradlew 35 | -------------------------------------------------------------------------------- /src/acme/header/header.module.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import { OverrideRegistry } from '@spinnaker/core'; 3 | import { ACME_HEADER_FEEDBACK } from 'acme/header/Feedback'; 4 | import './spinnakerHeader.less'; 5 | 6 | export const ACME_HEADER = 'spinnaker.acme.header'; 7 | const ngmodule = module(ACME_HEADER, [ 8 | ACME_HEADER_FEEDBACK, 9 | ]); 10 | 11 | ngmodule.run(function(overrideRegistry: OverrideRegistry) { 12 | 'ngInject'; 13 | overrideRegistry.overrideTemplate('spinnakerHeader', require('./spinnakerHeader.html')); 14 | }); 15 | -------------------------------------------------------------------------------- /src/acme/header/Feedback.tsx: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import * as React from 'react'; 3 | import './spinnakerHeader.less'; 4 | import { react2angular } from 'react2angular'; 5 | 6 | const Feedback = () => ( 7 | 8 | 9 | 10 | ); 11 | 12 | export const ACME_HEADER_FEEDBACK = 'spinnaker.acme.header.feedback'; 13 | const ngmodule = module(ACME_HEADER_FEEDBACK, []); 14 | ngmodule.component('feedback', react2angular(Feedback)); 15 | -------------------------------------------------------------------------------- /src/acme/cloudprovider/cloudprovider.module.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import { CloudProviderRegistry } from '@spinnaker/core'; 3 | 4 | export const ACME_CLOUDPROVIDER = 'spinnaker.acme.cloudprovider'; 5 | const ngmodule = module(ACME_CLOUDPROVIDER, [ ]); 6 | 7 | ngmodule.run(function(cloudProviderRegistry: CloudProviderRegistry) { 8 | 'ngInject'; 9 | cloudProviderRegistry.overrideValue('aws', 'instance.detailsTemplateUrl', require('./instance.detailsTemplate.html')); 10 | cloudProviderRegistry.overrideValue('aws', 'serverGroup.detailsTemplateUrl', require('./serverGroup.detailsTemplate.html')); 11 | }); 12 | -------------------------------------------------------------------------------- /src/acme/help/help.overrides.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | 3 | import { HELP_CONTENTS_REGISTRY, HelpContentsRegistry } from '@spinnaker/core'; 4 | 5 | export const ACME_HELP_OVERRIDES = 'spinnaker.acme.help.overrides'; 6 | module(ACME_HELP_OVERRIDES, [HELP_CONTENTS_REGISTRY]) 7 | .run((helpContentsRegistry: HelpContentsRegistry) => { 8 | const helpContents: { [key: string]: string } = { 9 | 'cluster.description': `

The original cluster was invented by ACME scientists in 1932.

`, 10 | }; 11 | Object.keys(helpContents).forEach(key => helpContentsRegistry.register(key, helpContents[key])); 12 | }); 13 | -------------------------------------------------------------------------------- /karma-shim.js: -------------------------------------------------------------------------------- 1 | Error.stackTraceLimit = Infinity; 2 | 3 | // jquery has to be first or many a test will break 4 | global.$ = global.jQuery = require('jquery'); 5 | 6 | // angular 1 test harnesss 7 | const angular = require('angular'); 8 | require('angular-mocks'); 9 | 10 | // polyfills 11 | require('core-js/client/shim'); 12 | 13 | require('rxjs'); 14 | 15 | require('./settings.js'); 16 | 17 | require('ngimport'); 18 | beforeEach(angular.mock.module('bcherny/ngimport')); 19 | 20 | require('./test/helpers/customMatchers'); 21 | 22 | const testContext = require.context('./src/', true, /\.spec\.(js|ts|tsx)$/); 23 | testContext.keys().forEach(testContext); 24 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | rootProject.name = 'deck-ui' 18 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NODE_VERSION=$(node -v) 4 | # Process engines format like { "node": ">=7.0.0" } 5 | npm_package_engines_node=$(node -e "console.log('$npm_package_engines_node'.replace(/[^0-9.]/g, ''))") 6 | 7 | if [[ $NODE_VERSION != $npm_package_engines_node ]]; then 8 | if [[ -f $HOME/.nvm/nvm.sh ]]; then 9 | echo "Updating your node version to $npm_package_engines_node..." 10 | . $HOME/.nvm/nvm.sh 11 | nvm use $npm_package_engines_node 12 | 13 | if [[ $? != 0 ]]; then 14 | echo "Installing node $npm_package_engines_node..." 15 | nvm install $npm_package_engines_node 16 | fi 17 | else 18 | echo "WARNING: could not update to node $NODE_VERSION, nvm not found..." 19 | fi 20 | fi 21 | 22 | npm run start-dev-server 23 | -------------------------------------------------------------------------------- /src/acme/acme.module.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | 3 | import { ACME_HEADER } from './header/header.module'; 4 | import { ACME_REPOS } from './datasource/repos/repos.module'; 5 | import { ACME_CLOUDPROVIDER } from './cloudprovider/cloudprovider.module'; 6 | import { ACME_HELP_OVERRIDES } from './help/help.overrides'; 7 | import { EXCEPTION_HANDLER } from './exceptionHandler.delegate'; 8 | 9 | // load all templates into the $templateCache 10 | const templates = require.context('./', true, /\.html$/); 11 | templates.keys().forEach(function (key) { 12 | templates(key); 13 | }); 14 | 15 | export const ACME_MODULE = 'spinnaker.acme'; 16 | module(ACME_MODULE, [ 17 | ACME_HEADER, 18 | ACME_REPOS, 19 | ACME_CLOUDPROVIDER, 20 | ACME_HELP_OVERRIDES, 21 | EXCEPTION_HANDLER, 22 | ]); 23 | -------------------------------------------------------------------------------- /test/helpers/customMatchers.ts: -------------------------------------------------------------------------------- 1 | beforeEach(() => { 2 | jasmine.addMatchers({ 3 | textMatch: () => { 4 | return { 5 | compare: function (node: JQuery, expected: string) { 6 | let actual: string; 7 | if (expected === undefined) { 8 | expected = ''; 9 | } 10 | if (node !== undefined) { 11 | actual = node.text().trim().replace(/\s+/g, ' '); 12 | } 13 | const result: jasmine.CustomMatcherResult = {pass: expected === actual}; 14 | if (result.pass) { 15 | result.message = `Expected ${expected}`; 16 | } else { 17 | result.message = `Expected ${expected} but was ${actual}`; 18 | } 19 | return result; 20 | } 21 | }; 22 | } 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/acme/datasource/repos/repos.states.ts: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | 3 | import { INestedState, APPLICATION_STATE_PROVIDER, ApplicationStateProvider } from '@spinnaker/core'; 4 | import { SpinnakerRepos } from './SpinnakerRepos'; 5 | import { Repo } from './Repo'; 6 | 7 | export const ACME_REPOS_STATES = 'spinnaker.acme.canary.states'; 8 | module(ACME_REPOS_STATES, [APPLICATION_STATE_PROVIDER]) 9 | .config((applicationStateProvider: ApplicationStateProvider) => { 10 | const repoState: INestedState = { 11 | name: 'repo', 12 | url: '/:reponame', 13 | component: Repo, 14 | }; 15 | 16 | const reposState: INestedState = { 17 | name: 'repos', 18 | url: '/repos', 19 | views: { 20 | insight: { 21 | component: SpinnakerRepos, $type: 'react', 22 | }, 23 | }, 24 | data: { 25 | pageTitleSection: { 26 | title: 'Spinnaker Projects' 27 | } 28 | }, 29 | children: [repoState] 30 | }; 31 | 32 | applicationStateProvider.addChildState(reposState); 33 | }); 34 | -------------------------------------------------------------------------------- /src/acme/header/spinnakerHeader.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | 28 |
29 | -------------------------------------------------------------------------------- /src/acme/datasource/repos/repos.dataSource.ts: -------------------------------------------------------------------------------- 1 | import { IHttpService, IQService, module } from 'angular'; 2 | import { 3 | Application, APPLICATION_DATA_SOURCE_REGISTRY, ApplicationDataSourceRegistry, DataSourceConfig 4 | } from '@spinnaker/core'; 5 | 6 | export const ACME_REPOS_DATASOURCE = 'spinnaker.acme.datasource.custom'; 7 | const ngmodule = module(ACME_REPOS_DATASOURCE, [APPLICATION_DATA_SOURCE_REGISTRY]); 8 | ngmodule.run(( 9 | $q: IQService, 10 | $http: IHttpService, 11 | applicationDataSourceRegistry: ApplicationDataSourceRegistry 12 | ) => { 13 | 'ngInject'; 14 | 15 | const loadRepos = () => 16 | $http.get('https://api.github.com/users/spinnaker/repos') 17 | .then(resp => resp.data) 18 | .catch(() => [ 19 | { name: 'foo'}, 20 | { name: 'bar'}, 21 | ]); 22 | 23 | const reposLoaded = (_application: Application, repositories: any[]) => { 24 | return $q.when(repositories); 25 | }; 26 | 27 | applicationDataSourceRegistry.registerDataSource(new DataSourceConfig({ 28 | optional: true, 29 | primary: true, 30 | loader: loadRepos, 31 | onLoad: reposLoaded, 32 | description: 'A custom data source fetching Spinnaker repos as out-of-band data', 33 | key: 'repos', 34 | sref: '.repos', 35 | label: 'Spinnaker Repos', 36 | icon: 'bar-chart' 37 | })); 38 | }); 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildOnSave": false, 3 | "compileOnSave": true, 4 | "compilerOptions": { 5 | "allowJs": false, 6 | "baseUrl": "src", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "jsx": "react", 10 | "lib": [ 11 | "es2016", 12 | "dom" 13 | ], 14 | "moduleResolution": "node", 15 | "module": "commonjs", 16 | "noEmitHelpers": false, 17 | "noImplicitAny": true, 18 | "noImplicitReturns": true, 19 | "noImplicitThis": false, // should really get to a place where we can turn this on 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "outDir": "transpiled", 23 | "paths": { 24 | "rxjs": ["../node_modules/rxjs"] // only needed when using npm link 25 | }, 26 | "pretty": true, 27 | "removeComments": true, 28 | "rootDir": ".", 29 | "skipLibCheck": true, 30 | "sourceMap": true, 31 | "strictNullChecks": false, // should really get to a place where we can turn this on 32 | "target": "es6", 33 | "typeRoots": [ 34 | "node_modules/@types" 35 | ] 36 | }, 37 | "awesomeTypescriptLoaderOptions": { 38 | "useBabel": true, 39 | "useCache": true 40 | }, 41 | "exclude": [ 42 | "build", 43 | "node", 44 | "node_modules", 45 | "transpiled" 46 | ], 47 | "include": [ 48 | "test/helpers/custom-matchers.d.ts", 49 | "src/**/*.ts", 50 | "src/**/*.tsx" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": false, 4 | "modules": true, 5 | }, 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "eol-last": 1, 9 | "semi": 2, 10 | "quotes": [1, "single", { "allowTemplateLiterals": true }], 11 | "comma-dangle": 0, 12 | "no-extra-boolean-cast": 1, 13 | "no-unused-vars": 1, 14 | "no-debugger": 1, 15 | "no-loop-func": 1, 16 | "space-infix-ops": 1, 17 | "no-multi-spaces": 1, 18 | "key-spacing": 0, // TODO 19 | "no-spaced-func": 0, 20 | "no-alert": 1, 21 | "no-use-before-define": 0, // TODO 22 | "no-underscore-dangle": 1, 23 | "no-shadow": 0, // TODO 24 | "no-unexpected-multiline": 1, 25 | "consistent-return": 0, // ANG, shutting this off for now....it's a bit chatty 26 | "no-extend-native": 1, 27 | "space-before-blocks": 1, 28 | "semi-spacing": 1, 29 | "no-array-constructor": 1, 30 | "no-negated-condition": 0, // TODO 31 | "no-trailing-spaces": 1, 32 | "operator-assignment": [1, "always"], 33 | "no-const-assign": 1, 34 | "no-console": 1, 35 | "no-control-regex": 0, 36 | "no-case-declarations": 0, 37 | "no-empty-pattern": 0 38 | }, 39 | "parserOptions": { 40 | "sourceType": "module" 41 | }, 42 | "env": { 43 | "browser": true, 44 | "node": true, 45 | "es6": true, 46 | "jasmine": true, 47 | }, 48 | "globals": { 49 | "angular": true, 50 | "$": true, 51 | "_": true, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/acme/datasource/repos/Repo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IHttpService } from 'angular'; 3 | import { BindAll } from 'lodash-decorators'; 4 | import { Application } from '@spinnaker/core'; 5 | import { Transition } from '@uirouter/core'; 6 | 7 | interface IRepoProps { 8 | app: Application; 9 | transition: Transition; 10 | repo: any; 11 | } 12 | 13 | interface IRepoState { 14 | repodata: any; 15 | } 16 | 17 | @BindAll() 18 | export class Repo extends React.Component { 19 | private $http: IHttpService; 20 | 21 | constructor(props: IRepoProps) { 22 | super(props); 23 | this.$http = props.transition.injector().get('$http'); 24 | this.fetchRepoData(props); 25 | this.state = { repodata: null }; 26 | } 27 | 28 | public componentWillReceiveProps(nextprops: IRepoProps) { 29 | this.setState({ repodata: null }); 30 | this.fetchRepoData(nextprops); 31 | } 32 | 33 | private fetchRepoData(props: IRepoProps) { 34 | const trans: Transition = props.transition; 35 | const reponame = trans.params().reponame; 36 | 37 | this.$http.get(`https://api.github.com/repos/spinnaker/${reponame}`) 38 | .then(resp => resp.data) 39 | .catch(() => ({ 40 | name: 'rate limit exceeded?', 41 | })) 42 | .then((repodata: any) => this.setState({ repodata: repodata })); 43 | } 44 | 45 | public render() { 46 | const { repodata } = this.state; 47 | 48 | if (!repodata) { 49 | return ( 50 |
Loading...
51 | ) 52 | } 53 | 54 | return ( 55 |
56 | Repo: {repodata.name} 57 | 58 |
59 |           {JSON.stringify(repodata, null, 2)}
60 |         
61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/acme/datasource/repos/SpinnakerRepos.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { UISref, UISrefActive, UIView } from '@uirouter/react'; 3 | import { BindAll } from 'lodash-decorators'; 4 | 5 | import { Application, ApplicationDataSource } from '@spinnaker/core'; 6 | 7 | interface ISpinnakerReposProps { 8 | app: Application; 9 | } 10 | 11 | interface ISpinnakerReposState { 12 | repos: any[]; 13 | } 14 | 15 | @BindAll() 16 | export class SpinnakerRepos extends React.Component { 17 | private dataSource: ApplicationDataSource; 18 | private unsubscribe: Function; 19 | 20 | constructor(props: ISpinnakerReposProps) { 21 | super(props); 22 | this.dataSource = this.props.app.getDataSource('repos'); 23 | this.state = { 24 | repos: this.dataSource.data, 25 | } 26 | } 27 | 28 | public reposChanged() { 29 | this.setState({ repos: this.dataSource.data }); 30 | } 31 | 32 | public componentDidMount() { 33 | this.unsubscribe = this.dataSource.onRefresh(null, () => this.reposChanged()); 34 | } 35 | 36 | public componentWillUnmount() { 37 | this.unsubscribe(); 38 | } 39 | 40 | private renderReposList(repos: any[]) { 41 | return ( 42 |
    43 | {repos.map(repo => ( 44 | 45 |
  • 46 | {repo.name} 47 |
  • 48 |
    49 | ))} 50 |
51 | ) 52 | } 53 | 54 | public render() { 55 | const { repos } = this.state; 56 | 57 | return ( 58 |
59 |

Spinnaker Repos

60 | 61 |
62 | {repos ? this.renderReposList(repos) :

Loading...

} 63 |
64 | 65 |
66 |
67 |
68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Customized Deck 2 | 3 | This example shows how to customize a few parts of Deck, the Spinnaker UI. 4 | 5 | This custom build of Deck uses the packages published to the public NPM registry at https://www.npmjs.com/org/spinnaker. 6 | To create a custom build of Deck for your organization, you should pull in `@spinnaker/core` and the published NPM package 7 | for your cloud provider. Having your cloud provider's UI published to NPM is a prerequisite to this custom build approach. 8 | 9 | This repository shows examples of three integration points: 10 | 11 | ### Header customization 12 | 13 | This customization replaces the main Spinnaker header with your own custom header 14 | 15 | ### Cloud provider override 16 | 17 | This customization replaces specific cloud provider templates with your own custom template. 18 | The provided example customizes the AWS server group and instance details pane. 19 | 20 | ### Custom Data Source 21 | 22 | This adds a custom data source to Spinnaker, which shows up as a separate tab for an application. 23 | The custom data source pulls Spinnaker repositories from Github and participates in the application data refresh lifecycle. 24 | 25 | ### Custom Help Contents 26 | 27 | The help contents on the Clusters page header is overridden. 28 | 29 | ### Home Page 30 | 31 | The home page has been slightly altered 32 | 33 | ### Custom Exception Handler 34 | 35 | A custom exception handler is stubbed in to show how you can propagate exceptions to an external service. 36 | 37 | You can test it out (if you're running AWS) by selecting a server group, then choosing "Throw an exception" from the Server Group Actions menu. 38 | 39 | ## Prerequisites 40 | 41 | Make sure that [node](http://nodejs.org/download/) and [yarn](https://yarnpkg.com/en/docs/install) are installed on your 42 | system. The minimum versions for each are listed in `package.json`. 43 | 44 | ## Quick Start 45 | 46 | Run the following commands (in the root directory) to get all dependencies installed in Deck and to start the server: 47 | 48 | * `yarn` 49 | * `yarn start` 50 | 51 | The app will start up on [localhost:9000](localhost:9000). 52 | 53 | ## Environment variables 54 | 55 | * `API_HOST` overrides the default Spinnaker API host. 56 | 57 | For example, `API_HOST=http://localhost:8084 yarn start` will run Deck 58 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const webpackCommon = require('./webpack.common'); 5 | const webpackConfig = webpackCommon(true); 6 | 7 | module.exports = function (config) { 8 | config.set({ 9 | autoWatch: true, 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: '', 13 | 14 | // testing framework to use (jasmine/mocha/qunit/...) 15 | frameworks: ['jasmine'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | {pattern: './karma-shim.js', watched: false} 20 | ], 21 | 22 | preprocessors: { 23 | './karma-shim.js': ['webpack'] 24 | }, 25 | 26 | webpack: webpackConfig, 27 | 28 | webpackMiddleware: { 29 | noInfo: true, 30 | }, 31 | 32 | customLaunchers: { 33 | Chrome_travis_ci: { 34 | base: 'Chrome', 35 | flags: ['--no-sandbox'] 36 | }, 37 | ChromeActive: { 38 | base: 'Chrome', 39 | flags: ['--override-plugin-power-saver-for-testing=0'] 40 | } 41 | }, 42 | 43 | plugins: [ 44 | require('karma-webpack'), 45 | require('karma-jasmine'), 46 | require('karma-chrome-launcher'), 47 | require('karma-junit-reporter'), 48 | require('karma-mocha-reporter'), 49 | require('karma-phantomjs-launcher'), 50 | ], 51 | 52 | // list of files / patterns to exclude 53 | exclude: [], 54 | 55 | // web server port 56 | port: 8081, 57 | 58 | browsers: [ 59 | 'PhantomJS' 60 | ], 61 | 62 | phantomjsLauncher: { 63 | // Have phantomjs exit if a ResourceError is encountered (useful if karma exits without killing phantom) 64 | exitOnResourceError: true 65 | }, 66 | 67 | colors: true, 68 | 69 | // level of logging 70 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 71 | logLevel: config.DEBUG, 72 | 73 | // jUnit Report output 74 | reporters: ['progress', 'mocha'], 75 | 76 | // the default configuration 77 | junitReporter: { 78 | outputFile: 'test-results.xml' 79 | }, 80 | 81 | mochaReporter: { 82 | ignoreSkipped: true, 83 | }, 84 | 85 | client: { 86 | captureConsole: true, 87 | }, 88 | 89 | browserNoActivityTimeout: 200000 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-react"], 3 | "rules": { 4 | "class-name": true, 5 | "comment-format": [ 6 | true, 7 | "check-space" 8 | ], 9 | "curly": true, 10 | "eofline": true, 11 | "forin": true, 12 | "indent": [ 13 | true, 14 | "spaces" 15 | ], 16 | "label-position": true, 17 | "max-line-length": false, 18 | "member-access": true, 19 | "member-ordering": [ 20 | true, 21 | "static-before-instance", 22 | "variables-before-functions" 23 | ], 24 | "no-arg": true, 25 | "no-bitwise": true, 26 | "no-console": [ 27 | true, 28 | "log", 29 | "warn", 30 | "debug", 31 | "info", 32 | "time", 33 | "timeEnd", 34 | "trace" 35 | ], 36 | "no-construct": true, 37 | "no-debugger": true, 38 | "no-duplicate-variable": true, 39 | "no-empty": false, 40 | "no-eval": true, 41 | "no-inferrable-types": true, 42 | "no-shadowed-variable": true, 43 | "no-string-literal": false, 44 | "no-switch-case-fall-through": true, 45 | "no-trailing-whitespace": true, 46 | "no-unused-expression": [true, "allow-fast-null-checks"], 47 | "no-use-before-declare": true, 48 | "no-var-keyword": true, 49 | "object-literal-sort-keys": false, 50 | "one-line": [ 51 | true, 52 | "check-catch", 53 | "check-else", 54 | "check-open-brace", 55 | "check-whitespace" 56 | ], 57 | "prefer-const": [ 58 | true, 59 | { 60 | "destructuring": "all" 61 | } 62 | ], 63 | "quotemark": [ 64 | true, 65 | "single", 66 | "avoid-escape", 67 | "jsx-double" 68 | ], 69 | "radix": true, 70 | "semicolon": [ 71 | "always" 72 | ], 73 | "triple-equals": [ 74 | true, 75 | "allow-null-check" 76 | ], 77 | "typedef": [ 78 | "call-signature", 79 | "property-declaration" 80 | ], 81 | "typedef-whitespace": [ 82 | true, 83 | { 84 | "call-signature": "nospace", 85 | "index-signature": "nospace", 86 | "parameter": "nospace", 87 | "property-declaration": "nospace", 88 | "variable-declaration": "nospace" 89 | } 90 | ], 91 | "variable-name": [ 92 | true, 93 | "ban-keywords", 94 | "allow-leading-underscore" 95 | ], 96 | "whitespace": [ 97 | true, 98 | "check-branch", 99 | "check-decl", 100 | "check-operator", 101 | "check-separator", 102 | "check-type" 103 | ], 104 | "jsx-no-multiline-js": false 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/acme/exceptionHandler.delegate.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: no-console */ 2 | 3 | import { IExceptionHandlerService, module } from 'angular'; 4 | import IInjectorService = angular.auto.IInjectorService; 5 | 6 | import { AUTHENTICATION_SERVICE, AuthenticationService, SETTINGS } from '@spinnaker/core'; 7 | 8 | export const EXCEPTION_HANDLER = 'spinnaker.acme.exception.handler'; 9 | module(EXCEPTION_HANDLER, [AUTHENTICATION_SERVICE]) 10 | .config(($provide: angular.auto.IProvideService) => { 11 | $provide.decorator('$exceptionHandler', ($injector: IInjectorService, 12 | $delegate: IExceptionHandlerService, 13 | authenticationService: AuthenticationService) => { 14 | 15 | const currentVersion = require('root/version.json'); 16 | return (exception: Error, cause: string) => { 17 | $delegate(exception, cause); 18 | const $http = $injector.get('$http'); // using injector access to avoid a circular dependency 19 | 20 | if (!SETTINGS.alert) { 21 | console.warn('Custom exception handler is not configured - please update the settings.js file to include alert configuration') 22 | } 23 | 24 | // Add whatever configuration you need (per environment) in the settings.js file to send a structured alert. 25 | // At Netflix, we send: 26 | // * the version (which is generated by build.gradle), 27 | // * the user agent, 28 | // * the current user, 29 | // * the current URL 30 | 31 | if (SETTINGS.alert) { 32 | 33 | let message: string = exception.message; 34 | if (!message) { 35 | try { 36 | message = JSON.stringify(exception); 37 | } catch (e) { 38 | message = '[No message available - could not convert exception to JSON string]'; 39 | } 40 | } 41 | 42 | if (message === 'IGNORE') { 43 | return; 44 | } 45 | 46 | const payload = { 47 | alertName: 'Spinnaker', 48 | details: { 49 | url: location.href, 50 | user: authenticationService.getAuthenticatedUser().name, 51 | version: currentVersion.version, 52 | userAgent: window.navigator.userAgent, 53 | }, 54 | exception: { 55 | classes: [exception.name || '[no name on exception]'], 56 | messages: [message], 57 | stackTraces: [exception.stack || '[no stacktrace available]'], 58 | callerClass: 'Spinnaker', 59 | callerMethod: '[see stack trace]', 60 | }, 61 | actions: [ 62 | { 63 | action: 'email', 64 | to: SETTINGS.alert.recipients, 65 | subject: SETTINGS.alert.subject || '[Spinnaker] Error in Deck', 66 | incidentKey: exception.message, 67 | } 68 | ], 69 | }; 70 | 71 | $http.post(SETTINGS.alert.url, payload); 72 | } 73 | }; 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var feedbackUrl = process.env.FEEDBACK_URL; 4 | var gateHost = process.env.API_HOST || 'http://localhost:8084'; 5 | var bakeryDetailUrl = process.env.BAKERY_DETAIL_URL || (gateHost + '/bakery/logs/{{context.region}}/{{context.status.resourceId}}'); 6 | var authEndpoint = process.env.AUTH_ENDPOINT || (gateHost + '/auth/user'); 7 | var fiatEnabled = process.env.FIAT_ENABLED === 'true' ? true : false; 8 | var entityTagsEnabled = process.env.ENTITY_TAGS_ENABLED === 'true' ? true : false; 9 | var liveCallsEnabled = process.env.LIVE_CALLS === 'true'; 10 | var defaultMetricStore = process.env.METRIC_STORE || 'atlas'; 11 | 12 | window.spinnakerSettings = { 13 | checkForUpdates: true, 14 | defaultProviders: ['aws', 'gce'], 15 | feedbackUrl: feedbackUrl, 16 | gateUrl: gateHost, 17 | bakeryDetailUrl: bakeryDetailUrl, 18 | authEndpoint: authEndpoint, 19 | pollSchedule: 30000, 20 | defaultTimeZone: process.env.TIMEZONE || 'America/Los_Angeles', // see http://momentjs.com/timezone/docs/#/data-utilities/ 21 | defaultCategory: 'serverGroup', 22 | defaultInstancePort: 80, 23 | providers: { 24 | aws: { 25 | defaults: { 26 | account: 'test', 27 | region: 'us-east-1', 28 | iamRole: 'BaseIAMRole', 29 | }, 30 | defaultSecurityGroups: [], 31 | loadBalancers: { 32 | // if true, VPC load balancers will be created as internal load balancers if the selected subnet has a purpose 33 | // tag that starts with "internal" 34 | inferInternalFlagFromSubnet: false, 35 | }, 36 | useAmiBlockDeviceMappings: false, 37 | }, 38 | gce: { 39 | defaults: { 40 | account: 'my-google-account', 41 | region: 'us-central1', 42 | zone: 'us-central1-f', 43 | }, 44 | associatePublicIpAddress: true, 45 | }, 46 | }, 47 | whatsNew: { 48 | gistId: '32526cd608db3d811b38', 49 | fileName: 'news.md', 50 | }, 51 | notifications: { 52 | email: { 53 | enabled: true, 54 | }, 55 | sms: { 56 | enabled: true, 57 | }, 58 | slack: { 59 | enabled: true, 60 | botName: 'spinnakerbot' 61 | } 62 | }, 63 | authEnabled: true, 64 | authTtl: 600000, 65 | gitSources: ['stash', 'github', 'bitbucket'], 66 | triggerTypes: ['git', 'pipeline', 'docker', 'cron', 'jenkins', 'travis'], 67 | canary: { 68 | liveCalls: liveCallsEnabled, 69 | metricsAccountName: 'my-google-account', 70 | storageAccountName: 'my-google-account', 71 | judge: 'dredd-v1.0', 72 | metricStore: defaultMetricStore, 73 | }, 74 | feature: { 75 | entityTags: entityTagsEnabled, 76 | fiatEnabled: fiatEnabled, 77 | pipelines: true, 78 | notifications: false, 79 | fastProperty: true, 80 | vpcMigrator: true, 81 | clusterDiff: false, 82 | roscoMode: false, 83 | chaosMonkey: true, 84 | // whether stages affecting infrastructure (like "Create Load Balancer") should be enabled or not 85 | infrastructureStages: process.env.INFRA_STAGES === 'enabled', 86 | jobs: false, 87 | snapshots: false, 88 | travis: false, 89 | }, 90 | }; 91 | -------------------------------------------------------------------------------- /src/index.deck: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spinnaker 5 | 6 | 7 | 8 | 9 | 10 | 68 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 |

Welcome to ACME Deploy!

102 |

From ACME, makers of

103 |

Powered by Spinnaker

104 |
105 | 106 | 126 | 127 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); 5 | 6 | const path = require('path'); 7 | const NODE_MODULE_PATH = path.join(__dirname, 'node_modules'); 8 | const fs = require('fs'); 9 | const inclusionPattern = [ 10 | path.resolve(__dirname, 'src'), 11 | ]; 12 | 13 | function configure(IS_TEST) { 14 | 15 | const config = { 16 | stats: 'errors-only', 17 | output: IS_TEST ? undefined : { 18 | path: path.join(__dirname, 'build', 'webpack', process.env.SPINNAKER_ENV || ''), 19 | filename: '[name].js', 20 | }, 21 | resolveLoader: IS_TEST ? {} : { 22 | modules: [ 23 | NODE_MODULE_PATH 24 | ], 25 | moduleExtensions: ['-loader'] 26 | }, 27 | resolve: { 28 | extensions: ['.json', '.js', '.jsx', '.ts', '.tsx', '.css', '.less', '.html'], 29 | modules: [ 30 | NODE_MODULE_PATH, 31 | path.join(__dirname, 'src'), 32 | ], 33 | alias: { 34 | 'coreImports': path.resolve(NODE_MODULE_PATH, '@spinnaker', 'core', 'src', 'presentation', 'less', 'imports', 'commonImports.less'), 35 | 'coreColors': path.resolve(NODE_MODULE_PATH, '@spinnaker', 'core', 'src', 'presentation', 'less', 'imports', 'colors.less'), 36 | 'root': __dirname, 37 | } 38 | }, 39 | module: { 40 | rules: [ 41 | {enforce: 'pre', test: /\.(spec\.)?tsx?$/, use: 'tslint-loader', include: inclusionPattern}, 42 | {enforce: 'pre', test: /\.(spec\.)?js$/, loader: 'eslint-loader', include: inclusionPattern}, 43 | {test: /\.(woff|otf|ttf|eot|svg|png|gif|ico)(.*)?$/, use: 'file-loader'}, 44 | {test: /\.json$/, loader: 'json-loader', include: inclusionPattern}, 45 | {test: require.resolve('jquery'), use: ['expose-loader?$', 'expose-loader?jQuery']}, 46 | { 47 | test: /\.tsx?$/, 48 | use: ['cache-loader', 'babel-loader', 'ts-loader'], 49 | include: inclusionPattern.concat(path.join(__dirname, 'test'))}, 50 | { 51 | test: /\.js$/, 52 | use: [ 'cache-loader', 'babel-loader', 'eslint-loader' ], 53 | include: inclusionPattern.concat(path.resolve(__dirname, 'settings.js')) 54 | }, 55 | { 56 | test: /\.less$/, 57 | use: ['cache-loader', 'style-loader', 'css-loader', 'less-loader'], 58 | include: inclusionPattern 59 | }, 60 | { 61 | test: /\.css$/, 62 | use: [ 'cache-loader', 'style-loader', 'css-loader' ], 63 | // include: inclusionPattern 64 | }, 65 | { 66 | test: /\.html$/, 67 | use: [ 'cache-loader', 'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname)) + '/', 'html-loader' ], 68 | include: inclusionPattern 69 | } 70 | ], 71 | }, 72 | devServer: IS_TEST ? { stats: 'errors-only' } : { 73 | port: process.env.DECK_PORT || 9000, 74 | host: process.env.DECK_HOST || 'localhost', 75 | https: process.env.DECK_HTTPS === 'true', 76 | stats: 'errors-only', 77 | }, 78 | watch: IS_TEST, 79 | externals: { 80 | 'cheerio': 'window', 81 | 'react/addons': 'react', 82 | 'react/lib/ExecutionEnvironment': 'react', 83 | 'react/lib/ReactContext': 'react', 84 | }, 85 | plugins: [ 86 | new ForkTsCheckerWebpackPlugin({ checkSyntacticErrors: true }), 87 | ], 88 | }; 89 | 90 | if (!IS_TEST) { 91 | config.entry = { 92 | settings: './settings.js', 93 | app: './src/app.ts', 94 | vendor: [ 95 | 'jquery', 'angular', 'angular-ui-bootstrap', 'source-sans-pro', 96 | 'angular-cache', 'angular-messages', 'angular-sanitize', 'bootstrap', 97 | 'clipboard', 'd3', 'jquery-ui', 'moment-timezone', 'rxjs', 'react', 'angular2react', 98 | 'react2angular', 'react-bootstrap', 'react-dom', 'react-ga', '@uirouter/visualizer', 'ui-select', 99 | '@uirouter/angularjs' 100 | ], 101 | spinnaker: ['@spinnaker/core', '@spinnaker/google', '@spinnaker/amazon'] 102 | }; 103 | 104 | config.plugins.push(...[ 105 | new webpack.optimize.CommonsChunkPlugin({ names: ['app', 'spinnaker', 'settings', 'vendor', 'manifest'] }), 106 | new CopyWebpackPlugin( 107 | [ 108 | { from: 'node_modules/@spinnaker/core/lib' }, 109 | { from: 'node_modules/@spinnaker/amazon/lib' }, 110 | { from: 'node_modules/@spinnaker/google/lib' }, 111 | ], 112 | { copyUnmodified: false, ignore: ['*.js', '*.ts', '*.map', 'index.html'] } 113 | ), 114 | new HtmlWebpackPlugin({ 115 | title: 'Spinnaker', 116 | template: './src/index.deck', 117 | favicon: 'src/favicon.ico', 118 | inject: true, 119 | }), 120 | new webpack.EnvironmentPlugin({ 121 | API_HOST: 'https://api-prestaging.spinnaker.mgmt.netflix.net', 122 | ENTITY_TAGS_ENABLED: true, 123 | FEEDBACK_URL: 'https://hootch.test.netflix.net/submit', 124 | FIAT_ENABLED: false, 125 | INFRA_STAGES: false, 126 | TIMEZONE: 'America/Los_Angeles', 127 | LIVE_CALLS: false, 128 | METRIC_STORE: 'atlas', 129 | }), 130 | ]); 131 | } 132 | 133 | // this is temporary and will be deprecated in WP3. moving forward, 134 | // loaders will individually need to accept this as an option. 135 | config.plugins.push(new webpack.LoaderOptionsPlugin({debug: !IS_TEST})); 136 | 137 | return config; 138 | } 139 | 140 | module.exports = configure; 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./dist/bundle.js", 3 | "name": "deck", 4 | "version": "0.0.0", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/acme/deck.git" 9 | }, 10 | "engines": { 11 | "node": ">=7.0.0", 12 | "npm": ">=3.10.8", 13 | "yarn": ">=0.21.3" 14 | }, 15 | "scripts": { 16 | "clean": "rimraf .awcache .happypack build node_modules transpiled", 17 | "build": "webpack --bail", 18 | "eslint": "eslint -c .eslintrc app/scripts/modules", 19 | "start": "sh ./start.sh", 20 | "start-dev-server": "webpack-dev-server --inline --hot", 21 | "test": "karma start", 22 | "tslint": "tslint --type-check --project tsconfig.json", 23 | "int": "protractor protractor.conf.js" 24 | }, 25 | "dependencies": { 26 | "@spinnaker/amazon": "0.0.46", 27 | "@spinnaker/core": "0.0.93", 28 | "@spinnaker/google": "0.0.1", 29 | "@uirouter/react-hybrid": "0.0.14", 30 | "@uirouter/rx": "0.4.5", 31 | "@uirouter/visualizer": "4.0.2", 32 | "Select2": "git://github.com/select2/select2.git#3.4.8", 33 | "angular": "~1.6.3", 34 | "angular-cache": "^4.6.0", 35 | "angular-cron-gen": "0.0.21", 36 | "angular-messages": "~1.6.3", 37 | "angular-sanitize": "~1.6.3", 38 | "angular-spinner": "^1.0.1", 39 | "angular-ui-bootstrap": "^2.5.0", 40 | "angular-ui-sortable": "^0.17.0", 41 | "angular2react": "^2.0.0", 42 | "babel-plugin-angularjs-annotate": "^0.8.0", 43 | "bootstrap": "3.3.7", 44 | "classnames": "^2.2.5", 45 | "clipboard": "^1.5.12", 46 | "commonmark": "^0.28.1", 47 | "d3-scale": "^1.0.4", 48 | "d3-shape": "^1.0.4", 49 | "diff-match-patch": "^1.0.0", 50 | "dompurify": "^0.8.5", 51 | "font-awesome": "^4.7.0", 52 | "fork-ts-checker-webpack-plugin": "^0.2.8", 53 | "formsy-react": "^0.19.2", 54 | "jasmine": "^2.6.0", 55 | "jquery": "2.1.4", 56 | "jquery-textcomplete": "1.6.1", 57 | "jquery-ui": "~1.10.5", 58 | "js-yaml": "^3.8.3", 59 | "lodash": "^4.16.1", 60 | "lodash-decorators": "^4.4.1", 61 | "moment-timezone": "^0.4.0", 62 | "n3-charts": "^2.0.18", 63 | "ngimport": "^0.6.0", 64 | "prop-types": "^15.5.10", 65 | "react": "~15.4.2", 66 | "react-bootstrap": "^0.31.0", 67 | "react-dom": "~15.4.2", 68 | "react-ga": "^2.1.2", 69 | "react-select": "^1.0.0-rc.4", 70 | "react-sortable-hoc": "^0.6.8", 71 | "react-virtualized": "^9.9.0", 72 | "react-virtualized-select": "^3.1.0", 73 | "react2angular": "^1.1.3", 74 | "reflect-metadata": "^0.1.9", 75 | "rxjs": "^5.4.2", 76 | "select2-bootstrap-css": "git://github.com/t0m/select2-bootstrap-css.git#v1.3.1", 77 | "source-map": "^0.4.4", 78 | "source-sans-pro": "^2.0.10", 79 | "sourcemapped-stacktrace": "^1.1.7", 80 | "spel2js": "^0.2.1", 81 | "spin.js": "git://github.com/fgnass/spin.js.git#2.3.1", 82 | "ui-select": "^0.19.6" 83 | }, 84 | "devDependencies": { 85 | "@types/angular": "1.6.26", 86 | "@types/angular-mocks": "1.5.10", 87 | "@types/angular-ui-bootstrap": "^0.13.41", 88 | "@types/clipboard": "^1.5.33", 89 | "@types/d3": "^4.5.0", 90 | "@types/dompurify": "^0.0.29", 91 | "@types/enzyme": "^2.7.9", 92 | "@types/jasmine": "2.5.47", 93 | "@types/jquery": "^2.0.34", 94 | "@types/js-yaml": "3.5.30", 95 | "@types/lodash": "^4.14.64", 96 | "@types/moment-timezone": "^0.2.34", 97 | "@types/node": "7.0.5", 98 | "@types/prop-types": "^15.5.1", 99 | "@types/react": "15.0.35", 100 | "@types/react-bootstrap": "^0.0.49", 101 | "@types/react-dom": "^15.5.0", 102 | "@types/react-ga": "^2.1.1", 103 | "@types/react-select": "^1.0.45", 104 | "@types/react-sortable-hoc": "^0.6.0", 105 | "@types/react-virtualized-select": "^3.0.1", 106 | "@types/webpack": "^2.2.4", 107 | "@types/webpack-env": "^1.13.0", 108 | "angular-mocks": "1.6.4", 109 | "angulartics": "^1.1.2", 110 | "angulartics-google-analytics": "^0.2.0", 111 | "babel-core": "^6.21.1", 112 | "babel-loader": "^7.0.0", 113 | "babel-preset-env": "^1.6.0", 114 | "cache-loader": "^1.0.3", 115 | "copy-webpack-plugin": "^4.0.1", 116 | "css-loader": "^0.28.1", 117 | "envify-loader": "^0.1.0", 118 | "enzyme": "^2.8.2", 119 | "eslint": "^3.17.0", 120 | "eslint-loader": "^1.6.3", 121 | "exports-loader": "^0.6.3", 122 | "expose-loader": "^0.7.1", 123 | "express": "^4.15.3", 124 | "file-loader": "^0.11.1", 125 | "html-loader": "0.4.5", 126 | "html-webpack-plugin": "^2.28.0", 127 | "imports-loader": "^0.7.0", 128 | "istanbul-instrumenter-loader": "^2.0.0", 129 | "jasmine-core": "2.6.1", 130 | "json-loader": "^0.5.4", 131 | "karma": "^1.4.1", 132 | "karma-chrome-launcher": "^2.0.0", 133 | "karma-jasmine": "^1.1.0", 134 | "karma-junit-reporter": "^1.1.0", 135 | "karma-mocha-reporter": "^2.1.0", 136 | "karma-phantomjs-launcher": "^1.0.4", 137 | "karma-sourcemap-loader": "^0.3.7", 138 | "karma-webpack": "^2.0.2", 139 | "less": "^2.7.2", 140 | "less-loader": "^4.0.2", 141 | "loader-utils": "^1.1.0", 142 | "md5": "^2.2.1", 143 | "ngtemplate-loader": "^1.3.1", 144 | "node-libs-browser": "^2.0.0", 145 | "phantomjs-prebuilt": "^2.1.14", 146 | "postcss-loader": "^2.0.8", 147 | "react-addons-test-utils": "^15.5.1", 148 | "react-test-renderer": "^15.5.4", 149 | "rimraf": "^2.5.4", 150 | "source-map-loader": "^0.2.1", 151 | "style-loader": "^0.17.0", 152 | "thread-loader": "^1.1.2", 153 | "ts-loader": "^2.3.7", 154 | "tslint": "^5.2.0", 155 | "tslint-loader": "^3.4.3", 156 | "tslint-react": "^3.0.0", 157 | "typescript": "~2.5.2", 158 | "url-loader": "^0.5.7", 159 | "webpack": "^2.5.1", 160 | "webpack-dev-server": "^2.3.0" 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/acme/cloudprovider/instance.detailsTemplate.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Custom!!!

4 | 5 |

Could not find instance {{instanceIdNotFound}}.

6 | Back to search results 7 |
8 |
9 | 10 |

Custom!!!

11 | 12 |
13 |
14 | 16 | 17 | 18 |
19 |

20 | 21 |

22 |
23 | 24 |
25 |
26 | 28 | 29 | 30 |
31 |
32 | 33 |

34 | {{instance ? instance.instanceId : instanceIdNotFound }} 35 |

36 |
37 |
38 |
39 | 56 | 64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
Launched
72 |
{{instance.launchTime | timestamp}}
73 |
(Unknown)
74 |
In
75 |
76 | 77 | {{instance.placement.availabilityZone || '(Unknown)'}} 78 |
79 |
Type
80 |
{{instance.instanceType || '(Unknown)'}}
81 |
Server Group
82 |
83 | {{instance.serverGroup}} 87 |
88 |
VPC
89 |
90 |
Subnet
91 |
92 |
Image ID
93 |
{{instance.imageId}}
94 |
95 |
96 | 97 |

98 | No health metrics found for this instance 99 |

100 |

101 | Starting 102 |

103 | 104 |
105 |
{{metric.type | robotToHuman}}
106 |
107 |
108 | 109 | 110 | {{metric.state | robotToHuman}} 111 | 112 | 113 | Health Check 114 | | 115 | Status 116 | 117 |
118 |
119 | 120 |
121 |
122 | 123 |
124 |
125 |
126 |
127 | 128 |
129 |
Private DNS Name
130 |
131 | {{instance.privateDnsName}} 132 | 136 | 137 |
138 |
Public DNS Name
139 |
140 | {{instance.publicDnsName}} 141 | 145 | 146 |
147 |
Private IP Address
148 |
149 | {{instance.privateIpAddress}} 150 | 154 | 155 |
156 |
Permanent IP Address
157 |
158 | {{ip}} 159 | 163 | 164 |
165 |
Public IP Address
166 |
167 | {{instance.publicIpAddress}} 168 | 172 | 173 |
174 |
175 |
176 | 177 | 184 | 185 | 186 |
No tags associated with this server
187 |
188 |
{{tag.key}}
189 |
{{tag.value}}
190 |
191 |
192 | 193 |
    194 |
  • 195 | 196 |
  • 197 |
198 |
199 | 200 |
201 |
202 |
203 |
204 |

Instance not found.

205 |
206 |
207 |
208 |
209 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Netflix 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /src/acme/cloudprovider/serverGroup.detailsTemplate.html: -------------------------------------------------------------------------------- 1 |
3 | 4 |

Custom!!!

5 | 6 |
7 |
8 | 10 | 11 | 12 |
13 |

14 | 15 |

16 |
17 | 18 | 19 |
20 |
21 | 23 | 24 | 25 |
26 |
27 | 28 |

29 | {{ctrl.serverGroup.name}} 30 | 31 | 32 | 41 | 42 | 43 |

44 |
45 |
46 |
47 | 76 | 84 |
85 |
86 |
87 |
88 |
89 | Disabled {{ctrl.disabledDate | timestamp}} 90 |
91 |
92 | 93 | 94 |
95 |
Created
96 |
{{ctrl.serverGroup.createdTime | timestamp}}
97 | 98 | 99 | 103 | 104 |
In
105 |
106 | 107 | 108 | {{ctrl.serverGroup.region}} 109 |
110 |
VPC
111 |
112 |
Subnet
113 |
{{ctrl.serverGroup.subnetType || 'None (EC2 Classic)'}}
114 |
Zones
115 |
116 |
    117 |
  • {{zone}}
  • 118 |
119 |
120 |
121 |
122 | 123 |
125 |
Min/Max
126 |
{{ctrl.serverGroup.asg.desiredCapacity}}
127 |
Current
128 |
{{ctrl.serverGroup.instances.length}}
129 |
130 |
132 |
Min
133 |
{{ctrl.serverGroup.asg.minSize}}
134 |
Desired
135 |
{{ctrl.serverGroup.asg.desiredCapacity}}
136 |
Max
137 |
{{ctrl.serverGroup.asg.maxSize}}
138 |
Current
139 |
{{ctrl.serverGroup.instances.length}}
140 |
141 |
142 | Resize Server Group 143 |
144 |
145 | 146 |
147 |
148 | 149 |
151 |
Instances
152 |
153 | 154 |
155 |
156 |
157 | 158 |
159 |
Name
160 |
{{ctrl.serverGroup.launchConfig.launchConfigurationName}}
161 | 162 |
Image ID
163 |
{{ctrl.serverGroup.launchConfig.imageId}}
164 | 165 |
Image Name
166 |
{{ctrl.image.imageLocation}}
167 | 168 |
Base Image Name
169 |
{{ctrl.image.baseImage}}
170 | 171 |
Instance Type
172 |
{{ctrl.serverGroup.launchConfig.instanceType}}
173 | 174 |
IAM Profile
175 |
{{ctrl.serverGroup.launchConfig.iamInstanceProfile}}
176 | 177 |
Instance Monitoring
178 |
{{ctrl.serverGroup.launchConfig.instanceMonitoring.enabled ? 'enabled' : 'disabled'}}
179 | 180 |
Spot Price
181 |
{{ctrl.serverGroup.launchConfig.spotPrice}}
182 | 183 |
Key Name
184 |
{{ctrl.serverGroup.launchConfig.keyName}}
185 | 186 |
Kernel ID
187 |
{{ctrl.serverGroup.launchConfig.kernelId}}
188 | 189 |
Ramdisk ID
190 |
{{ctrl.serverGroup.launchConfig.ramdiskId}}
191 | 192 |
User Data
193 |
Show User Data
194 |
[none]
195 |
196 |
197 | 198 | 205 | Edit Security Groups 206 | 207 | 208 | 209 | 211 | 213 | Scaling Processes 214 | 215 | 216 |
    217 |
  • 218 | 219 | {{process.name}} 220 | 221 |
    222 | Suspended {{process.suspensionDate | timestamp}} 223 |
    224 |
  • 225 |
226 | Edit Scaling Processes 227 |
228 |
229 | 230 | 231 | 233 | Scaling Policies 234 | 235 | 236 |
237 | Some scaling processes are disabled that may prevent scaling policies from working. 238 |
239 | 243 | 245 |
246 |
247 | 248 | 249 | 251 | Scheduled Actions 252 | 253 | 254 | 255 |

Note: Schedules are evaluated in UTC.

256 |

No Scheduled Actions are configured for this server group.

257 | Edit Scheduled Actions 258 |
259 |
260 | 261 |
No tags associated with this server group
262 |
263 |
{{tag.key}}
264 |
{{tag.value}}
265 |
266 |
267 | 268 |
269 |
Job
270 |
{{ctrl.serverGroup.buildInfo.jenkins.name}}
271 |
Package
272 |
{{ctrl.serverGroup.buildInfo.package_name}}
273 |
Build
274 |
{{ctrl.serverGroup.buildInfo.jenkins.number}}
275 |
Commit
276 |
{{ctrl.truncateCommitHash()}}
277 |
Version
278 |
{{ctrl.serverGroup.buildInfo.version}}
279 |
Build Link
280 |
{{ctrl.buildJenkinsLink()}}
281 |
282 |
283 | 284 | 285 | Edit Advanced Settings 286 | 287 | 288 |
    289 |
  • 290 | 291 |
  • 292 |
293 |
294 |
295 |
296 | --------------------------------------------------------------------------------