├── .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 | Help
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 |
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 |
23 |
24 |
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 |
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 |
17 |
18 |
19 |
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 |
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 |
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 |
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 |
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------