Adding Angular to UI
Angular was added to organize the FE of the UI application better. In doing so I have rebuilt the build scripts, added a routing mechanism for the Go server to route and serve the compiled TS pages from Angular. Change-Id: I7ae2cacfd90372fa536b1639e5b54a8da786e2cd
47
.gitignore
vendored
@ -5,12 +5,49 @@ tools/*node*
|
||||
|
||||
# Generated binaries
|
||||
bin
|
||||
dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/build
|
||||
*.exe
|
||||
|
||||
# Developer IDE
|
||||
.idea/
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# Node modules
|
||||
web/node_modules
|
||||
node_modules
|
||||
|
||||
# Sphinx build venv
|
||||
.tox/
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
34
Makefile
@ -9,12 +9,13 @@ SHELL=/bin/bash
|
||||
GIT_VERSION=$(shell git describe --match 'v*' --always)
|
||||
|
||||
TOOLBINDIR := tools/bin
|
||||
WEBDIR := web
|
||||
WEBDIR := client
|
||||
LINTER := $(TOOLBINDIR)/golangci-lint
|
||||
LINTER_CONFIG := .golangci.yaml
|
||||
JSLINTER_BIN := $(realpath tools)/node-v12.16.3/bin
|
||||
NPM := $(JSLINTER_BIN)/npm
|
||||
NPX := $(JSLINTER_BIN)/npx
|
||||
NODEJS_BIN := $(realpath tools)/node-v12.16.3/bin
|
||||
NPM := $(NODEJS_BIN)/npm
|
||||
NPX := $(NODEJS_BIN)/npx
|
||||
NG := $(NODEJS_BIN)/ng
|
||||
|
||||
# docker
|
||||
DOCKER_MAKE_TARGET := build
|
||||
@ -46,7 +47,7 @@ LD_FLAGS= '-X opendev.org/airship/airshipui/internal/commands.version=$(GIT_VERS
|
||||
GO_FLAGS := -ldflags=$(LD_FLAGS)
|
||||
BUILD_DIR := bin
|
||||
|
||||
# Find all main.go files under cmd, excluding airshipui itself (which is the octant wrapper)
|
||||
# Find all main.go files under cmd, excluding airshipui itself
|
||||
EXAMPLE_NAMES := $(notdir $(subst /main.go,,$(wildcard examples/*/main.go)))
|
||||
EXAMPLES := $(addprefix $(BUILD_DIR)/, $(EXAMPLE_NAMES))
|
||||
MAIN := $(BUILD_DIR)/airshipui
|
||||
@ -66,9 +67,11 @@ DIRS = internal
|
||||
RECURSIVE_DIRS = $(addprefix ./, $(addsuffix /..., $(DIRS)))
|
||||
|
||||
.PHONY: build
|
||||
build: $(MAIN) $(NPX)
|
||||
build: $(NPX) $(MAIN)
|
||||
$(MAIN): FORCE
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(NODEJS_BIN)"; $(NPM) install) && cd ..
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(NODEJS_BIN)"; $(NG) build) && cd ..
|
||||
go build -o $(MAIN)$(EXTENSION) $(GO_FLAGS) cmd/$(@F)/main.go
|
||||
|
||||
FORCE:
|
||||
@ -77,6 +80,9 @@ FORCE:
|
||||
examples: $(EXAMPLES)
|
||||
$(EXAMPLES): FORCE
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
./tools/install_npm
|
||||
cd $(WEBDIR) && npm install && cd ..
|
||||
cd $(WEBDIR) && ng build && cd ..
|
||||
go build -o $@$(EXTENSION) $(GO_FLAGS) examples/$(@F)/main.go
|
||||
|
||||
.PHONY: install-octant-plugins
|
||||
@ -84,9 +90,11 @@ install-octant-plugins:
|
||||
@mkdir -p $(OCTANT_PLUGINSTUB_DIR)
|
||||
cp $(addsuffix $(EXTENSION), $(BUILD_DIR)/octant) $(OCTANT_PLUGINSTUB_DIR)
|
||||
|
||||
|
||||
.PHONY: install-npm-modules
|
||||
install-npm-modules: $(NPX)
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(JSLINTER_BIN)"; $(NPM) install) && cd ..
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(NODEJS_BIN)"; $(NPM) install) && cd ..
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test: lint
|
||||
@ -166,16 +174,16 @@ docs:
|
||||
.PHONY: env
|
||||
|
||||
.PHONY: lint
|
||||
lint: tidy $(LINTER) $(NPX)
|
||||
lint: tidy $(LINTER)
|
||||
@echo "Performing linting steps..."
|
||||
@echo "Running whitespace linting step..."
|
||||
@./tools/whitespace_linter
|
||||
@echo "Running golangci-lint linting step..."
|
||||
$(LINTER) run --config $(LINTER_CONFIG)
|
||||
@echo "Running eslint for JavaScript linting step..."
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(JSLINTER_BIN)"; $(NPX) --no-install eslint js) && cd ..
|
||||
@echo "Running eslint for HTML linting step..."
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(JSLINTER_BIN)"; $(NPX) --no-install eslint --ext .html .) && cd ..
|
||||
@echo "Installing NPM & running client linting step..."
|
||||
./tools/install_npm
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(NODEJS_BIN)"; $(NPM) install) && cd ..
|
||||
cd $(WEBDIR) && (PATH="$(PATH):$(NODEJS_BIN)"; $(NG) build) && cd ..
|
||||
@echo "Linting completed successfully"
|
||||
|
||||
.PHONY: tidy
|
||||
@ -190,7 +198,7 @@ $(LINTER):
|
||||
|
||||
$(NPX):
|
||||
@mkdir -p $(TOOLBINDIR)
|
||||
./tools/install_js_linter
|
||||
./tools/install_npm
|
||||
|
||||
# add-copyright is a utility to add copyright header to missing files
|
||||
.PHONY: add-copyright
|
||||
|
18
client/.browserslistrc
Normal file
@ -0,0 +1,18 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
16
client/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
27
client/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# AirshipuiUi
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.2.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
126
client/angular.json
Normal file
@ -0,0 +1,126 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"airshipui-ui": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/airshipui-ui",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/airship-icon.svg",
|
||||
"src/assets",
|
||||
{ "glob": "**/*", "input": "node_modules/monaco-editor/min", "output": "./assets/monaco/" }
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css",
|
||||
"node_modules/ngx-toastr/toastr.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "airshipui-ui:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "airshipui-ui:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "airshipui-ui:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "airshipui-ui:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "airshipui-ui:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
"defaultProject": "airshipui-ui"
|
||||
}
|
36
client/e2e/protractor.conf.js
Normal file
@ -0,0 +1,36 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: StacktraceOption.PRETTY
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
23
client/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('airshipui-ui app is running!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
11
client/e2e/src/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl) as Promise<unknown>;
|
||||
}
|
||||
|
||||
getTitleText(): Promise<string> {
|
||||
return element(by.css('app-root .content span')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
14
client/e2e/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
32
client/karma.conf.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/airshipui-ui'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
13761
client/package-lock.json
generated
Normal file
52
client/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "airshipui-ui",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~10.0.3",
|
||||
"@angular/cdk": "^10.0.1",
|
||||
"@angular/common": "~10.0.3",
|
||||
"@angular/compiler": "~10.0.3",
|
||||
"@angular/core": "~10.0.3",
|
||||
"@angular/flex-layout": "^9.0.0-beta.31",
|
||||
"@angular/forms": "~10.0.3",
|
||||
"@angular/material": "^10.0.1",
|
||||
"@angular/platform-browser": "~10.0.3",
|
||||
"@angular/platform-browser-dynamic": "~10.0.3",
|
||||
"@angular/router": "~10.0.3",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"ngx-toastr": "^13.0.0",
|
||||
"rxjs": "~6.5.5",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.1000.2",
|
||||
"@angular/cli": "~10.0.2",
|
||||
"@angular/compiler-cli": "~10.0.3",
|
||||
"@types/jasmine": "~3.5.0",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "^12.11.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"eslint-plugin-html": "^6.0.2",
|
||||
"jasmine-core": "~3.5.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"karma": "~5.0.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~3.3.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~3.9.5"
|
||||
}
|
||||
}
|
1
client/src/airship-icon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="0.57in" height="0.5in" viewBox="0 0 40.89 36"><defs><style>.cls-1{fill:#65c7c2;}.cls-2{fill:#141f47;}</style></defs><title>Airship_Icon</title><path class="cls-1" d="M57.17,70.87l-2.34-1.33a2.78,2.78,0,0,1-1.36-1.89l-.88-4.48a40.81,40.81,0,0,1-2.47-5.59l-7.28,12.7a2.1,2.1,0,0,0,.77,2.87,2,2,0,0,0,1,.28h11.5a.74.74,0,0,0,.65-.39l1.07-1.88A3.41,3.41,0,0,1,57.17,70.87Z" transform="translate(-42.56 -37.43)"/><path class="cls-2" d="M64.83,38.48A2.1,2.1,0,0,0,62,37.72a2.05,2.05,0,0,0-.76.76L52.13,54.21a1.86,1.86,0,0,1,1.11-.75c2.95-.78,8.9-1.82,13.51.84A14.46,14.46,0,0,1,72,60h4.81a.64.64,0,0,1,.59.29Z" transform="translate(-42.56 -37.43)"/><path class="cls-1" d="M83.19,70.28,77.5,60.44a.76.76,0,0,1-.16.81l-3,3.06L83.45,71C83.37,70.73,83.29,70.51,83.19,70.28Z" transform="translate(-42.56 -37.43)"/><path class="cls-2" d="M62,62.52A42.66,42.66,0,0,1,52,54.4l-.16.29a1.49,1.49,0,0,0,0,1.24,25.13,25.13,0,0,0,2.76,6.49l.94,4.84a.68.68,0,0,0,.36.52l2.34,1.33a.72.72,0,0,0,1-.22l0-.07L60,67.56a15,15,0,0,0,6.83,1.33L69.22,73a.76.76,0,0,0,1,.29.81.81,0,0,0,.36-.45L72,68.24a6.14,6.14,0,0,0,.84-.23A1.94,1.94,0,0,0,74,67.07,42.25,42.25,0,0,1,62,62.52Z" transform="translate(-42.56 -37.43)"/><path class="cls-1" d="M82.05,73.3l-8.41-3.7-1.07,3.8h8.8A1.14,1.14,0,0,0,82.05,73.3Z" transform="translate(-42.56 -37.43)"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
0
client/src/app/airship/airship.component.css
Normal file
1
client/src/app/airship/airship.component.html
Normal file
@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
25
client/src/app/airship/airship.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AirshipComponent } from './airship.component';
|
||||
|
||||
describe('AirshipComponent', () => {
|
||||
let component: AirshipComponent;
|
||||
let fixture: ComponentFixture<AirshipComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AirshipComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AirshipComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
15
client/src/app/airship/airship.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-airship',
|
||||
templateUrl: './airship.component.html',
|
||||
styleUrls: ['./airship.component.css']
|
||||
})
|
||||
export class AirshipComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
<button mat-raised-button color="accent" (click)="generateIso()">Generate ISO</button>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BareMetalComponent } from './bare-metal.component';
|
||||
|
||||
describe('BareMetalComponent', () => {
|
||||
let component: BareMetalComponent;
|
||||
let fixture: ComponentFixture<BareMetalComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BareMetalComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BareMetalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
26
client/src/app/airship/bare-metal/bare-metal.component.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {WebsocketMessage} from '../../../services/websocket/models/websocket-message/websocket-message';
|
||||
import {WebsocketService} from '../../../services/websocket/websocket.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bare-metal',
|
||||
templateUrl: './bare-metal.component.html',
|
||||
styleUrls: ['./bare-metal.component.css']
|
||||
})
|
||||
export class BareMetalComponent implements OnInit {
|
||||
|
||||
private message: WebsocketMessage;
|
||||
|
||||
constructor(private websocketService: WebsocketService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void { }
|
||||
|
||||
generateIso(): void {
|
||||
this.message = new WebsocketMessage();
|
||||
this.message.type = 'airshipctl';
|
||||
this.message.component = 'baremetal';
|
||||
this.message.subComponent = 'generateISO';
|
||||
this.websocketService.sendMessage(this.message);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
<div id="DocOverviewDiv" style="height: 60vh;overflow: hidden;border:1px solid grey"></div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DocumentOverviewComponent } from './document-overview.component';
|
||||
|
||||
describe('DocumentOverviewComponent', () => {
|
||||
let component: DocumentOverviewComponent;
|
||||
let fixture: ComponentFixture<DocumentOverviewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DocumentOverviewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DocumentOverviewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-document-overview',
|
||||
templateUrl: './document-overview.component.html',
|
||||
styleUrls: ['./document-overview.component.css']
|
||||
})
|
||||
export class DocumentOverviewComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<button type="button" class="btn btn-info" id="DocPullBtn" (click)="documentPull()" style="width: 150px;">Document Pull</button>
|
||||
<p>Response to Pull: {{obby}}</p>
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DocumentPullComponent } from './document-pull.component';
|
||||
|
||||
describe('DocumentPullComponent', () => {
|
||||
let component: DocumentPullComponent;
|
||||
let fixture: ComponentFixture<DocumentPullComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DocumentPullComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DocumentPullComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { WebsocketService } from '../../../../services/websocket/websocket.service';
|
||||
import { WebsocketMessage } from '../../../../services/websocket/models/websocket-message/websocket-message';
|
||||
|
||||
@Component({
|
||||
selector: 'app-document-pull',
|
||||
templateUrl: './document-pull.component.html',
|
||||
styleUrls: ['./document-pull.component.css']
|
||||
})
|
||||
export class DocumentPullComponent implements OnInit {
|
||||
|
||||
obby: string;
|
||||
|
||||
constructor(private websocketService: WebsocketService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.subject.subscribe(message => {
|
||||
if (message.type === 'airshipctl' && message.component === 'document' && message.subComponent === 'docPull') {
|
||||
this.obby = JSON.stringify(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
documentPull(): void {
|
||||
const websocketMessage = new WebsocketMessage();
|
||||
websocketMessage.type = 'airshipctl';
|
||||
websocketMessage.component = 'document';
|
||||
websocketMessage.subComponent = 'docPull';
|
||||
this.websocketService.sendMessage(websocketMessage);
|
||||
}
|
||||
|
||||
}
|
5
client/src/app/airship/document/document.component.html
Normal file
@ -0,0 +1,5 @@
|
||||
<nav mat-tab-nav-bar>
|
||||
<a mat-tab-link routerLink="overview" (click)="activeLink = 'overview'" [active]="activeLink == 'overview'">Document Overview</a>
|
||||
<a mat-tab-link routerLink="pull" (click)="activeLink = 'pull'" [active]="activeLink == 'pull'">Document Pull</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
25
client/src/app/airship/document/document.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DocumentComponent } from './document.component';
|
||||
|
||||
describe('DocumentComponent', () => {
|
||||
let component: DocumentComponent;
|
||||
let fixture: ComponentFixture<DocumentComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DocumentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DocumentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
16
client/src/app/airship/document/document.component.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-document',
|
||||
templateUrl: './document.component.html',
|
||||
styleUrls: ['./document.component.css']
|
||||
})
|
||||
export class DocumentComponent implements OnInit {
|
||||
|
||||
activeLink = 'overview';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
}
|
47
client/src/app/app-routing.module.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { DashboardsComponent } from './dashboards/dashboards.component';
|
||||
import { AirshipComponent } from './airship/airship.component';
|
||||
import { BareMetalComponent } from './airship/bare-metal/bare-metal.component';
|
||||
import { DocumentComponent } from './airship/document/document.component';
|
||||
import { DocumentOverviewComponent } from './airship/document/document-overview/document-overview.component';
|
||||
import { DocumentPullComponent } from './airship/document/document-pull/document-pull.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'airship',
|
||||
component: AirshipComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'bare-metal',
|
||||
component: BareMetalComponent
|
||||
}, {
|
||||
path: 'documents',
|
||||
component: DocumentComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'overview',
|
||||
component: DocumentOverviewComponent
|
||||
}, {
|
||||
path: 'pull',
|
||||
component: DocumentPullComponent
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
path: 'dashboard',
|
||||
component: DashboardsComponent
|
||||
}, {
|
||||
path: '',
|
||||
component: HomeComponent
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {
|
||||
|
||||
}
|
67
client/src/app/app.component.css
Normal file
@ -0,0 +1,67 @@
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidenav-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.sidenav-content {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header{
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel:not([class*='mat-elevation-z']) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.mat-sidenav-content {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-padding {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.page-body {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.toolbar-footer {
|
||||
flex-shrink: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.toolbar-header {
|
||||
flex-shrink: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
font-size: 12px;
|
||||
}
|
71
client/src/app/app.component.html
Normal file
@ -0,0 +1,71 @@
|
||||
<div class="main-container">
|
||||
<mat-sidenav-container class="sidenav-container">
|
||||
<mat-sidenav class="sidenav-content" #snav [mode]="'side'">
|
||||
<mat-nav-list>
|
||||
<svg width="249" height="46">
|
||||
<use xlink:href="assets/logo/airship-horizontal-logo.svg#Layer_1"></use>
|
||||
</svg>
|
||||
<span *ngFor="let item of menu">
|
||||
<span *ngIf="item.children && item.children.length > 0">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<div fxLayout="row" fxLayoutAlign="center center" >
|
||||
<div *ngIf="item.iconName" class="icon-container" fxLayoutAlign="center center">
|
||||
<mat-icon svgIcon="{{ item.iconName }}"></mat-icon>
|
||||
</div>
|
||||
{{ item.displayName }}
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<span *ngFor="let child of item.children">
|
||||
<mat-list-item *ngIf="!child.external" routerLink="{{ child.route }}">
|
||||
<div fxLayout="row" fxLayoutAlign="center center" >
|
||||
<div *ngIf="child.iconName" class="icon-container" fxLayoutAlign="center center">
|
||||
<mat-icon svgIcon="{{ child.iconName }}"></mat-icon>
|
||||
</div>
|
||||
{{ child.displayName }}
|
||||
</div>
|
||||
</mat-list-item>
|
||||
<mat-list-item *ngIf="child.external" (click)="openLink(child.route)">
|
||||
<div fxLayout="row" fxLayoutAlign="center center" >
|
||||
<div *ngIf="child.iconName" class="icon-container" fxLayoutAlign="center center">
|
||||
<mat-icon svgIcon=launch></mat-icon>
|
||||
</div>
|
||||
{{ child.displayName }}
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</span>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</span>
|
||||
<span *ngIf="!item.children || item.children.length === 0">
|
||||
<mat-list-item routerLink="{{ item.route }}">
|
||||
<div fxLayout="row" fxLayoutAlign="center center">
|
||||
<div *ngIf="item.iconName" class="icon-container" fxLayoutAlign="center center">
|
||||
<mat-icon svgIcon="{{ item.iconName }}"></mat-icon>
|
||||
</div>
|
||||
{{item.displayName}}
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</span>
|
||||
</span>
|
||||
</mat-nav-list>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content>
|
||||
<mat-toolbar color="primary" class="toolbar-header">
|
||||
<button mat-icon-button (click)="snav.toggle()"><mat-icon svgIcon="list"></mat-icon></button>
|
||||
<span class="spacer"></span>
|
||||
<button mat-icon-button><mat-icon svgIcon="account"></mat-icon></button>
|
||||
</mat-toolbar>
|
||||
<router-outlet></router-outlet>
|
||||
<span class="page-body"></span>
|
||||
<mat-toolbar class="toolbar-footer">
|
||||
<h3>Airship UI © {{ this.currentYear }}</h3>
|
||||
<span class="spacer"></span>
|
||||
<h3>Version: {{ this.version }}</h3>
|
||||
</mat-toolbar>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
</div>
|
31
client/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'airshipui-ui'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect('airshipui-ui').toEqual('airshipui-ui');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('airshipui-ui app is running!');
|
||||
});
|
||||
});
|
73
client/src/app/app.component.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NavInterface } from './models/nav.interface';
|
||||
import { environment } from '../environments/environment';
|
||||
import { IconService } from '../services/icon/icon.service';
|
||||
import { NotificationService } from '../services/notification/notification.service';
|
||||
import {WebsocketService} from '../services/websocket/websocket.service';
|
||||
import {Dashboard} from '../services/websocket/models/websocket-message/dashboard/dashboard';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
currentYear: number;
|
||||
version: string;
|
||||
|
||||
menu: NavInterface [] = [
|
||||
{
|
||||
displayName: 'Airship',
|
||||
iconName: 'airplane',
|
||||
children: [
|
||||
{
|
||||
displayName: 'Bare Metal',
|
||||
route: 'airship/bare-metal',
|
||||
iconName: 'server'
|
||||
}, {
|
||||
displayName: 'Documents',
|
||||
route: 'airship/documents/overview',
|
||||
iconName: 'doc'
|
||||
}]
|
||||
}, {
|
||||
displayName: 'Dashboards',
|
||||
iconName: 'speed',
|
||||
}];
|
||||
|
||||
constructor(private iconService: IconService,
|
||||
private notificationService: NotificationService,
|
||||
private websocketService: WebsocketService) {
|
||||
this.currentYear = new Date().getFullYear();
|
||||
this.version = environment.version;
|
||||
this.websocketService.subject.subscribe(message => {
|
||||
if (message.type === 'airshipui' && message.component === 'initialize' && message.dashboards !== undefined) {
|
||||
this.updateDashboards(message.dashboards);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.iconService.registerIcons();
|
||||
}
|
||||
|
||||
updateDashboards(dashboards: Dashboard[]): void {
|
||||
if (this.menu[1].children === undefined) {
|
||||
this.menu[1].children = [];
|
||||
}
|
||||
dashboards.forEach((dashboard) => {
|
||||
const navInterface = new NavInterface();
|
||||
navInterface.displayName = dashboard.name;
|
||||
navInterface.route = dashboard.baseURL;
|
||||
navInterface.external = true;
|
||||
this.menu[1].children.push(navInterface);
|
||||
});
|
||||
}
|
||||
|
||||
openLink(url: string): void {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
65
client/src/app/app.module.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AirshipComponent } from './airship/airship.component';
|
||||
import { DashboardsComponent } from './dashboards/dashboards.component';
|
||||
import { HomeComponent } from './home/home.component';
|
||||
import { BareMetalComponent } from './airship/bare-metal/bare-metal.component';
|
||||
import { DocumentComponent } from './airship/document/document.component';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { DocumentOverviewComponent } from './airship/document/document-overview/document-overview.component';
|
||||
import { DocumentPullComponent } from './airship/document/document-pull/document-pull.component';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { WebsocketService } from '../services/websocket/websocket.service';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AirshipComponent,
|
||||
DashboardsComponent,
|
||||
HomeComponent,
|
||||
BareMetalComponent,
|
||||
DocumentComponent,
|
||||
DocumentOverviewComponent,
|
||||
DocumentPullComponent,
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FlexLayoutModule,
|
||||
FormsModule,
|
||||
MatToolbarModule,
|
||||
MatSidenavModule,
|
||||
MatListModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
MatTableModule,
|
||||
MatCheckboxModule,
|
||||
MatExpansionModule,
|
||||
HttpClientModule,
|
||||
RouterModule,
|
||||
MatTabsModule,
|
||||
ToastrModule.forRoot()
|
||||
],
|
||||
providers: [WebsocketService],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
0
client/src/app/dashboards/dashboards.component.css
Normal file
1
client/src/app/dashboards/dashboards.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>dashboards works!</p>
|
25
client/src/app/dashboards/dashboards.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DashboardsComponent } from './dashboards.component';
|
||||
|
||||
describe('DashboardsComponent', () => {
|
||||
let component: DashboardsComponent;
|
||||
let fixture: ComponentFixture<DashboardsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DashboardsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DashboardsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
15
client/src/app/dashboards/dashboards.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboards',
|
||||
templateUrl: './dashboards.component.html',
|
||||
styleUrls: ['./dashboards.component.css']
|
||||
})
|
||||
export class DashboardsComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
0
client/src/app/home/home.component.css
Normal file
0
client/src/app/home/home.component.html
Normal file
25
client/src/app/home/home.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HomeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
15
client/src/app/home/home.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.css']
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
8
client/src/app/models/nav.interface.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export class NavInterface {
|
||||
displayName: string;
|
||||
disabled?: boolean;
|
||||
iconName?: string;
|
||||
route?: string;
|
||||
external?: boolean;
|
||||
children?: NavInterface[];
|
||||
}
|
0
client/src/assets/.gitkeep
Normal file
1
client/src/assets/icons/account.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 5v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2zm12 4c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3zm-9 8c0-2 4-3.1 6-3.1s6 1.1 6 3.1v1H6v-1z"/></svg>
|
After Width: | Height: | Size: 314 B |
1
client/src/assets/icons/airplane.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><g><path d="M22,16v-2l-8.5-5V3.5C13.5,2.67,12.83,2,12,2s-1.5,0.67-1.5,1.5V9L2,14v2l8.5-2.5V19L8,20.5L8,22l4-1l4,1l0-1.5L13.5,19 v-5.5L22,16z"/><path d="M0,0h24v24H0V0z" fill="none"/></g></svg>
|
After Width: | Height: | Size: 309 B |
1
client/src/assets/icons/doc.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>
|
After Width: | Height: | Size: 264 B |
1
client/src/assets/icons/launch.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
After Width: | Height: | Size: 268 B |
1
client/src/assets/icons/list.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>
|
After Width: | Height: | Size: 224 B |
1
client/src/assets/icons/server.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 13H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zM7 19c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM20 3H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM7 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>
|
After Width: | Height: | Size: 395 B |
1
client/src/assets/icons/speed.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44zm-9.79 6.84a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/></svg>
|
After Width: | Height: | Size: 364 B |
0
web/images/Airship_Logo_Horizontal_2Color_RGB.svg → client/src/assets/logo/airship-horizontal-logo.svg
Executable file → Normal file
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
4
client/src/environments/environment.prod.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
version: 'Development'
|
||||
};
|
17
client/src/environments/environment.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
version: 'Development'
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
13
client/src/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Airship UI</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="airship-icon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
12
client/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
63
client/src/polyfills.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
16
client/src/services/icon/icon.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { IconService } from './icon.service';
|
||||
|
||||
describe('IconService', () => {
|
||||
let service: IconService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(IconService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
24
client/src/services/icon/icon.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Icons } from 'src/services/icon/icons.enum';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class IconService {
|
||||
constructor(
|
||||
private matIconRegistry: MatIconRegistry,
|
||||
private domSanitizer: DomSanitizer
|
||||
) { }
|
||||
|
||||
public registerIcons(): void {
|
||||
this.loadIcons(Object.values(Icons), '../assets/icons');
|
||||
}
|
||||
|
||||
private loadIcons(iconKeys: string[], iconUrl: string): void {
|
||||
iconKeys.forEach(key => {
|
||||
this.matIconRegistry.addSvgIcon(key, this.domSanitizer.bypassSecurityTrustResourceUrl(`${iconUrl}/${key}.svg`));
|
||||
});
|
||||
}
|
||||
}
|
9
client/src/services/icon/icons.enum.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export enum Icons {
|
||||
account = 'account',
|
||||
airplane = 'airplane',
|
||||
doc = 'doc',
|
||||
list = 'list',
|
||||
server = 'server',
|
||||
speed = 'speed',
|
||||
launch = 'launch'
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
describe('NotificationService', () => {
|
||||
let service: NotificationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(NotificationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
24
client/src/services/notification/notification.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { WebsocketService } from '../websocket/websocket.service';
|
||||
import { WebsocketMessage } from '../websocket/models/websocket-message/websocket-message';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
|
||||
constructor(private toastrService: ToastrService,
|
||||
private websocketService: WebsocketService) {
|
||||
this.websocketService.subject.subscribe(message => {
|
||||
this.printIfToast(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
printIfToast(message: WebsocketMessage): void {
|
||||
if (message.error !== undefined && message.error !== null) {
|
||||
this.toastrService.error(message.error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { Dashboard } from './dashboard';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new Dashboard()).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
import {Executable} from './executable/executable';
|
||||
|
||||
export class Dashboard {
|
||||
name: string;
|
||||
baseURL: string;
|
||||
path: string;
|
||||
isProxied: boolean;
|
||||
executable: Executable;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { Executable } from './executable';
|
||||
|
||||
describe('Executable', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new Executable()).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
export class Executable {
|
||||
autoStart: boolean;
|
||||
filePath: string;
|
||||
args: string[];
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { WebsocketMessage } from './websocket-message';
|
||||
|
||||
describe('WebsocketMessage', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new WebsocketMessage()).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
import {Dashboard} from './dashboard/dashboard';
|
||||
|
||||
export class WebsocketMessage {
|
||||
type: string;
|
||||
component: string;
|
||||
subComponent: string;
|
||||
timestamp: number;
|
||||
dashboards: Dashboard[];
|
||||
error: string;
|
||||
fade: boolean;
|
||||
html: string;
|
||||
isAuthenticated: boolean;
|
||||
message: string;
|
||||
data: JSON;
|
||||
yaml: string;
|
||||
}
|
16
client/src/services/websocket/websocket.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WebsocketService } from './websocket.service';
|
||||
|
||||
describe('WebsocketService', () => {
|
||||
let service: WebsocketService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(WebsocketService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
194
client/src/services/websocket/websocket.service.ts
Normal file
@ -0,0 +1,194 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { WebsocketMessage } from './models/websocket-message/websocket-message';
|
||||
import { Subject } from 'rxjs';
|
||||
import {Dashboard} from './models/websocket-message/dashboard/dashboard';
|
||||
import {Executable} from './models/websocket-message/dashboard/executable/executable';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebsocketService {
|
||||
|
||||
public subject = new Subject<WebsocketMessage>();
|
||||
private ws: WebSocket;
|
||||
private timeout: number;
|
||||
|
||||
private static convertIncomingMessageJsonToObject(incomingMessage: string): WebsocketMessage {
|
||||
const json = JSON.parse(incomingMessage);
|
||||
const messageTransform = new WebsocketMessage();
|
||||
if (typeof json.type === 'string') {
|
||||
messageTransform.type = json.type;
|
||||
}
|
||||
if (typeof json.component === 'string') {
|
||||
messageTransform.component = json.component;
|
||||
}
|
||||
if (typeof json.subComponent === 'string') {
|
||||
messageTransform.subComponent = json.subComponent;
|
||||
}
|
||||
if (typeof json.timestamp === 'number') {
|
||||
messageTransform.timestamp = json.timestamp;
|
||||
}
|
||||
if (typeof json.error === 'string') {
|
||||
messageTransform.error = json.error;
|
||||
}
|
||||
if (typeof json.fade === 'boolean') {
|
||||
messageTransform.fade = json.fade;
|
||||
}
|
||||
if (typeof json.html === 'string') {
|
||||
messageTransform.html = json.html;
|
||||
}
|
||||
if (typeof json.isAuthenticated === 'boolean') {
|
||||
messageTransform.isAuthenticated = json.isAuthenticated;
|
||||
}
|
||||
if (typeof json.message === 'string') {
|
||||
messageTransform.message = json.message;
|
||||
}
|
||||
if (typeof json.data === 'string' && JSON.parse(json.data)) {
|
||||
messageTransform.data = JSON.parse(json.data);
|
||||
}
|
||||
if (typeof json.yaml === 'string') {
|
||||
messageTransform.yaml = json.yaml;
|
||||
}
|
||||
if (typeof json.dashboards !== undefined && Array.isArray(json.dashboards)) {
|
||||
json.dashboards.forEach(dashboard => {
|
||||
const dashboardTransform = new Dashboard();
|
||||
if (typeof dashboard.name === 'string') {
|
||||
dashboardTransform.name = dashboard.name;
|
||||
}
|
||||
if (typeof dashboard.baseURL === 'string') {
|
||||
dashboardTransform.baseURL = dashboard.baseURL;
|
||||
}
|
||||
if (typeof dashboard.path === 'string') {
|
||||
dashboardTransform.path = dashboard.path;
|
||||
}
|
||||
if (typeof dashboard.isProxied === 'boolean') {
|
||||
dashboardTransform.isProxied = dashboard.isProxied;
|
||||
}
|
||||
if (typeof dashboard.executable === 'object') {
|
||||
const executableTransform = new Executable();
|
||||
if (typeof dashboard.executable.autoStart === 'boolean') {
|
||||
executableTransform.autoStart = dashboard.executable.autoStart;
|
||||
}
|
||||
if (typeof dashboard.executable.filePath === 'string') {
|
||||
executableTransform.filePath = dashboard.executable.filePath;
|
||||
}
|
||||
if (typeof dashboard.executable.args !== undefined && Array.isArray(typeof dashboard.executable.args)) {
|
||||
dashboard.executable.args.forEach(arg => {
|
||||
if (typeof arg === 'string') {
|
||||
executableTransform.args.push(arg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (messageTransform.dashboards === undefined) {
|
||||
messageTransform.dashboards = [];
|
||||
}
|
||||
messageTransform.dashboards.push(dashboardTransform);
|
||||
});
|
||||
}
|
||||
return messageTransform;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.register();
|
||||
}
|
||||
|
||||
public sendMessage(message: WebsocketMessage): void {
|
||||
message.timestamp = new Date().getTime();
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
private register(): void {
|
||||
if (this.ws !== undefined && this.ws !== null) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
this.ws = new WebSocket('ws://localhost:8080/ws');
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
this.subject.next(WebsocketService.convertIncomingMessageJsonToObject(event.data));
|
||||
};
|
||||
|
||||
this.ws.onerror = (event) => {
|
||||
console.log('Web Socket received an error: ', event);
|
||||
};
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('Websocket established');
|
||||
const json = { type: 'airshipui', component: 'initialize' };
|
||||
this.ws.send(JSON.stringify(json));
|
||||
// start up the keepalive so the websocket-message stays open
|
||||
this.keepAlive();
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
this.close(event.code);
|
||||
};
|
||||
}
|
||||
|
||||
private close(code): void {
|
||||
switch (code) {
|
||||
case 1000:
|
||||
console.log('Web Socket Closed: Normal closure: ', code);
|
||||
break;
|
||||
case 1001:
|
||||
console.log('Web Socket Closed: An endpoint is "going away", such as a server going down or a browser having navigated away from a page:', code);
|
||||
break;
|
||||
case 1002:
|
||||
console.log('Web Socket Closed: terminating the connection due to a protocol error: ', code);
|
||||
break;
|
||||
case 1003:
|
||||
console.log('Web Socket Closed: terminating the connection because it has received a type of data it cannot accept: ', code);
|
||||
break;
|
||||
case 1004:
|
||||
console.log('Web Socket Closed: Reserved. The specific meaning might be defined in the futur: ', code);
|
||||
break;
|
||||
case 1005:
|
||||
console.log('Web Socket Closed: No status code was actually present: ', code);
|
||||
break;
|
||||
case 1006:
|
||||
console.log('Web Socket Closed: The connection was closed abnormally: ', code);
|
||||
break;
|
||||
case 1007:
|
||||
console.log('Web Socket Closed: terminating the connection because it has received data within a message that was not ' +
|
||||
'consistent with the type of the message: ', code);
|
||||
break;
|
||||
case 1008:
|
||||
console.log('Web Socket Closed: terminating the connection because it has received a message that "violates its policy": ', code);
|
||||
break;
|
||||
case 1009:
|
||||
console.log('Web Socket Closed: terminating the connection because it has received a message that is too big for it to ' +
|
||||
'process: ', code);
|
||||
break;
|
||||
case 1010:
|
||||
console.log('Web Socket Closed: client is terminating the connection because it has expected the server to negotiate ' +
|
||||
'one or more extension, but the server didn\'t return them in the response message of the WebSocket handshake: ', code);
|
||||
break;
|
||||
case 1011:
|
||||
console.log('Web Socket Closed: server is terminating the connection because it encountered an unexpected condition that' +
|
||||
' prevented it from fulfilling the request: ', code);
|
||||
break;
|
||||
case 1015:
|
||||
console.log('Web Socket Closed: closed due to a failure to perform a TLS handshake (e.g., the server certificate can\'t be' +
|
||||
' verified): ', code);
|
||||
break;
|
||||
default:
|
||||
console.log('Web Socket Closed: unknown error code: ', code);
|
||||
break;
|
||||
}
|
||||
|
||||
this.ws = null;
|
||||
}
|
||||
|
||||
private keepAlive(): void {
|
||||
if (this.ws !== undefined && this.ws !== null && this.ws.readyState !== this.ws.CLOSED) {
|
||||
// clear the previously set timeout
|
||||
window.clearTimeout(this.timeout);
|
||||
window.clearInterval(this.timeout);
|
||||
const json = { type: 'airshipui', component: 'keepalive' };
|
||||
this.ws.send(JSON.stringify(json));
|
||||
this.timeout = window.setTimeout(this.keepAlive, 60000);
|
||||
}
|
||||
}
|
||||
}
|
15
client/src/styles.css
Normal file
@ -0,0 +1,15 @@
|
||||
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
|
||||
|
||||
body {
|
||||
font-family: Roboto, Arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.basic-container {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
font-size: 8pt;
|
||||
float: right;
|
||||
}
|
25
client/src/test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
15
client/tsconfig.app.json
Normal file
@ -0,0 +1,15 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
20
client/tsconfig.base.json
Normal file
@ -0,0 +1,20 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
20
client/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience.
|
||||
It is not intended to be used to perform a compilation.
|
||||
|
||||
To learn more about this file see: https://angular.io/config/solution-tsconfig.
|
||||
*/
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./e2e/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
18
client/tsconfig.spec.json
Normal file
@ -0,0 +1,18 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
152
client/tslint.json
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
"parameters",
|
||||
"statements"
|
||||
]
|
||||
},
|
||||
"array-type": false,
|
||||
"arrow-return-shorthand": true,
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
],
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": {
|
||||
"options": [
|
||||
"spaces"
|
||||
]
|
||||
},
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": {
|
||||
"options": [
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"space-before-function-paren": {
|
||||
"options": {
|
||||
"anonymous": "never",
|
||||
"asyncArrow": "always",
|
||||
"constructor": "never",
|
||||
"method": "never",
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature"
|
||||
],
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
},
|
||||
{
|
||||
"call-signature": "onespace",
|
||||
"index-signature": "onespace",
|
||||
"parameter": "onespace",
|
||||
"property-declaration": "onespace",
|
||||
"variable-declaration": "onespace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable-name": {
|
||||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
},
|
||||
"whitespace": {
|
||||
"options": [
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
]
|
||||
}
|
0
examples/authentication/main.go
Executable file → Normal file
170
examples/authentication/templates/index.html
Executable file → Normal file
@ -1,86 +1,86 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AirshipUI Test {{.Title}}</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
</head>
|
||||
<script>
|
||||
function testIt() {
|
||||
document.getElementById("AuthBtn").disabled = true;
|
||||
console.log(window.location.pathname);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", window.location.pathname);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
xhr.onload = function () {
|
||||
if (this.status === 201) {
|
||||
document.cookie = "airshipUI=" + xhr.response + "expires=" + new Date().getUTCDate;
|
||||
console.log(JSON.parse(xhr.response));
|
||||
} else {
|
||||
console.log({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
document.getElementById("OutputDiv").innerHTML = '<span style="color:red"><h2>☠ ID or Password is incorrect please try again ☠</h2></span>';
|
||||
document.getElementById("AuthBtn").disabled = false;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
};
|
||||
|
||||
let json = JSON.stringify({"id": document.getElementById("ID").value, "password": document.getElementById("Passwd").value});
|
||||
console.log(json)
|
||||
xhr.send(json);
|
||||
}
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<h1>Airship UI Test {{.Title}}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Id:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="ID">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Password:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" id="Passwd">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button id="AuthBtn" onclick="testIt()">Test It!</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="OutputDiv"></div>
|
||||
<h2>⚠ Warning! ⚠</h2>
|
||||
<p>This is a {{.Title}} test page is only intended as an example for how to use {{.Title}} with AirshipUI.</p>
|
||||
<p>The System will return the following HTML status codes and responses</p>
|
||||
<ul>
|
||||
{{if eq .Title "Basic Auth"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has sent an xauth token header to AirshipUI.</li>
|
||||
{{else if eq .Title "Cookie"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has set a cookie and sent the cookie contents to AirshipUI.</li>
|
||||
{{else if eq .Title "OAuth"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has set a JWT (JSON Web Token) and sent the JWT contents to AirshipUI.</li>
|
||||
{{end}}
|
||||
<li>400: Bad request. There was an error sending the system the authentication request, most likely bad JSON.</li>
|
||||
<li>401: Unauthorized. Bad id / password attempt.</li>
|
||||
<li>403: Forbidden. The id / password combination was correct but the id is not allowed for the resource.</li>
|
||||
<li>500: Internal Server Error. There was a processing error on the back end.</li>
|
||||
</ul>
|
||||
</body>
|
||||
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AirshipUI Test {{.Title}}</title>
|
||||
<link rel="icon" href="data:;base64,=">
|
||||
</head>
|
||||
<script>
|
||||
function testIt() {
|
||||
document.getElementById("AuthBtn").disabled = true;
|
||||
console.log(window.location.pathname);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", window.location.pathname);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
xhr.onload = function () {
|
||||
if (this.status === 201) {
|
||||
document.cookie = "airshipUI=" + xhr.response + "expires=" + new Date().getUTCDate;
|
||||
console.log(JSON.parse(xhr.response));
|
||||
} else {
|
||||
console.log({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
document.getElementById("OutputDiv").innerHTML = '<span style="color:red"><h2>☠ ID or Password is incorrect please try again ☠</h2></span>';
|
||||
document.getElementById("AuthBtn").disabled = false;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
reject({
|
||||
status: this.status,
|
||||
statusText: xhr.statusText
|
||||
});
|
||||
};
|
||||
|
||||
let json = JSON.stringify({"id": document.getElementById("ID").value, "password": document.getElementById("Passwd").value});
|
||||
console.log(json)
|
||||
xhr.send(json);
|
||||
}
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<h1>Airship UI Test {{.Title}}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Id:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="ID">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Password:</b>
|
||||
</td>
|
||||
<td>
|
||||
<input type="password" id="Passwd">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<button id="AuthBtn" onclick="testIt()">Test It!</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="OutputDiv"></div>
|
||||
<h2>⚠ Warning! ⚠</h2>
|
||||
<p>This is a {{.Title}} test page is only intended as an example for how to use {{.Title}} with AirshipUI.</p>
|
||||
<p>The System will return the following HTML status codes and responses</p>
|
||||
<ul>
|
||||
{{if eq .Title "Basic Auth"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has sent an xauth token header to AirshipUI.</li>
|
||||
{{else if eq .Title "Cookie"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has set a cookie and sent the cookie contents to AirshipUI.</li>
|
||||
{{else if eq .Title "OAuth"}}
|
||||
<li>201: Created. The password attempt was successful and the backend has set a JWT (JSON Web Token) and sent the JWT contents to AirshipUI.</li>
|
||||
{{end}}
|
||||
<li>400: Bad request. There was an error sending the system the authentication request, most likely bad JSON.</li>
|
||||
<li>401: Unauthorized. Bad id / password attempt.</li>
|
||||
<li>403: Forbidden. The id / password combination was correct but the id is not allowed for the resource.</li>
|
||||
<li>500: Internal Server Error. There was a processing error on the back end.</li>
|
||||
</ul>
|
||||
</body>
|
||||
|
||||
</html>
|
0
examples/octant/main.go
Executable file → Normal file
9
go.mod
@ -5,10 +5,13 @@ go 1.13
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v0.0.7
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vmware-tanzu/octant v0.12.0
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f
|
||||
opendev.org/airship/airshipctl v0.0.0-20200518155418-7276dd68d8d0
|
||||
)
|
||||
|
||||
|
22
go.sum
@ -158,6 +158,7 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@ -455,9 +456,11 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
||||
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
@ -615,6 +618,8 @@ github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
|
||||
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
|
||||
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@ -638,6 +643,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
@ -668,6 +675,7 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@ -698,6 +706,8 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -735,6 +745,8 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
@ -748,6 +760,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -777,6 +791,9 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -817,6 +834,7 @@ golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDq
|
||||
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff h1:XdBG6es/oFDr1HwaxkxgVve7NB281QhxgK/i4voubFs=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
@ -851,6 +869,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
@ -887,6 +906,8 @@ gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -979,6 +1000,7 @@ sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5
|
||||
sigs.k8s.io/kustomize/api v0.3.1 h1:oqMIXvS6tFEUVuKIRUKDa05eC4Hh+cb9JYg8Zhp2d24=
|
||||
sigs.k8s.io/kustomize/api v0.3.1/go.mod h1:A+ATnlHqzictQfQC1q3KB/T6MSr0UWQsrrLxMWkge2E=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
|
0
internal/configs/configs.go
Executable file → Normal file
@ -11,15 +11,13 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package configs_test
|
||||
package configs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
"opendev.org/airship/airshipui/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,22 +26,62 @@ const (
|
||||
invalidTestFile string = "testdata/airshipui_invalid.json"
|
||||
)
|
||||
|
||||
// DummyExecutableConfig returns a populated Executable struct
|
||||
func dummyExecutableConfig() *Executable {
|
||||
return &Executable{
|
||||
AutoStart: true,
|
||||
Filepath: "/fake/path/to/executable",
|
||||
Args: []string{
|
||||
"--fakeflag",
|
||||
"fakevalue",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DummyDashboardsConfig returns an array of populated Dashboard structs
|
||||
func dummyDashboardsConfig() []Dashboard {
|
||||
e := dummyExecutableConfig()
|
||||
return []Dashboard{
|
||||
{
|
||||
Name: "dummy_dashboard",
|
||||
BaseURL: "http://dummyhost",
|
||||
Path: "fake/login/path",
|
||||
Executable: e,
|
||||
},
|
||||
{
|
||||
Name: "dummy_plugin_no_dash",
|
||||
Executable: e,
|
||||
},
|
||||
{
|
||||
Name: "dummy_dashboard_no_exe",
|
||||
BaseURL: "http://dummyhost",
|
||||
Path: "fake/login/path",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dummyAuthMethodConfig() *AuthMethod {
|
||||
return &AuthMethod{
|
||||
URL: "http://fake.auth.method.com/auth",
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetUIConfig(t *testing.T) {
|
||||
conf := configs.Config{
|
||||
Dashboards: testutil.DummyDashboardsConfig(),
|
||||
AuthMethod: testutil.DummyAuthMethodConfig(),
|
||||
conf := Config{
|
||||
Dashboards: dummyDashboardsConfig(),
|
||||
AuthMethod: dummyAuthMethodConfig(),
|
||||
}
|
||||
|
||||
err := configs.SetUIConfig(testFile)
|
||||
err := SetUIConfig(testFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, conf, configs.UIConfig)
|
||||
assert.Equal(t, conf, UIConfig)
|
||||
|
||||
err = configs.SetUIConfig(invalidTestFile)
|
||||
err = SetUIConfig(invalidTestFile)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFileNotFound(t *testing.T) {
|
||||
err := configs.SetUIConfig(fakeFile)
|
||||
err := SetUIConfig(fakeFile)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
1
internal/integrations/ctl/airshipctl.go
Executable file → Normal file
@ -34,7 +34,6 @@ var (
|
||||
|
||||
// CTLFunctionMap is a function map for the CTL functions that is referenced in the webservice
|
||||
var CTLFunctionMap = map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{
|
||||
configs.CTLConfig: HandleConfigRequest,
|
||||
configs.Baremetal: HandleBaremetalRequest,
|
||||
configs.Document: HandleDocumentRequest,
|
||||
}
|
||||
|
0
internal/integrations/ctl/baremetal.go
Executable file → Normal file
@ -18,14 +18,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
"opendev.org/airship/airshipui/util/utiltest"
|
||||
)
|
||||
|
||||
func TestHandleDefaultBaremetalRequest(t *testing.T) {
|
||||
initCTL(t)
|
||||
html, err := GetBaremetalHTML()
|
||||
require.NoError(t, err)
|
||||
utiltest.InitConfig(t)
|
||||
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
@ -39,10 +37,11 @@ func TestHandleDefaultBaremetalRequest(t *testing.T) {
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: html,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
assert.Equal(t, expected.Type, response.Type)
|
||||
assert.Equal(t, expected.Component, response.Component)
|
||||
assert.Equal(t, expected.SubComponent, response.SubComponent)
|
||||
}
|
||||
|
||||
func TestHandleUnknownBaremetalSubComponent(t *testing.T) {
|
||||
|
@ -1,180 +0,0 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
)
|
||||
|
||||
// HandleConfigRequest will flop between requests so we don't have to have them all mapped as function calls
|
||||
func HandleConfigRequest(request configs.WsMessage) configs.WsMessage {
|
||||
response := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: request.SubComponent,
|
||||
}
|
||||
|
||||
var err error
|
||||
var message string
|
||||
switch request.SubComponent {
|
||||
case configs.GetDefaults:
|
||||
response.HTML, err = getConfigHTML()
|
||||
case configs.SetContext:
|
||||
message, err = setContext(request)
|
||||
case configs.SetCluster:
|
||||
message, err = setCluster(request)
|
||||
case configs.SetCredential:
|
||||
message, err = setCredential(request)
|
||||
default:
|
||||
err = fmt.Errorf("Subcomponent %s not found", request.SubComponent)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
response.Error = err.Error()
|
||||
} else {
|
||||
response.Message = message
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// GetCluster gets cluster information from the airshipctl config
|
||||
func (c *Client) getCluster() []*config.Cluster {
|
||||
return c.settings.Config.GetClusters()
|
||||
}
|
||||
|
||||
// getClusterTableRows turns an array of cluster into html table rows
|
||||
func getClusterTableRows() string {
|
||||
info := c.getCluster()
|
||||
|
||||
var rows string
|
||||
for _, config := range info {
|
||||
// TODO: all rows are editable, probably shouldn't be
|
||||
rows += "<tr><td><div contenteditable=true>" +
|
||||
config.Bootstrap + "</div></td><td><div contenteditable=true>" +
|
||||
config.NameInKubeconf + "</div></td><td><div contenteditable=true>" +
|
||||
config.ManagementConfiguration + "</div></td><td>" +
|
||||
config.KubeCluster().LocationOfOrigin + "</td><td><div contenteditable=true>" +
|
||||
config.KubeCluster().Server + "</div></td><td><div contenteditable=true>" +
|
||||
config.KubeCluster().CertificateAuthority + "</div></td><td>" +
|
||||
"<button type=\"button\" class=\"btn btn-success\" onclick=\"saveConfig(this)\">Save</button></td></tr>"
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// GetContext gets cluster information from the airshipctl config
|
||||
func (c *Client) getContext() []*config.Context {
|
||||
return c.settings.Config.GetContexts()
|
||||
}
|
||||
|
||||
// getContextTableRows turns an array of contexts into html table rows
|
||||
func getContextTableRows() string {
|
||||
info := c.getContext()
|
||||
|
||||
var rows string
|
||||
for _, context := range info {
|
||||
// TODO: all rows are editable, probably shouldn't be
|
||||
rows += "<tr><td><div contenteditable=true>" +
|
||||
context.NameInKubeconf + "</div></td><td><div contenteditable=true>" +
|
||||
context.Manifest + "</div></td><td>" +
|
||||
context.KubeContext().LocationOfOrigin + "</td><td><div contenteditable=true>" +
|
||||
context.KubeContext().Cluster + "</div></td><td><div contenteditable=true>" +
|
||||
context.KubeContext().AuthInfo + "</div></td><td>" +
|
||||
"<button type=\"button\" class=\"btn btn-success\" onclick=\"saveConfig(this)\">Save</button></td></tr>"
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// GetCredential gets user credentials from the airshipctl config
|
||||
func (c *Client) getCredential() []*config.AuthInfo {
|
||||
authinfo, err := c.settings.Config.GetAuthInfos()
|
||||
if err != nil {
|
||||
return []*config.AuthInfo{}
|
||||
}
|
||||
|
||||
return authinfo
|
||||
}
|
||||
|
||||
// getContextTableRows turns an array of contexts into html table rows
|
||||
func getCredentialTableRows() string {
|
||||
info := c.getCredential()
|
||||
|
||||
var rows string
|
||||
for _, credential := range info {
|
||||
// TODO: all rows are editable, probably shouldn't be
|
||||
rows += "<tr><td>" +
|
||||
credential.KubeAuthInfo().LocationOfOrigin + "</td><td><div contenteditable=true>" +
|
||||
credential.KubeAuthInfo().Username + "</div></td><td>" +
|
||||
"<button type=\"button\" class=\"btn btn-success\" onclick=\"saveConfig(this)\">Save</button></td></tr>"
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func getConfigHTML() (string, error) {
|
||||
return getHTML("/templates/config.html", ctlPage{
|
||||
ClusterRows: getClusterTableRows(),
|
||||
ContextRows: getContextTableRows(),
|
||||
CredentialRows: getCredentialTableRows(),
|
||||
Title: "Config",
|
||||
Version: getAirshipCTLVersion(),
|
||||
})
|
||||
}
|
||||
|
||||
// SetCluster will take ui cluster info, translate them into CTL commands and send a response back to the UI
|
||||
func setCluster(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetCluster(request.ClusterOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
message = fmt.Sprintf("Cluster %q of type %q modified.",
|
||||
request.ClusterOptions.Name, request.ClusterOptions.ClusterType)
|
||||
} else {
|
||||
message = fmt.Sprintf("Cluster %q of type %q created.",
|
||||
request.ClusterOptions.Name, request.ClusterOptions.ClusterType)
|
||||
}
|
||||
|
||||
return message, err
|
||||
}
|
||||
|
||||
// SetContext will take ui context info, translate them into CTL commands and send a response back to the UI
|
||||
func setContext(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetContext(request.ContextOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
message = fmt.Sprintf("Context %q modified.", request.ContextOptions.Name)
|
||||
} else {
|
||||
message = fmt.Sprintf("Context %q created.", request.ContextOptions.Name)
|
||||
}
|
||||
|
||||
return message, err
|
||||
}
|
||||
|
||||
// SetContext will take ui context info, translate them into CTL commands and send a response back to the UI
|
||||
func setCredential(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetAuthInfo(request.AuthInfoOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
message = fmt.Sprintf("Credential %q modified.", request.AuthInfoOptions.Name)
|
||||
} else {
|
||||
message = fmt.Sprintf("Credential %q created.", request.AuthInfoOptions.Name)
|
||||
}
|
||||
|
||||
return message, err
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctl
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/testutil"
|
||||
)
|
||||
|
||||
// TODO: Determine if this should be broken out into it's own file
|
||||
// setup the airshipCTL env prior to running
|
||||
func initCTL(t *testing.T) {
|
||||
conf, configPath, kubeConfigPath, cleanup := testutil.InitConfig(t)
|
||||
defer cleanup(t)
|
||||
// point airshipctl client toward test configs
|
||||
c.settings = &environment.AirshipCTLSettings{
|
||||
AirshipConfigPath: configPath,
|
||||
KubeConfigPath: kubeConfigPath,
|
||||
Config: conf,
|
||||
}
|
||||
|
||||
err := c.settings.Config.LoadConfig(
|
||||
c.settings.AirshipConfigPath,
|
||||
c.settings.KubeConfigPath,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDefaultConfigRequest(t *testing.T) {
|
||||
initCTL(t)
|
||||
// get the default html
|
||||
html, err := getConfigHTML()
|
||||
require.NoError(t, err)
|
||||
|
||||
// simulate incoming WsMessage from websocket client
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.GetDefaults,
|
||||
}
|
||||
|
||||
response := HandleConfigRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: html,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
|
||||
request = configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.SetCredential,
|
||||
AuthInfoOptions: testutil.DummyAuthInfoOptions(),
|
||||
}
|
||||
response = HandleConfigRequest(request)
|
||||
assert.Contains(t, response.Message, "created")
|
||||
|
||||
request = configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.SetCluster,
|
||||
ClusterOptions: testutil.DummyClusterOptions(),
|
||||
}
|
||||
response = HandleConfigRequest(request)
|
||||
assert.Contains(t, response.Message, "created")
|
||||
|
||||
request = configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.SetContext,
|
||||
ContextOptions: testutil.DummyContextOptions(),
|
||||
}
|
||||
response = HandleConfigRequest(request)
|
||||
assert.Contains(t, response.Message, "created")
|
||||
}
|
||||
|
||||
func TestHandleUnknownConfigSubComponent(t *testing.T) {
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: "fake_subcomponent",
|
||||
}
|
||||
|
||||
response := HandleConfigRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: "fake_subcomponent",
|
||||
Error: "Subcomponent fake_subcomponent not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
32
internal/integrations/ctl/document.go
Executable file → Normal file
@ -19,8 +19,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/pull"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
@ -38,7 +36,6 @@ func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage {
|
||||
var message string
|
||||
switch request.SubComponent {
|
||||
case configs.GetDefaults:
|
||||
response.HTML, err = GetDocumentHTML()
|
||||
response.Data = getGraphData()
|
||||
case configs.DocPull:
|
||||
message, err = c.docPull()
|
||||
@ -129,32 +126,3 @@ func (c *Client) docPull() (string, error) {
|
||||
|
||||
return message, err
|
||||
}
|
||||
|
||||
// GetDocumentHTML will return the templated document pagelet
|
||||
func GetDocumentHTML() (string, error) {
|
||||
return getHTML("/templates/document.html", ctlPage{
|
||||
Title: "Document",
|
||||
Version: getAirshipCTLVersion(),
|
||||
YAMLTree: getYamlTree(),
|
||||
YAMLHome: filepath.Dir(c.settings.AirshipConfigPath),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: when we figure out what tree structure we're doing make this dynamic
|
||||
// The string builder is unnecessary in an non dynamic role, so it may be needed later
|
||||
func getYamlTree() string {
|
||||
var s strings.Builder
|
||||
|
||||
s.WriteString("<li><table>" +
|
||||
"<tr><td><span class=\"document\" id=\"AirshipConfigSpan\"> </span></td>" +
|
||||
"<td><button id=\"AirshipConfigBtn\" class=\"unstyled-button\" onclick=\"return documentAction(this)\"> - " +
|
||||
filepath.Base(c.settings.AirshipConfigPath) +
|
||||
"</button></td></tr>" +
|
||||
"<tr><td><span class=\"document\" id=\"KubeConfigSpan\"> </span></td>" +
|
||||
"<td><button id=\"KubeConfigBtn\" class=\"unstyled-button\" onclick=\"return documentAction(this)\"> - " +
|
||||
filepath.Base(c.settings.KubeConfigPath) +
|
||||
"</button></td></tr>" +
|
||||
"</table></li>")
|
||||
|
||||
return s.String()
|
||||
}
|
||||
|
@ -18,14 +18,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
"opendev.org/airship/airshipui/util/utiltest"
|
||||
)
|
||||
|
||||
func TestHandleDefaultDocumentRequest(t *testing.T) {
|
||||
initCTL(t)
|
||||
html, err := GetDocumentHTML()
|
||||
require.NoError(t, err)
|
||||
utiltest.InitConfig(t)
|
||||
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
@ -39,11 +37,13 @@ func TestHandleDefaultDocumentRequest(t *testing.T) {
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: html,
|
||||
Data: getGraphData(),
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
assert.Equal(t, expected.Type, response.Type)
|
||||
assert.Equal(t, expected.Component, response.Component)
|
||||
assert.Equal(t, expected.SubComponent, response.SubComponent)
|
||||
assert.Equal(t, expected.Data, response.Data)
|
||||
}
|
||||
|
||||
func TestHandleUnknownDocumentSubComponent(t *testing.T) {
|
||||
|
@ -1,5 +0,0 @@
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<h2>Generate ISO</h2>
|
||||
<button type="button" class="btn btn-info" id="GenIsoBtn" onclick="baremetalAction(this)" style="width: 150px;" {{.Disabled}}>{{.ButtonText}}</button>
|