├── docs
├── .gitkeep
└── images
│ ├── application.png
│ └── deployments.png
├── webui
├── src
│ ├── assets
│ │ ├── .gitkeep
│ │ ├── not-found.html
│ │ └── i18n
│ │ │ └── en.json
│ ├── app
│ │ ├── widget
│ │ │ ├── graph
│ │ │ │ ├── graph.component.css
│ │ │ │ └── graph.component.html
│ │ │ ├── help-widget
│ │ │ │ ├── help-widget.component.css
│ │ │ │ ├── help-widget.component.html
│ │ │ │ └── help-widget.component.ts
│ │ │ ├── cytograph
│ │ │ │ ├── cytograph.component.html
│ │ │ │ └── cytograph.component.css
│ │ │ └── badgewidget
│ │ │ │ ├── badgewidget.component.html
│ │ │ │ ├── badgewidget.component.css
│ │ │ │ └── badgewidget.component.ts
│ │ ├── components
│ │ │ ├── env-chip
│ │ │ │ ├── env-chip.component.css
│ │ │ │ ├── blank.gif
│ │ │ │ ├── env-chip.component.html
│ │ │ │ ├── env-chip.component.spec.ts
│ │ │ │ └── env-chip.component.ts
│ │ │ ├── openapi-ui
│ │ │ │ ├── openapi-ui.component.html
│ │ │ │ └── openapi-ui.component.ts
│ │ │ ├── domains-browse
│ │ │ │ ├── domains-browse.component.css
│ │ │ │ ├── domains-browse.component.html
│ │ │ │ └── domains-browse.component.ts
│ │ │ ├── graphs
│ │ │ │ ├── graph-browse.component.html
│ │ │ │ └── graph-browse.component.ts
│ │ │ ├── badges
│ │ │ │ ├── badges.component.css
│ │ │ │ └── badges.component.html
│ │ │ ├── applications
│ │ │ │ ├── applications.component.css
│ │ │ │ ├── applications.component.spec.ts
│ │ │ │ └── applications.component.html
│ │ │ └── appdetail
│ │ │ │ ├── appdetail.component.spec.ts
│ │ │ │ └── appdetail.component.css
│ │ ├── kit
│ │ │ ├── oui-pagination
│ │ │ │ ├── oui-pagination.component.css
│ │ │ │ ├── oui-pagination.component.html
│ │ │ │ └── oui-pagination.component.ts
│ │ │ ├── oui-progress-tracker
│ │ │ │ ├── oui-progress-tracker.component.css
│ │ │ │ ├── oui-progress-tracker.component.html
│ │ │ │ └── oui-progress-tracker.component.ts
│ │ │ ├── oui-action-menu
│ │ │ │ ├── oui-action-menu.component.css
│ │ │ │ ├── oui-action-menu.component.html
│ │ │ │ └── oui-action-menu.component.ts
│ │ │ ├── oui-nav-bar
│ │ │ │ ├── oui-nav-bar.component.css
│ │ │ │ ├── oui-nav-bar.component.html
│ │ │ │ └── oui-nav-bar.component.ts
│ │ │ └── oui-message
│ │ │ │ ├── oui-message.component.html
│ │ │ │ └── oui-message.component.ts
│ │ ├── app.component.css
│ │ ├── models
│ │ │ ├── commons
│ │ │ │ ├── content-bean.ts
│ │ │ │ ├── entity-bean.ts
│ │ │ │ ├── badges-bean.ts
│ │ │ │ └── applications-bean.ts
│ │ │ ├── kit
│ │ │ │ ├── paginate.ts
│ │ │ │ ├── progress-tracker.ts
│ │ │ │ └── navbar.ts
│ │ │ └── graph
│ │ │ │ └── graph-bean.ts
│ │ ├── stores
│ │ │ ├── action-with-payload.ts
│ │ │ ├── environments-store.service.ts
│ │ │ ├── graphs-store.service.ts
│ │ │ ├── badges-store.service.ts
│ │ │ ├── errors-store.service.ts
│ │ │ ├── help-store.service.ts
│ │ │ └── config-store.service.ts
│ │ ├── guards
│ │ │ ├── profile.guard.ts
│ │ │ └── routing.guard.ts
│ │ ├── services
│ │ │ ├── configuration.service.ts
│ │ │ ├── data-badge.service.ts
│ │ │ ├── data-graph.service.ts
│ │ │ ├── data-domain.service.ts
│ │ │ ├── data-deployment.service.ts
│ │ │ ├── data-environment.service.ts
│ │ │ ├── data-application-version.service.ts
│ │ │ ├── data-content.service.ts
│ │ │ ├── security.service.ts
│ │ │ ├── data-badgestats.service.ts
│ │ │ ├── data-badgeratings.service.ts
│ │ │ ├── data-stream-resources.service.ts
│ │ │ └── data-graph-resources.service.ts
│ │ ├── shared
│ │ │ └── decorator
│ │ │ │ └── autoUnsubscribe.ts
│ │ ├── interfaces
│ │ │ └── default-resources.interface.ts
│ │ ├── app.component.html
│ │ ├── pipes
│ │ │ └── pipes-applications.component.ts
│ │ ├── app.component.spec.ts
│ │ ├── resolver
│ │ │ ├── resolve-graph.ts
│ │ │ ├── resolve-domains.ts
│ │ │ ├── resolve-applications.ts
│ │ │ └── resolve-badges.ts
│ │ └── app-routing.module.ts
│ ├── styles.css
│ ├── environments
│ │ ├── environment.ts
│ │ └── environment.prod.ts
│ ├── typings.d.ts
│ ├── tsconfig.app.json
│ ├── main.ts
│ ├── tsconfig.spec.json
│ ├── index.html
│ ├── test.ts
│ └── polyfills.ts
├── e2e
│ ├── app.po.ts
│ ├── tsconfig.e2e.json
│ └── app.e2e-spec.ts
├── .editorconfig
├── tsconfig.json
├── proxy-conf.js
├── .gitignore
├── protractor.conf.js
├── karma.conf.js
├── Makefile
├── README.md
├── package.json
└── tslint.json
├── api
├── .gitignore
├── handlers
│ ├── version.go
│ ├── ping.go
│ ├── handlers_test.go
│ └── hooks.go
├── config
│ ├── type.go
│ └── loader.go
├── hateoas
│ ├── hooks.go
│ ├── errors.go
│ └── utils.go
├── .gometalinter.json
├── logger
│ └── logger.go
├── db
│ ├── type.go
│ ├── migrations.go
│ ├── transactions.go
│ ├── logger.go
│ └── dbconfig.go
├── v1
│ ├── graph
│ │ └── handlers.go
│ ├── environment
│ │ ├── handlers.go
│ │ └── environment_test.go
│ ├── deployment
│ │ ├── depend.go
│ │ └── deployer.go
│ ├── badge
│ │ ├── handlers.go
│ │ └── badge_test.go
│ ├── content
│ │ └── handlers.go
│ ├── application
│ │ └── latest.go
│ └── domain
│ │ └── repository.go
├── graphapi
│ ├── graph.go
│ └── type.go
├── tests
│ └── fixtures.go
├── security
│ ├── type.go
│ ├── policy.go
│ └── policy_test.go
├── Gopkg.toml
└── Makefile
├── migrations
├── v0009.sql
├── v0008.sql
├── v0010.sql
├── v0003.sql
├── v0004.sql
├── v0006.sql
├── v0005.sql
├── v0002.sql
├── v0001.sql
└── v0007.sql
├── .gitignore
├── docker-compose.yml
├── tests
├── 01-version.yml
├── 02-ping.yml
├── 11-applications-latest-v1.yml
├── 40-contents-v1.yml
└── 20-environments-v1.yml
├── CHANGELOG.md
├── docker
└── main
│ └── Dockerfile
├── .config.json.dist
├── scripts
└── appcatalog.sh
├── ROADMAP.md
├── LICENSE
├── Makefile
└── README.md
/docs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webui/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webui/src/app/widget/graph/graph.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webui/src/assets/not-found.html:
--------------------------------------------------------------------------------
1 | Not found
2 |
--------------------------------------------------------------------------------
/api/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | **/*.png
3 | *-packr.go
4 |
--------------------------------------------------------------------------------
/webui/src/app/components/env-chip/env-chip.component.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webui/src/styles.css:
--------------------------------------------------------------------------------
1 | @import '~swagger-ui/dist/swagger-ui.css'
2 |
--------------------------------------------------------------------------------
/webui/src/app/components/openapi-ui/openapi-ui.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/images/application.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ovh/lhasa/HEAD/docs/images/application.png
--------------------------------------------------------------------------------
/docs/images/deployments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ovh/lhasa/HEAD/docs/images/deployments.png
--------------------------------------------------------------------------------
/webui/src/app/components/domains-browse/domains-browse.component.css:
--------------------------------------------------------------------------------
1 | .count{
2 | font-size: small;
3 | }
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-pagination/oui-pagination.component.css:
--------------------------------------------------------------------------------
1 | .oui-pagination {
2 | clear: both;
3 | }
4 |
--------------------------------------------------------------------------------
/webui/src/app/widget/help-widget/help-widget.component.css:
--------------------------------------------------------------------------------
1 | .can-be-clicked {
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/migrations/v0009.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | UPDATE releases SET badge_ratings = NULL;
4 |
5 | -- +migrate Up
6 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-progress-tracker/oui-progress-tracker.component.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
--------------------------------------------------------------------------------
/webui/src/app/components/env-chip/blank.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ovh/lhasa/HEAD/webui/src/app/components/env-chip/blank.gif
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-action-menu/oui-action-menu.component.css:
--------------------------------------------------------------------------------
1 |
2 | .adjust-width {
3 | width: 100%;
4 | min-width: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/webui/src/app/widget/cytograph/cytograph.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/webui/src/app/components/graphs/graph-browse.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/webui/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | tracing: true,
4 | apiUrl: '/api/'
5 | };
6 |
--------------------------------------------------------------------------------
/webui/src/app/widget/cytograph/cytograph.component.css:
--------------------------------------------------------------------------------
1 | ngx-cytoscape {
2 | position: absolute;
3 | width: 100%;
4 | height: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/webui/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | tracing: false,
4 | apiUrl: '/api/'
5 | };
6 |
--------------------------------------------------------------------------------
/webui/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | .block-widget-overlay {
2 | background-color: #ffffff;
3 | opacity: 0.9;
4 | filter: alpha(opacity=90);
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | test.db
3 | **/*.png
4 | vault-file.json
5 | .config.json
6 | **/gin-bin
7 | **/test_results.xml
8 | .cds
9 | .sherpa.json
10 | debug
11 |
--------------------------------------------------------------------------------
/webui/src/app/models/commons/content-bean.ts:
--------------------------------------------------------------------------------
1 | import { EntityBean } from './entity-bean';
2 |
3 | // Content
4 | export class ContentBean extends String {
5 | }
6 |
--------------------------------------------------------------------------------
/webui/src/app/components/badges/badges.component.css:
--------------------------------------------------------------------------------
1 |
2 | dl{
3 | margin: 0.5em;
4 | }
5 | dl dt {
6 | float:left;
7 | font-weight:bold;
8 | width:7em;
9 | }
10 |
--------------------------------------------------------------------------------
/webui/src/app/models/kit/paginate.ts:
--------------------------------------------------------------------------------
1 |
2 | export class UiKitPaginate {
3 | totalElements: number;
4 | totalPages: number;
5 | size: number;
6 | number: number;
7 | }
8 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | db:
4 | image: postgres:9.4
5 | environment:
6 | POSTGRES_PASSWORD: appcatalog
7 | ports:
8 | - ${PORT}32:5432
9 |
--------------------------------------------------------------------------------
/migrations/v0008.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | ALTER TABLE releases DROP COLUMN IF EXISTS "properties";
4 |
5 | -- +migrate Up
6 |
7 | ALTER TABLE releases ADD COLUMN "properties" JSONB;
8 |
--------------------------------------------------------------------------------
/webui/src/app/widget/help-widget/help-widget.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/webui/src/app/models/kit/progress-tracker.ts:
--------------------------------------------------------------------------------
1 |
2 | export class UiKitStep {
3 | id: string;
4 | label: string;
5 | status: string;
6 | stepClass?: string;
7 | labelClass?: string;
8 | }
9 |
--------------------------------------------------------------------------------
/webui/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 | declare var global: any;
7 | declare var require: any;
8 |
--------------------------------------------------------------------------------
/migrations/v0010.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | DROP INDEX IF EXISTS "idx_deployments_public_id";
4 |
5 | -- +migrate Up
6 |
7 | CREATE UNIQUE INDEX "idx_deployments_public_id"
8 | ON "deployments" ("public_id");
9 |
--------------------------------------------------------------------------------
/webui/src/app/models/kit/navbar.ts:
--------------------------------------------------------------------------------
1 |
2 | export class UiKitMenuItem {
3 | id: string;
4 | label: string;
5 | routerLink?: string;
6 | icon?: string;
7 | expanded?: boolean;
8 | items?: UiKitMenuItem[];
9 | }
10 |
--------------------------------------------------------------------------------
/webui/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/webui/src/app/widget/badgewidget/badgewidget.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{_badge.title}}{{_badge.label}}
3 |
4 |
--------------------------------------------------------------------------------
/tests/01-version.yml:
--------------------------------------------------------------------------------
1 | name: Monitoring TestSuite
2 | testcases:
3 | - name: GET {{.APP_HOST}}/unsecured/version
4 | steps:
5 | - type: http
6 | method: GET
7 | url: "{{.APP_HOST}}/api/unsecured/version"
8 | assertions:
9 | - result.statuscode ShouldEqual 200
10 |
--------------------------------------------------------------------------------
/webui/src/app/widget/graph/graph.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/webui/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/migrations/v0003.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | ALTER TABLE "deployments" DROP COLUMN IF EXISTS "dependencies";
4 |
5 | -- +migrate Up
6 |
7 | ALTER TABLE "deployments" ADD "dependencies" JSONB;
8 |
9 | CREATE INDEX idx_deployments_dependencies ON "deployments" USING GIN ("dependencies");
10 |
11 |
--------------------------------------------------------------------------------
/webui/src/app/components/applications/applications.component.css:
--------------------------------------------------------------------------------
1 | .application-description {
2 | height: 5em;
3 | overflow: hidden;
4 | }
5 |
6 | .pad-tile {
7 | padding: 8px;
8 | cursor: pointer;
9 | }
10 |
11 | .filter{
12 | float: right;
13 | }
14 |
15 | .count{
16 | font-size: small;
17 | }
--------------------------------------------------------------------------------
/webui/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/webui/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/webui/src/app/components/env-chip/env-chip.component.html:
--------------------------------------------------------------------------------
1 |
2 |
4 | {{ environments[slug].name }}
5 |
6 |
--------------------------------------------------------------------------------
/tests/02-ping.yml:
--------------------------------------------------------------------------------
1 | name: Monitoring TestSuite
2 | testcases:
3 | - name: GET {{.APP_HOST}}/unsecured/ping
4 | steps:
5 | - type: http
6 | method: GET
7 | url: "{{.APP_HOST}}/api/unsecured/mon"
8 | assertions:
9 | - result.statuscode ShouldEqual 200
10 | - result.body ShouldEqual '"OK"'
11 |
--------------------------------------------------------------------------------
/api/handlers/version.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | )
6 |
7 | // VersionHandler displays the current version number
8 | func VersionHandler(version string) func(*gin.Context) (string, error) {
9 | return func(_ *gin.Context) (string, error) {
10 | return version, nil
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/migrations/v0004.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | DROP INDEX IF EXISTS idx_deployments_application_id;
4 | DROP INDEX IF EXISTS idx_deployments_environment_id;
5 |
6 | -- +migrate Up
7 |
8 | CREATE INDEX idx_deployments_application_id ON deployments (application_id);
9 | CREATE INDEX idx_deployments_environment_id ON deployments (environment_id);
10 |
11 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-nav-bar/oui-nav-bar.component.css:
--------------------------------------------------------------------------------
1 | .oui-navbar-link {
2 | min-height: 1.0rem;
3 | }
4 |
5 | .oui-navbar-menu .oui-navbar-link {
6 | width: auto;
7 | }
8 |
9 | .navbar-container {
10 | position: fixed;
11 | width: 100%;
12 | top: 0;
13 | z-index: 2;
14 | }
15 |
16 | .navbar-spacer {
17 | height: 5em;
18 | }
19 |
--------------------------------------------------------------------------------
/webui/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('console App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/api/config/type.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/ovh/lhasa/api/db"
5 | "github.com/ovh/lhasa/api/security"
6 | )
7 |
8 | // Lhasa is the main config format
9 | type Lhasa struct {
10 | DB db.DatabaseCredentials `json:"appcatalog-db"`
11 | Policy security.Policy `json:"security"`
12 | LogHeaders []string `json:"log-headers"`
13 | }
14 |
--------------------------------------------------------------------------------
/api/handlers/ping.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "database/sql"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // PingHandler provides monitoring status on a rest endpoint
10 | func PingHandler(db *sql.DB) func(c *gin.Context) (string, error) {
11 | return func(c *gin.Context) (string, error) {
12 | err := db.Ping()
13 | if err != nil {
14 | return "", err
15 | }
16 | return "OK", nil
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/webui/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/webui/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts",
15 | "polyfills.ts"
16 | ],
17 | "include": [
18 | "**/*.spec.ts",
19 | "**/*.d.ts"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/webui/src/app/stores/action-with-payload.ts:
--------------------------------------------------------------------------------
1 | import { Action } from '@ngrx/store';
2 | import { Subject } from 'rxjs';
3 |
4 | export class ActionWithPayload implements Action {
5 | readonly type: string;
6 | constructor(public payload: T) {
7 | }
8 | }
9 |
10 | export class ActionWithPayloadAndPromise implements Action {
11 | readonly type: string;
12 | constructor(public payload: T, public subject?: Subject) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/webui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "dom"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/guards/profile.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class ProfileGuard implements CanActivate {
7 | canActivate(
8 | next: ActivatedRouteSnapshot,
9 | state: RouterStateSnapshot): Observable | Promise | boolean {
10 | return true;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-message/oui-message.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ message | translate:param }}
4 |
{{ stack }}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 |
11 | ## [0.1.1] - 2018-02-19
12 | ### Added
13 | - First release
14 | - Basic application CRUD in the API
15 | - Basic angular Web UI
16 |
17 | [Unreleased]: https://github.com/ovh/lhasa/commits/master
18 |
--------------------------------------------------------------------------------
/webui/proxy-conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * gateway deployment simulation
3 | * @param {*} req
4 | * @param {*} res
5 | * @param {*} proxyOptions
6 | */
7 |
8 | const injectCreds = function (req, res, proxyOptions) {
9 | console.log("Inject.");
10 | req.headers["X-Remote-User"] = "fabien.meurillon";
11 | };
12 |
13 | const PROXY_CONFIG = {
14 | "/api": {
15 | "target": "http://localhost:8081",
16 | "secure": false
17 | },
18 | "/all": {
19 | "secure": false
20 | },
21 | }
22 |
23 | module.exports = PROXY_CONFIG;
24 |
--------------------------------------------------------------------------------
/api/hateoas/hooks.go:
--------------------------------------------------------------------------------
1 | package hateoas
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/loopfz/gadgeto/tonic"
6 | )
7 |
8 | // RenderHookWrapper handles hateoas entity conversion
9 | func RenderHookWrapper(hook tonic.RenderHook) tonic.RenderHook {
10 | return func(c *gin.Context, status int, payload interface{}) {
11 | baseURL := BaseURL(c)
12 | switch r := payload.(type) {
13 | case Resourceable:
14 | r.ToResource(baseURL)
15 | }
16 | if hook == nil {
17 | return
18 | }
19 | hook(c, status, payload)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/docker/main/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:jessie
2 |
3 | COPY dist /app
4 |
5 | WORKDIR /app
6 |
7 | RUN chown -R nobody:nogroup /app \
8 | && chmod +x /app/mycompany.sh \
9 | /app/appcatalog.sh \
10 | /app/appcatalog-configuration \
11 | /app/appcatalog \
12 | && apt-get update \
13 | && apt-get install -y curl wget ca-certificates
14 |
15 | USER nobody
16 |
17 | EXPOSE 8081
18 |
19 | CMD ./appcatalog-configuration --debug --output=./config.json start && ./appcatalog.sh --config=./config.json
20 |
--------------------------------------------------------------------------------
/api/.gometalinter.json:
--------------------------------------------------------------------------------
1 | {
2 | "Enable": [
3 | "vet",
4 | "gotype",
5 | "gotypex",
6 | "deadcode",
7 | "gocyclo",
8 | "golint",
9 | "varcheck",
10 | "structcheck",
11 | "maligned",
12 | "errcheck",
13 | "megacheck",
14 | "ineffassign",
15 | "interfacer",
16 | "unconvert",
17 | "goconst",
18 | "gofmt",
19 | "goimports",
20 | "misspell"
21 | ],
22 | "Exclude": [
23 | ".+-packr.go",
24 | ".*_test.go"
25 | ],
26 | "Vendor": true,
27 | "Test": true,
28 | "Deadline": "5m"
29 | }
30 |
--------------------------------------------------------------------------------
/webui/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Application Catalog
6 |
7 |
8 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-action-menu/oui-action-menu.component.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-progress-tracker/oui-progress-tracker.component.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 | - {{ step.label | translate }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/webui/src/app/guards/routing.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, ActivatedRoute, Router, Params } from '@angular/router';
3 | import { Observable } from 'rxjs';
4 |
5 | @Injectable()
6 | export class RoutingGuard implements CanActivate {
7 |
8 | constructor(
9 | private route: ActivatedRoute,
10 | private router: Router
11 | ) {
12 |
13 | }
14 |
15 | canActivate(
16 | next: ActivatedRouteSnapshot,
17 | state: RouterStateSnapshot): Observable | Promise | boolean {
18 | return true;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/api/config/loader.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "os"
7 |
8 | "github.com/ovh/lhasa/api/security"
9 | )
10 |
11 | // Empty returns an empty configuration struct
12 | func Empty() Lhasa {
13 | return Lhasa{
14 | Policy: make(security.Policy),
15 | }
16 | }
17 |
18 | // LoadFromFile extract configuration file
19 | func LoadFromFile(configFile *os.File) (config Lhasa, err error) {
20 | config = Empty()
21 | // Init config file
22 | b, err := ioutil.ReadFile(configFile.Name())
23 | if err != nil {
24 | return config, err
25 | }
26 | err = json.Unmarshal(b, &config)
27 | return config, err
28 | }
29 |
--------------------------------------------------------------------------------
/.config.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "appcatalog-db": {
3 | "writers": [
4 | {
5 | "host": "#addr#",
6 | "port": 5432,
7 | "sslmode": "disable"
8 | }
9 | ],
10 | "database": "postgres",
11 | "user": "postgres",
12 | "password": "#sample#",
13 | "type": "postgresql"
14 | },
15 | "security": {
16 | "ROLE_ADMIN": {
17 | "X-Remote-User": ["john.doe"]
18 | },
19 | "ROLE_USER": {
20 | "X-Remote-User": ["*"]
21 | }
22 | }
23 | "log-headers": [
24 | "X-Remote-User",
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/webui/src/app/components/domains-browse/domains-browse.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/api/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/fabienm/go-logrus-formatters"
7 | "github.com/sirupsen/logrus"
8 | )
9 |
10 | // NewLogger creates a configured logrus logger instance
11 | func NewLogger(isVerbose, isDebug, isQuiet, isJSON bool) logrus.FieldLogger {
12 | log := logrus.New()
13 | log.Level = logrus.WarnLevel
14 | if isVerbose {
15 | log.Level = logrus.InfoLevel
16 | }
17 | if isQuiet {
18 | log.Level = logrus.FatalLevel
19 | }
20 | if isDebug {
21 | log.Level = logrus.DebugLevel
22 | }
23 | if isJSON {
24 | hostname, _ := os.Hostname()
25 | log.Formatter = formatters.NewGelf(hostname)
26 | }
27 |
28 | return log
29 | }
30 |
--------------------------------------------------------------------------------
/webui/src/app/widget/badgewidget/badgewidget.component.css:
--------------------------------------------------------------------------------
1 | span.badge {
2 | color: white;
3 | font-size: smaller;
4 | padding: 0.2em;
5 | padding-left: 0.5em;
6 | padding-right: 0.5em;
7 | text-shadow: 0.1em 0.1em #01010145;
8 | white-space:nowrap;
9 | line-height: 2em;
10 | }
11 |
12 | span.left {
13 | border-radius: 0.3em 0em 0em 0.3em;
14 | background: #555;
15 | background: linear-gradient(to bottom, rgba(187, 187, 187, 0.2), rgba(0, 0, 0, 0.2)), #555;
16 | }
17 |
18 | span.right {
19 | border-radius: 0em 0.3em 0.3em 0em;
20 | background: red;
21 | background: linear-gradient(to bottom, rgba(187, 187, 187, 0.2), rgba(0, 0, 0, 0.2)), red;
22 | }
23 |
--------------------------------------------------------------------------------
/webui/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/webui/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/webui/src/app/models/commons/entity-bean.ts:
--------------------------------------------------------------------------------
1 | export class EntityBean {
2 | id?: string;
3 | timestamp?: Date;
4 | }
5 |
6 | export class HrefLinks {
7 | rel: string;
8 | href: string;
9 | }
10 |
11 | // Page meta data
12 | export class PageMetaData {
13 | totalElements?: number;
14 | totalPages?: number;
15 | size: number;
16 | number: number;
17 | }
18 |
19 | export class ContentListResponse {
20 | content: T[];
21 | pageMetadata: PageMetaData;
22 | _links: {};
23 | }
24 |
25 |
26 | export abstract class AbstractPaginatedResource {
27 | metadata: PageMetaData = {
28 | totalElements: 0,
29 | totalPages: 0,
30 | size: 0,
31 | number: 0
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/webui/src/app/services/configuration.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { environment } from '../../environments/environment';
3 |
4 | @Injectable()
5 | export class ConfigurationService {
6 |
7 | public ApiUrl: string;
8 | public AuthToken: string;
9 |
10 | /**
11 | * constructor
12 | */
13 | constructor() {
14 | this.ApiUrl = environment.apiUrl;
15 | }
16 |
17 | /**
18 | * fix session token
19 | * @param AuthToken
20 | */
21 | public setAuthToken(AuthToken: string): void {
22 | this.AuthToken = AuthToken;
23 | }
24 |
25 | /**
26 | * get token
27 | * @param AuthToken
28 | */
29 | public getAuthToken(): string {
30 | return this.AuthToken;
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/migrations/v0006.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | -- Remove tables
4 | DROP TABLE IF EXISTS "badges";
5 |
6 | ALTER TABLE applications DROP COLUMN IF EXISTS "badge_ratings";
7 |
8 | -- Remove indexes
9 |
10 |
11 | -- +migrate Up
12 |
13 | -- Tables
14 | CREATE TABLE IF NOT EXISTS "badges" (
15 | "id" BIGSERIAL,
16 | "slug" VARCHAR(255) UNIQUE NOT NULL,
17 | "title" VARCHAR(255),
18 | "type" VARCHAR(20),
19 | "levels" JSONB,
20 | "created_at" TIMESTAMP WITH TIME ZONE,
21 | "updated_at" TIMESTAMP WITH TIME ZONE,
22 | "deleted_at" TIMESTAMP WITH TIME ZONE,
23 | PRIMARY KEY ("id"),
24 | CONSTRAINT non_empty CHECK (slug <> '')
25 | );
26 |
27 | ALTER TABLE applications ADD COLUMN "badge_ratings" JSONB;
28 |
29 | -- Indexes
30 |
--------------------------------------------------------------------------------
/migrations/v0005.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | -- Remove indexes
4 | DROP INDEX IF EXISTS "idx_contents_name";
5 |
6 | -- Remove tables
7 | DROP TABLE IF EXISTS "contents";
8 |
9 | -- +migrate Up
10 |
11 | -- Tables
12 | CREATE TABLE "contents" (
13 | "id" BIGSERIAL,
14 | "name" VARCHAR(255) NOT NULL DEFAULT '',
15 | "content_type" VARCHAR(128) NOT NULL DEFAULT 'text/plain',
16 | "locale" VARCHAR(255) NOT NULL DEFAULT 'en-GB',
17 | "body" BYTEA NOT NULL DEFAULT '',
18 | "created_at" TIMESTAMP WITH TIME ZONE,
19 | "updated_at" TIMESTAMP WITH TIME ZONE,
20 | "deleted_at" TIMESTAMP WITH TIME ZONE,
21 | PRIMARY KEY ("id")
22 | );
23 |
24 | CREATE UNIQUE INDEX idx_contents_name
25 | ON "contents" ("name","locale");
26 |
--------------------------------------------------------------------------------
/webui/src/app/components/env-chip/env-chip.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { EnvChipComponent } from './env-chip.component';
4 |
5 | describe('EnvChipComponent', () => {
6 | let component: EnvChipComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ EnvChipComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(EnvChipComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-badge.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ConfigurationService } from './configuration.service';
3 | import { DefaultResource } from '../interfaces/default-resources.interface';
4 | import { DataCoreResource } from './data-core-resources.service';
5 | /**
6 | * data model
7 | */
8 | import { BadgeBean } from '../models/commons/badges-bean';
9 | import { HttpClient } from '@angular/common/http';
10 |
11 | @Injectable()
12 | export class DataBadgeService extends DataCoreResource implements DefaultResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/badges', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-graph.service.ts:
--------------------------------------------------------------------------------
1 | import { DataGraphResource } from './data-graph-resources.service';
2 | import { Injectable } from '@angular/core';
3 | import { ConfigurationService } from './configuration.service';
4 | import { DefaultGraphResource } from '../interfaces/default-resources.interface';
5 | /**
6 | * data model
7 | */
8 | import { HttpClient } from '@angular/common/http';
9 | import { GraphBean } from '../models/graph/graph-bean';
10 |
11 | @Injectable()
12 | export class DataGraphService extends DataGraphResource implements DefaultGraphResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/graphs', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/components/appdetail/appdetail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AppdetailComponent } from './appdetail.component';
4 |
5 | describe('AppdetailComponent', () => {
6 | let component: AppdetailComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ AppdetailComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(AppdetailComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-domain.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ConfigurationService } from './configuration.service';
3 | import { DefaultResource } from '../interfaces/default-resources.interface';
4 | import { DataCoreResource } from './data-core-resources.service';
5 | /**
6 | * data model
7 | */
8 | import { DomainBean } from '../models/commons/applications-bean';
9 | import { HttpClient } from '@angular/common/http';
10 |
11 | @Injectable()
12 | export class DataDomainService extends DataCoreResource implements DefaultResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/domains', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/scripts/appcatalog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 |
5 | ./appcatalog $* &
6 |
7 | wait_for_apis () {
8 | export API_BASE_URL=http://localhost:8081/api
9 | # wait for api
10 | ret=1
11 | while [ $ret -ne 0 ]
12 | do
13 | curl -s $API_BASE_URL/unsecured/version >/dev/null
14 | ret=$?
15 | echo "Return:" $ret
16 | sleep 1
17 | done
18 | }
19 |
20 | # Activate IMPORT_SAMPLE_DATA env var to inject sample data
21 | [ "x${IMPORT_SAMPLE_DATA}" != "x" ] && {
22 | wait_for_apis
23 | ./mycompany.sh >/dev/null
24 | }
25 |
26 | # Any script called init-script.sh will be executed
27 | # Use it for your own integration
28 | [ -f ./init-script.sh ] && {
29 | wait_for_apis
30 | chmod 700 ./init-script.sh && ./init-script.sh
31 | }
32 |
33 | wait %1
34 |
35 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-action-menu/oui-action-menu.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Output, EventEmitter, Input, OnDestroy } from '@angular/core';
2 |
3 | import { UiKitMenuItem } from '../../models/kit/navbar';
4 |
5 | @Component({
6 | selector: 'app-oui-action-menu',
7 | templateUrl: './oui-action-menu.component.html',
8 | styleUrls: ['./oui-action-menu.component.css'],
9 | providers: []
10 | })
11 | export class OuiActionMenuComponent implements OnInit {
12 |
13 | visible = true;
14 | @Input() items: UiKitMenuItem;
15 |
16 | @Output() select: EventEmitter = new EventEmitter();
17 |
18 | constructor(
19 | ) {
20 | }
21 |
22 | ngOnInit() {
23 | }
24 |
25 | onSelect(event: any, tabs: string) {
26 | event.data = tabs;
27 | this.select.emit(event);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/webui/src/app/models/commons/badges-bean.ts:
--------------------------------------------------------------------------------
1 | import { EntityBean, PageMetaData, HrefLinks, AbstractPaginatedResource } from './entity-bean';
2 | import { Timestamp } from 'rxjs';
3 |
4 | export class BadgeLevelBean {
5 | id: string;
6 | description: string;
7 | label: string;
8 | color: string;
9 | }
10 |
11 | // Badge
12 | export class BadgeBean extends EntityBean {
13 | slug: string;
14 | title: string;
15 | type: string;
16 | levels: BadgeLevelBean[];
17 | _links?: HrefLinks[];
18 | _stats: Map;
19 | }
20 |
21 | // Badge for page browse
22 | export class BadgePagesBean extends AbstractPaginatedResource {
23 | badges: BadgeBean[] = [];
24 | metadata: PageMetaData = {
25 | totalElements: 0,
26 | totalPages: 0,
27 | size: 0,
28 | number: 0
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-deployment.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { ConfigurationService } from './configuration.service';
4 | import { DefaultResource } from '../interfaces/default-resources.interface';
5 | import { DataCoreResource } from './data-core-resources.service';
6 | /**
7 | * data model
8 | */
9 | import { DeploymentBean } from '../models/commons/applications-bean';
10 |
11 | @Injectable()
12 | export class DataDeploymentService extends DataCoreResource implements DefaultResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/deployments', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/components/applications/applications.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ApplicationsComponent } from './applications.component';
4 |
5 | describe('ApplicationsComponent', () => {
6 | let component: ApplicationsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ ApplicationsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(ApplicationsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-environment.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ConfigurationService } from './configuration.service';
3 | import { DefaultResource } from '../interfaces/default-resources.interface';
4 | import { DataCoreResource } from './data-core-resources.service';
5 | /**
6 | * data model
7 | */
8 | import { EnvironmentBean } from '../models/commons/applications-bean';
9 | import { HttpClient } from '@angular/common/http';
10 |
11 | @Injectable()
12 | export class DataEnvironmentService extends DataCoreResource implements DefaultResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/environments', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-application-version.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ConfigurationService } from './configuration.service';
3 | import { DefaultResource } from '../interfaces/default-resources.interface';
4 | import { DataCoreResource } from './data-core-resources.service';
5 | /**
6 | * data model
7 | */
8 | import { ApplicationBean } from '../models/commons/applications-bean';
9 | import { HttpClient } from '@angular/common/http';
10 |
11 | @Injectable()
12 | export class DataApplicationService extends DataCoreResource implements DefaultResource {
13 | constructor(
14 | private _http: HttpClient,
15 | private _configuration: ConfigurationService
16 | ) {
17 | super(_configuration, _configuration.ApiUrl + 'v1/applications', _http);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/webui/src/app/shared/decorator/autoUnsubscribe.ts:
--------------------------------------------------------------------------------
1 | export function AutoUnsubscribe( blackList = [] ) {
2 |
3 | return function ( constructor ) {
4 | const original = constructor.prototype.ngOnDestroy;
5 |
6 | constructor.prototype.ngOnDestroy = function () {
7 | for ( const prop in this ) {
8 | if (prop) {
9 | const property = this[ prop ];
10 | if ( blackList.indexOf(prop) === -1 ) {
11 | if ( property && ( typeof property.unsubscribe === 'function' ) ) {
12 | property.unsubscribe();
13 | }
14 | }
15 | }
16 | }
17 | return original && typeof original === 'function' && original.apply(this, arguments);
18 | };
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/webui/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Roadmap
2 |
3 | ## Business processes
4 |
5 | Generate from observation a whole and consistent business process in standard BPM Notation.
6 |
7 | Retry failed steps or place compensatory requests.
8 |
9 | ## Knowledge base
10 |
11 | For each application, provide a Q&A or Board section to allow discussion between maintainers and consumers.
12 |
13 | ## Gamification badges
14 |
15 | Show some badges on an application page to view which metrics could be enhanced to increase quality of service.
16 |
17 | ## Open API sandbox
18 |
19 | If a service provides an API and a sandbox environment, provide a Swagger UI to allow discovering of the API.
20 |
21 | ## Change management
22 |
23 | When a global information system change has to be performed (for instance, RGPD compliance), track the change progress
24 | over all applications of the information system.
25 |
--------------------------------------------------------------------------------
/api/db/type.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | // DatabaseInstance host and port
4 | type DatabaseInstance struct {
5 | Port int `json:"port"`
6 | Host string `json:"host"`
7 | Ssl string `json:"sslmode"`
8 | }
9 |
10 | // DatabaseCredentials credentials
11 | type DatabaseCredentials struct {
12 | Readers []DatabaseInstance `json:"readers"`
13 | Writers []DatabaseInstance `json:"writers"`
14 | Database string `json:"database"`
15 | Password string `json:"password"`
16 | User string `json:"user"`
17 | Type string `json:"type"`
18 | }
19 |
20 | // Type db type
21 | type Type string
22 |
23 | const (
24 | // PostgreSQL connect string
25 | PostgreSQL Type = "user=%s password=%s host=%s port=%d DB.name=%s sslmode=%s"
26 |
27 | // PostgreSQLDefaultSslMode default ssl connect string
28 | PostgreSQLDefaultSslMode = "require"
29 | )
30 |
--------------------------------------------------------------------------------
/webui/src/app/models/graph/graph-bean.ts:
--------------------------------------------------------------------------------
1 | // Vis side object
2 | export class GraphVis {
3 | nodes: VisNode[];
4 | edges: VisEdge[];
5 | }
6 |
7 | export class VisNode {
8 | id: string;
9 | label: string;
10 | group: string;
11 | environment: string;
12 | domain: string;
13 | application:string;
14 | }
15 |
16 | export class VisEdge {
17 | id: string;
18 | from: string;
19 | to: string;
20 | label: string;
21 | }
22 |
23 | // Server side object
24 | export class GraphBean {
25 | nodes: NodeBean[];
26 | edges: EdgeBean[];
27 | options: any;
28 | }
29 |
30 | export class NodeBean {
31 | id: string;
32 | name: string;
33 | type: string;
34 | properties: any;
35 | }
36 |
37 | export class EdgeBean {
38 | id: string;
39 | from: string;
40 | to: string;
41 | type: string;
42 | properties: any;
43 | }
44 |
--------------------------------------------------------------------------------
/webui/src/app/widget/help-widget/help-widget.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Output, EventEmitter, Input, ElementRef, OnDestroy, Renderer2 } from '@angular/core';
2 | import { DomHandler } from 'primeng/primeng';
3 | import { HelpsStoreService } from '../../stores/help-store.service';
4 |
5 | @Component({
6 | selector: 'app-help-widget',
7 | templateUrl: './help-widget.component.html',
8 | styleUrls: ['./help-widget.component.css'],
9 | providers: [DomHandler]
10 | })
11 | export class HelpWidgetComponent implements OnInit {
12 |
13 | private _key = '';
14 | @Output() select: EventEmitter = new EventEmitter();
15 |
16 | constructor(
17 | public help: HelpsStoreService
18 | ) {
19 | }
20 |
21 | ngOnInit() {
22 | }
23 |
24 | public get key() {
25 | return this._key;
26 | }
27 |
28 | @Input() public set key(val: string) {
29 | this._key = val;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/api/v1/graph/handlers.go:
--------------------------------------------------------------------------------
1 | package graph
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/loopfz/gadgeto/tonic"
9 | "github.com/ovh/lhasa/api/graphapi"
10 | "github.com/ovh/lhasa/api/v1"
11 | "github.com/ovh/lhasa/api/v1/deployment"
12 | )
13 |
14 | // HandlerGraph returns a dependency graph for a given deployment
15 | func HandlerGraph(repo *Repository, depRepo *deployment.Repository) gin.HandlerFunc {
16 | return tonic.Handler(func(c *gin.Context, deployment *v1.Deployment) (*graphapi.Graph, error) {
17 | entity, err := depRepo.FindOneBy(map[string]interface{}{"public_id": deployment.PublicID})
18 | if err != nil {
19 | return nil, err
20 | }
21 | deployment, ok := entity.(*v1.Deployment)
22 | if !ok {
23 | return nil, errors.New("internal type error")
24 | }
25 |
26 | return repo.FindByDeployment(deployment.PublicID)
27 | }, http.StatusOK)
28 | }
29 |
--------------------------------------------------------------------------------
/webui/src/app/components/badges/badges.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | - {{level.id}}
8 | -
9 | →
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/webui/src/app/interfaces/default-resources.interface.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 |
3 | /**
4 | * data model
5 | */
6 | import { EntityBean, ContentListResponse } from '../models/commons/entity-bean';
7 |
8 | export interface DefaultResource {
9 | GetAll(): Observable;
10 | GetAllFromContent(filter: string, params: {[key: string]: any | any[]}): Observable>;
11 | GetSingle(id: string): Observable;
12 | GetSingleAny(id: string): Observable;
13 | Task(path: String, payload: any): Observable;
14 | Add(itemToAdd: T): Observable;
15 | Update(id: string, itemToUpdate: T): Observable;
16 | Delete(id: string): Observable;
17 | }
18 |
19 | export interface DefaultStreamResource {
20 | GetSingle(id: string): Observable;
21 | }
22 |
23 | export interface DefaultGraphResource {
24 | Get(params: any): Observable;
25 | }
26 |
--------------------------------------------------------------------------------
/webui/src/app/services/data-content.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, Response, Headers } from '@angular/http';
3 | import { ConfigurationService } from './configuration.service';
4 | import { DefaultResource, DefaultStreamResource } from '../interfaces/default-resources.interface';
5 | import { DataCoreResource } from './data-core-resources.service';
6 |
7 | /**
8 | * data model
9 | */
10 | import { ContentBean } from '../models/commons/content-bean';
11 | import { HttpClient } from '@angular/common/http';
12 | import { DataStreamResource } from './data-stream-resources.service';
13 |
14 | @Injectable()
15 | export class DataContentService extends DataStreamResource implements DefaultStreamResource {
16 | constructor(
17 | private _http: HttpClient,
18 | private _configuration: ConfigurationService
19 | ) {
20 | super(_configuration, _configuration.ApiUrl + 'v1/contents', _http);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/api/db/migrations.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "database/sql"
5 |
6 | "github.com/gobuffalo/packr"
7 | "github.com/rubenv/sql-migrate"
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | var migrations = &migrate.PackrMigrationSource{
12 | Box: packr.NewBox("../../migrations"),
13 | }
14 |
15 | func init() {
16 | migrate.SetTable("ovh_sql_schema_migrations")
17 | }
18 |
19 | // MigrateUp run sql-migrate migrations
20 | func MigrateUp(db *sql.DB, log logrus.FieldLogger) error {
21 | count, err := migrate.Exec(db, "postgres", migrations, migrate.Up)
22 | if err != nil {
23 | return err
24 | }
25 | log.Warnf("Applied %d migrations", count)
26 | return nil
27 | }
28 |
29 | // MigrateDown run sql-migrate migrations
30 | func MigrateDown(db *sql.DB, log logrus.FieldLogger) error {
31 | count, err := migrate.Exec(db, "postgres", migrations, migrate.Down)
32 | if err != nil {
33 | return err
34 | }
35 | log.Warnf("Removed %d migrations", count)
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/webui/src/app/kit/oui-pagination/oui-pagination.component.html:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/webui/src/app/widget/badgewidget/badgewidget.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Output, EventEmitter, Input, ElementRef, OnDestroy, Renderer2 } from '@angular/core';
2 | import { DomHandler } from 'primeng/primeng';
3 |
4 | // BadgeShieldsIOBean for a badge SVG representation
5 | export class BadgeShieldsIOBean {
6 | title: string;
7 | value: string;
8 | comment: string;
9 | label: string;
10 | color: string;
11 | description: string;
12 | }
13 |
14 | @Component({
15 | selector: 'app-badge-shieldsio',
16 | templateUrl: './badgewidget.component.html',
17 | styleUrls: ['./badgewidget.component.css'],
18 | providers: [DomHandler]
19 | })
20 | export class BadgeWidgetComponent implements OnInit {
21 |
22 | _badge: BadgeShieldsIOBean;
23 |
24 | @Output() select: EventEmitter = new EventEmitter();
25 |
26 | constructor(
27 | ) {
28 | }
29 |
30 | ngOnInit() {
31 | }
32 |
33 | @Input() set badge(val: BadgeShieldsIOBean) {
34 | this._badge = val;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/api/graphapi/graph.go:
--------------------------------------------------------------------------------
1 | package graphapi
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/loopfz/gadgeto/tonic"
8 | )
9 |
10 | // HandlerFindAllActive returns a resource list
11 | func HandlerFindAllActive(repository Repository) gin.HandlerFunc {
12 | return tonic.Handler(func(c *gin.Context) (*Graph, error) {
13 | // params and query are user to filter resultset
14 | graphResult, err := repository.FindAllActive(nil)
15 | if err != nil {
16 | return nil, err
17 | }
18 |
19 | return graphResult, nil
20 | }, http.StatusOK)
21 | }
22 |
23 | // Convert to dependencies node behaviour
24 | func Convert(repo Repository, entities []interface{}) []ImplementNode {
25 | var nodes = []ImplementNode{}
26 | for _, entity := range entities {
27 | mappers := map[string]ImplementNode{}
28 | // Resolve child dependencies
29 | repo.Resolve(entity, mappers)
30 | // Add this resolved node
31 | nodes = append(nodes, repo.Map(entity, mappers))
32 | }
33 | return nodes
34 | }
35 |
--------------------------------------------------------------------------------
/api/tests/fixtures.go:
--------------------------------------------------------------------------------
1 | package tests
2 |
3 | import (
4 | "net/http/httptest"
5 |
6 | mocket "github.com/Selvatico/go-mocket"
7 | "github.com/gobwas/glob"
8 | "github.com/ovh/lhasa/api/config"
9 | "github.com/ovh/lhasa/api/db"
10 | "github.com/ovh/lhasa/api/security"
11 |
12 | "github.com/jinzhu/gorm"
13 |
14 | "github.com/ovh/lhasa/api/logger"
15 | "github.com/ovh/lhasa/api/routing"
16 | )
17 |
18 | // StartTestHTTPServer starts a fake http server for testing purposes
19 | func StartTestHTTPServer() *httptest.Server {
20 | log := logger.NewLogger(true, true, false, false)
21 | mocket.Catcher.Register()
22 | mocket.Catcher.Reset()
23 | mocket.Catcher.Logging = true
24 | dbHandle, _ := gorm.Open(mocket.DRIVER_NAME, "any_string")
25 | tm := db.NewTransactionManager(dbHandle)
26 | c := config.Lhasa{Policy: security.Policy{"ROLE_ADMIN": {"X-Remote-User": {glob.MustCompile("*")}}}}
27 | router := routers.NewRouter(tm, c, "1.0.0", "/api", "/ui", "/", "./", true, log)
28 | server := httptest.NewServer(router)
29 | return server
30 | }
31 |
--------------------------------------------------------------------------------
/webui/src/app/components/openapi-ui/openapi-ui.component.ts:
--------------------------------------------------------------------------------
1 | import { OnChanges, Component, ElementRef, Input, ViewChild, AfterViewInit } from '@angular/core';
2 |
3 | import SwaggerUI from 'swagger-ui';
4 |
5 | @Component({
6 | selector: 'app-openapi-ui',
7 | templateUrl: './openapi-ui.component.html',
8 | styleUrls: []
9 | })
10 | export class OpenAPIUIComponent implements AfterViewInit {
11 |
12 | _url: string;
13 | @ViewChild('openapi') targetdiv: ElementRef;
14 |
15 | constructor(private el: ElementRef) {
16 | }
17 |
18 | @Input() set url(val: string) {
19 | this._url = val;
20 | this.apply();
21 | }
22 |
23 | ngAfterViewInit() {
24 | this.apply();
25 | }
26 |
27 | apply() {
28 | if (!this._url || !this.targetdiv) {
29 | return;
30 | }
31 | SwaggerUI({
32 | url: this._url,
33 | domNode: this.targetdiv.nativeElement,
34 | deepLinking: false,
35 | validatorUrl: null,
36 | displayRequestDuration: true,
37 | presets: [
38 | SwaggerUI.presets.apis
39 | ],
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/webui/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 |
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/api/v1/environment/handlers.go:
--------------------------------------------------------------------------------
1 | package environment
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | "github.com/loopfz/gadgeto/tonic"
8 | "github.com/ovh/lhasa/api/hateoas"
9 | "github.com/ovh/lhasa/api/v1"
10 | )
11 |
12 | // HandlerCreate replace or create a resource
13 | func HandlerCreate(repository *Repository) gin.HandlerFunc {
14 | return tonic.Handler(func(c *gin.Context, env *v1.Environment) (*v1.Environment, error) {
15 | oldres, err := repository.FindOneByUnscoped(map[string]interface{}{"slug": env.Slug})
16 | oldenv := oldres.(*v1.Environment)
17 | if hateoas.IsEntityDoesNotExistError(err) {
18 | if err := repository.Save(env); err != nil {
19 | return nil, err
20 | }
21 | return nil, hateoas.ErrorCreated
22 | }
23 | if err != nil {
24 | return nil, err
25 | }
26 | env.ID = oldenv.ID
27 | env.CreatedAt = oldenv.CreatedAt
28 | if err := repository.Save(env); err != nil {
29 | return nil, err
30 | }
31 | if oldenv.DeletedAt != nil {
32 | return env, hateoas.ErrorCreated
33 | }
34 | return env, nil
35 | }, http.StatusOK)
36 | }
37 |
--------------------------------------------------------------------------------
/webui/src/app/app.component.html:
--------------------------------------------------------------------------------
1 | 0">
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/migrations/v0002.sql:
--------------------------------------------------------------------------------
1 | -- +migrate Down
2 |
3 | DROP TABLE IF EXISTS "deployments";
4 | DROP TABLE IF EXISTS "environments";
5 |
6 | -- +migrate Up
7 |
8 | CREATE TABLE "environments" (
9 | "id" BIGSERIAL,
10 | "slug" VARCHAR(255) NOT NULL DEFAULT '',
11 | "name" VARCHAR(255),
12 | "properties" JSONB,
13 | "created_at" TIMESTAMP WITH TIME ZONE,
14 | "updated_at" TIMESTAMP WITH TIME ZONE,
15 | "deleted_at" TIMESTAMP WITH TIME ZONE,
16 | PRIMARY KEY ("id")
17 | );
18 |
19 | CREATE TABLE "deployments" (
20 | "id" BIGSERIAL,
21 | "public_id" VARCHAR(255),
22 | "environment_id" BIGINT NOT NULL DEFAULT 0,
23 | "application_id" BIGINT NOT NULL DEFAULT 0,
24 | "properties" JSONB,
25 | "created_at" TIMESTAMP WITH TIME ZONE,
26 | "undeployed_at" TIMESTAMP WITH TIME ZONE,
27 | "updated_at" TIMESTAMP WITH TIME ZONE,
28 | "deleted_at" TIMESTAMP WITH TIME ZONE,
29 | PRIMARY KEY ("id"),
30 | FOREIGN KEY ("environment_id") REFERENCES "environments" ON DELETE CASCADE,
31 | FOREIGN KEY ("application_id") REFERENCES "applications" ON DELETE CASCADE
32 | );
33 |
--------------------------------------------------------------------------------
/webui/Makefile:
--------------------------------------------------------------------------------
1 | UI_BASE_HREF ?= /
2 |
3 | all: ui-kit
4 | ng build --output-path=../dist/webui --prod --aot --configuration production --base-href '{{ .UIBasePath }}' --deploy-url ${UI_BASE_HREF}
5 |
6 | dev: ui-kit dep
7 | ng build --output-path=../dist/webui
8 |
9 | ui-kit: dep
10 | @rm -rf ../dist/ovh-ui-kit
11 | @mkdir -p ../dist/ovh-ui-kit
12 | @echo copy ovh-ui-kit dist icons
13 | @cp -rfp ./node_modules//ovh-ui-kit/dist/icons ../dist/ovh-ui-kit
14 | @echo copy ovh-ui-kit dist fonts
15 | @cp -rfp ./node_modules//ovh-ui-kit/packages/oui-typography/fonts ../dist/ovh-ui-kit
16 | @echo copy ovh-ui-kit css
17 | @cp ./node_modules/ovh-ui-kit/dist/oui.css ../dist/ovh-ui-kit/ovh-ui-kit.css
18 | @echo trick relative resouce location
19 | @sed s:../../dist/::g ../dist/ovh-ui-kit/ovh-ui-kit.css > ../dist/ovh-ui-kit/ovh-ui-kit.css.tmp
20 | @mv -f ../dist/ovh-ui-kit/ovh-ui-kit.css.tmp ../dist/ovh-ui-kit/ovh-ui-kit.css
21 |
22 | dep:
23 | npm install
24 |
25 | test: dep
26 | ng test
27 | ng e2e
28 |
29 | run: all
30 | npm start
31 |
32 | live:
33 | npm start
34 |
35 | clean:
36 | echo "no cleaning command available"
37 |
--------------------------------------------------------------------------------
/webui/src/app/pipes/pipes-applications.component.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { ApplicationBean, DomainBean } from '../models/commons/applications-bean';
3 |
4 | import { sortBy } from 'lodash';
5 |
6 | @Pipe({
7 | name: 'orderByDomains'
8 | })
9 | export class DomainSortPipe implements PipeTransform {
10 | transform(domains: Array): Array {
11 | const ordered = sortBy(domains, (domain) => {
12 | return this.pad(domain.name, 'a', 64);
13 | });
14 | return ordered;
15 | }
16 |
17 | pad(str, padString, length) {
18 | while (str.length < length) {
19 | str = str + padString;
20 | }
21 | return str;
22 | }
23 | }
24 |
25 | @Pipe({
26 | name: 'orderByApps'
27 | })
28 | export class ApplicationSortPipe implements PipeTransform {
29 | transform(applications: Array): Array {
30 | const ordered = sortBy(applications, (app) => {
31 | return this.pad(app.name, 'a', 64) + this.pad(app.domain, 'a', 64);
32 | });
33 | return ordered;
34 | }
35 |
36 | pad(str, padString, length) {
37 | while (str.length < length) {
38 | str = str + padString;
39 | }
40 | return str;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/webui/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 | describe('AppComponent', () => {
5 | beforeEach(async(() => {
6 | TestBed.configureTestingModule({
7 | imports: [
8 | RouterTestingModule
9 | ],
10 | declarations: [
11 | AppComponent
12 | ],
13 | }).compileComponents();
14 | }));
15 | it('should create the app', async(() => {
16 | const fixture = TestBed.createComponent(AppComponent);
17 | const app = fixture.debugElement.componentInstance;
18 | expect(app).toBeTruthy();
19 | }));
20 | it(`should have as title 'app'`, async(() => {
21 | const fixture = TestBed.createComponent(AppComponent);
22 | const app = fixture.debugElement.componentInstance;
23 | expect(app.title).toEqual('app');
24 | }));
25 | it('should render title in a h1 tag', async(() => {
26 | const fixture = TestBed.createComponent(AppComponent);
27 | fixture.detectChanges();
28 | const compiled = fixture.debugElement.nativeElement;
29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
30 | }));
31 | });
32 |
--------------------------------------------------------------------------------
/webui/src/app/services/security.service.ts:
--------------------------------------------------------------------------------
1 |
2 | import {catchError} from 'rxjs/operators';
3 | import { Injectable } from '@angular/core';
4 |
5 | import { Observable } from 'rxjs';
6 | import { Router } from '@angular/router';
7 |
8 | import { ConfigurationService } from '../services/configuration.service';
9 | import { DefaultResource } from '../interfaces/default-resources.interface';
10 | /**
11 | * data model
12 | */
13 | import { EntityBean } from '../models/commons/entity-bean';
14 | import { DataCoreResource } from './data-core-resources.service';
15 | import { HttpClient } from '@angular/common/http';
16 |
17 | @Injectable()
18 | export class SecurityService extends DataCoreResource implements DefaultResource {
19 |
20 | /**
21 | * constructor
22 | */
23 | constructor(
24 | private _http: HttpClient,
25 | private _router: Router,
26 | private _ConfigurationService: ConfigurationService) {
27 | super(_ConfigurationService, _ConfigurationService.ApiUrl, _http);
28 | }
29 |
30 | /**
31 | * get connect resource
32 | */
33 | public Connect = (): Observable