├── .argo
└── claudia-ci.yaml
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── Dockerfile-builder
├── LICENSE.md
├── README.md
├── VERSION
├── ami
├── docker-compose-ami.yml.in
└── docker-storage
├── billingbucket
└── billingbucket.go
├── build.py
├── build.sh
├── claudiad
└── main.go
├── config.env
├── constants.go
├── costdb
├── costdb.go
├── dimensions.go
└── ingest.go
├── docker-compose-debug.yml
├── docker-compose-dev.yml
├── docker-compose.yml
├── docs
├── about
│ ├── contact.md
│ └── release-notes.md
├── faq.md
├── index.md
├── report-screenshot.png
└── setup
│ ├── aws.md
│ ├── aws
│ ├── attach-policy.png
│ ├── copy-credentials.png
│ ├── create-report-1.png
│ ├── create-report-2.png
│ ├── create-report-3.png
│ ├── create-user.png
│ ├── enable-createdby-tag.png
│ └── enable-user-tags.png
│ ├── claudia.md
│ └── claudia
│ ├── account-aliases.png
│ ├── add-bucket.png
│ ├── bucket-information.png
│ ├── http-sg.png
│ └── manage-certificates.png
├── errors
└── errors.go
├── glide.lock
├── glide.yaml
├── gometalinter.json
├── ingest
├── processor.go
└── service.go
├── ingestd
└── main.go
├── mkdocs.yml
├── packer.json
├── parser
├── parser.go
└── region.go
├── routers
├── auth.go
├── cost.go
├── reports.go
└── routers.go
├── server
└── server.go
├── ui
├── .babelrc
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .nvmrc
├── Dockerfile
├── README.md
├── build.sh
├── config
│ ├── head-config.common.js
│ ├── helpers.js
│ ├── html-elements-plugin
│ │ └── index.js
│ ├── karma.conf.js
│ ├── modules
│ │ └── angular2-hmr-prod.js
│ ├── protractor.conf.js
│ ├── spec-bundle.js
│ ├── webpack.common.js
│ ├── webpack.dev.js
│ └── webpack.prod.js
├── karma.conf.js
├── npm-shrinkwrap.json
├── package.json
├── protractor.conf.js
├── src
│ ├── app
│ │ ├── +user-management
│ │ │ ├── change-password
│ │ │ │ ├── change-password-data.ts
│ │ │ │ ├── change-password.component.html
│ │ │ │ └── change-password.component.ts
│ │ │ ├── index.ts
│ │ │ ├── user-details
│ │ │ │ ├── user-details.component.html
│ │ │ │ ├── user-details.component.scss
│ │ │ │ └── user-details.component.ts
│ │ │ └── user-management.module.ts
│ │ ├── accounts
│ │ │ ├── accounts.component.html
│ │ │ ├── accounts.component.scss
│ │ │ ├── accounts.component.ts
│ │ │ └── index.ts
│ │ ├── app.component.html
│ │ ├── app.component.ts
│ │ ├── app.error-handler.ts
│ │ ├── app.module.ts
│ │ ├── app.routes.ts
│ │ ├── app.service.ts
│ │ ├── common
│ │ │ ├── abstract-value-accessor
│ │ │ │ └── abstract-value-accessor.ts
│ │ │ ├── ax-lib
│ │ │ │ ├── ax-lib.module.ts
│ │ │ │ ├── checkbox-slide
│ │ │ │ │ ├── checkbox-slide.component.html
│ │ │ │ │ ├── checkbox-slide.component.scss
│ │ │ │ │ ├── checkbox-slide.component.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── checkbox
│ │ │ │ │ ├── checkbox.component.html
│ │ │ │ │ ├── checkbox.component.scss
│ │ │ │ │ ├── checkbox.component.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── date-range
│ │ │ │ │ ├── _date-range.scss
│ │ │ │ │ ├── date-range.component.ts
│ │ │ │ │ ├── date-range.html
│ │ │ │ │ ├── date-range.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── dropdown-multiselect
│ │ │ │ │ ├── dropdown-multiselect.component.ts
│ │ │ │ │ ├── dropdown-multiselect.html
│ │ │ │ │ └── dropdown-multiselect.scss
│ │ │ │ ├── dropdown
│ │ │ │ │ ├── _dropdown.scss
│ │ │ │ │ └── dropdown.component.ts
│ │ │ │ ├── help-panel
│ │ │ │ │ ├── help-panel.component.html
│ │ │ │ │ ├── help-panel.component.scss
│ │ │ │ │ ├── help-panel.component.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── radio-button
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── radio-button.component.html
│ │ │ │ │ ├── radio-button.component.scss
│ │ │ │ │ └── radio-button.component.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── http.service.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── sliding-panel
│ │ │ │ │ ├── _sliding-panel.component.scss
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── sliding-panel.component.html
│ │ │ │ │ └── sliding-panel.component.ts
│ │ │ │ ├── sort-arrow
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── sort-arrow.component.html
│ │ │ │ │ ├── sort-arrow.component.scss
│ │ │ │ │ └── sort-arrow.component.ts
│ │ │ │ └── switch
│ │ │ │ │ ├── switch.component.ts
│ │ │ │ │ ├── switch.html
│ │ │ │ │ └── switch.scss
│ │ │ ├── collapse-box
│ │ │ │ ├── collapse-box.component.html
│ │ │ │ ├── collapse-box.component.scss
│ │ │ │ ├── collapse-box.component.ts
│ │ │ │ ├── collapse-box.module.ts
│ │ │ │ └── index.ts
│ │ │ ├── http-interceptor.ts
│ │ │ ├── index.ts
│ │ │ ├── msg-box
│ │ │ │ ├── index.ts
│ │ │ │ ├── msg-box.component.html
│ │ │ │ ├── msg-box.component.scss
│ │ │ │ ├── msg-box.component.ts
│ │ │ │ └── msg-box.module.ts
│ │ │ ├── notifications
│ │ │ │ ├── index.ts
│ │ │ │ ├── notification.model.ts
│ │ │ │ ├── notifications.component.html
│ │ │ │ ├── notifications.component.scss
│ │ │ │ ├── notifications.component.ts
│ │ │ │ ├── notifications.module.ts
│ │ │ │ └── notifications.service.ts
│ │ │ ├── password-macher.ts
│ │ │ ├── shared
│ │ │ │ ├── constants.ts
│ │ │ │ └── index.ts
│ │ │ └── validate-onblur.ts
│ │ ├── config
│ │ │ ├── aws-config.component.html
│ │ │ ├── aws-config.component.scss
│ │ │ ├── aws-config.component.ts
│ │ │ ├── bucket.component.html
│ │ │ ├── bucket.component.scss
│ │ │ ├── bucket.component.ts
│ │ │ ├── config.component.html
│ │ │ ├── config.component.scss
│ │ │ ├── config.component.ts
│ │ │ ├── index.ts
│ │ │ └── keys-config
│ │ │ │ ├── keys-config.component.html
│ │ │ │ ├── keys-config.component.scss
│ │ │ │ └── keys-config.component.ts
│ │ ├── dashboard
│ │ │ ├── dashboard.component.html
│ │ │ ├── dashboard.component.ts
│ │ │ └── index.ts
│ │ ├── environment.ts
│ │ ├── error-page
│ │ │ ├── error-page.component.ts
│ │ │ ├── error-page.html
│ │ │ └── index.ts
│ │ ├── eula
│ │ │ ├── eula.component.html
│ │ │ ├── eula.component.ts
│ │ │ ├── eula.scss
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── layout
│ │ │ ├── base
│ │ │ │ ├── base-layout.component.html
│ │ │ │ ├── base-layout.component.scss
│ │ │ │ ├── base-layout.component.ts
│ │ │ │ ├── base-layout.module.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── topbar
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── topbar.component.html
│ │ │ │ │ ├── topbar.component.scss
│ │ │ │ │ └── topbar.component.ts
│ │ │ ├── index.ts
│ │ │ └── intro
│ │ │ │ ├── index.ts
│ │ │ │ ├── intro-layout.component.html
│ │ │ │ ├── intro-layout.component.scss
│ │ │ │ ├── intro-layout.component.ts
│ │ │ │ └── intro-layout.module.ts
│ │ ├── login
│ │ │ ├── index.ts
│ │ │ ├── login.component.html
│ │ │ ├── login.component.scss
│ │ │ └── login.component.ts
│ │ ├── model
│ │ │ ├── account.model.ts
│ │ │ ├── bucket.model.ts
│ │ │ ├── config.model.ts
│ │ │ ├── dimension.model.ts
│ │ │ ├── graph.model.ts
│ │ │ ├── index.ts
│ │ │ ├── report.model.ts
│ │ │ ├── usage.model.ts
│ │ │ └── user.model.ts
│ │ ├── no-content
│ │ │ ├── index.ts
│ │ │ └── no-content.component.ts
│ │ ├── password
│ │ │ ├── index.ts
│ │ │ ├── password-data.ts
│ │ │ ├── password.component.html
│ │ │ └── password.component.ts
│ │ ├── pipe
│ │ │ ├── axc-pipe.module.ts
│ │ │ ├── date.pipe.ts
│ │ │ ├── index.ts
│ │ │ └── trim-text.pipe.ts
│ │ ├── report
│ │ │ ├── advanced-filter.component.html
│ │ │ ├── advanced-filter.component.ts
│ │ │ ├── advanced-filter.scss
│ │ │ ├── config.ts
│ │ │ ├── data-chart
│ │ │ │ ├── data-chart.component.html
│ │ │ │ ├── data-chart.component.ts
│ │ │ │ ├── data-chart.scss
│ │ │ │ └── index.ts
│ │ │ ├── index.ts
│ │ │ ├── report.component.html
│ │ │ ├── report.component.ts
│ │ │ ├── reporting.scss
│ │ │ └── tags-filter
│ │ │ │ ├── tags-filter.component.html
│ │ │ │ └── tags-filter.component.ts
│ │ ├── resolvers
│ │ │ ├── has-no-session.resolver.ts
│ │ │ ├── report.resolver.ts
│ │ │ └── who-am-i.resolver.ts
│ │ ├── services
│ │ │ ├── authentication.service.ts
│ │ │ ├── config.service.ts
│ │ │ ├── index.ts
│ │ │ ├── reporting.service.ts
│ │ │ └── user.service.ts
│ │ └── settings
│ │ │ ├── index.ts
│ │ │ ├── settings.component.html
│ │ │ ├── settings.component.scss
│ │ │ └── settings.component.ts
│ ├── assets
│ │ ├── css
│ │ │ ├── _base.scss
│ │ │ ├── _config.scss
│ │ │ ├── _reset.scss
│ │ │ ├── _typography.scss
│ │ │ ├── elements
│ │ │ │ ├── _buttons.scss
│ │ │ │ ├── _dropdown.scss
│ │ │ │ ├── _form.scss
│ │ │ │ ├── _list-group.scss
│ │ │ │ ├── _table.scss
│ │ │ │ ├── _tag.scss
│ │ │ │ ├── _toolbar.scss
│ │ │ │ └── _white-box.scss
│ │ │ └── main.scss
│ │ ├── favicon
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-256x256.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── browserconfig.xml
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── manifest.json
│ │ │ ├── mstile-150x150.png
│ │ │ └── safari-pinned-tab.svg
│ │ └── icons
│ │ │ ├── AXClaudia_logo.png
│ │ │ ├── AX_Logo.svg
│ │ │ ├── axlogo_teal.svg
│ │ │ ├── full_logo.svg
│ │ │ ├── ico_environments.svg
│ │ │ ├── ico_failed.svg
│ │ │ ├── ico_success.svg
│ │ │ ├── ico_users.svg
│ │ │ └── logo.png
│ ├── custom-typings.d.ts
│ ├── index.html
│ ├── main.browser.ts
│ ├── polyfills.browser.ts
│ └── vendor.browser.ts
├── tsconfig.json
├── tslint.json
├── typedoc.json
└── webpack.config.js
├── userdb
├── schema.go
├── session.go
└── userdb.go
├── util
└── util.go
└── version.go
/.argo/claudia-ci.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | type: workflow
3 | version: 1
4 | name: Claudia Build
5 | description: Claudia Build Workflow
6 | inputs:
7 | parameters:
8 | COMMIT:
9 | default: "%%session.commit%%"
10 | REPO:
11 | default: "%%session.repo%%"
12 | steps:
13 | - CHECKOUT:
14 | image: argoproj/argoscm:v2.0
15 | command: ["axscm"]
16 | args: ["clone", "%%inputs.parameters.REPO%%", "/src", "--commit", "%%inputs.parameters.COMMIT%%"]
17 | resources:
18 | mem_mib: 256
19 | cpu_cores: 0.1
20 | outputs:
21 | artifacts:
22 | CODE:
23 | path: /src
24 | - BUILD:
25 | image: applatix/claudia-builder:latest
26 | command: [/src/build.py]
27 | args: [""] # Workaround argo issue https://github.com/argoproj/argo/issues/97
28 | inputs:
29 | artifacts:
30 | CODE:
31 | from: "%%steps.CHECKOUT.outputs.artifacts.CODE%%"
32 | path: "/src"
33 | resources:
34 | mem_mib: 256
35 | cpu_cores: 0.1
36 | annotations:
37 | ax_ea_docker_enable: '{ "graph-storage-name": "claudia-build", "graph-storage-size": "10Gi", "mem_mib": 1024, "cpu_cores": 0.3 }'
38 |
39 | ---
40 | type: policy
41 | version: 1
42 | name: Claudia Build Policy
43 | description: Policy to trigger build for all events
44 | template: Claudia Build
45 | notifications:
46 | - when:
47 | - on_success
48 | - on_failure
49 | whom:
50 | - committer
51 | - author
52 | when:
53 | - event: on_push
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .project
2 | .vscode/
3 | .DS_Store
4 | ami/claudia.tar.gz
5 | ami/docker-compose.yml
6 | # Delve debug binaries
7 | */debug
8 | .idea/
9 | site/
10 | dist/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### Version 1.1.0 (2017-08-30)
4 | * Open source release
5 | * Cache bucket region during configuration
6 |
7 | ### Version 1.0.9 (2017-04-27)
8 | * Fix issue preventing the ingestion of reports with Redshift/QuickSight option enabled
9 | * Better error reporting to UI
10 |
11 | ### Version 1.0.8 (2017-04-25)
12 | * Fix issue where some metrics (e.g. instance hours) would appear twice in the metrics selector
13 |
14 | ### Version 1.0.7 (2017-04-20)
15 | * Eliminate the GetBucketLocation requirement for the Claudia IAM policy
16 |
17 | ### Version 1.0.6 (2017-04-11)
18 | * Accomodate changes to AWS Cost & Usage reports with respect to reserved instance pricing
19 |
20 | ### Version 1.0.5 (2017-04-08)
21 | * Improved region detection of billing report line items
22 |
23 | ### Version 1.0.4 (2017-04-07)
24 | * Add support for AWS GovCloud (US) region
25 | * Better handling of new regions
26 |
27 | ### Version 1.0.3 (2017-04-04)
28 | * Fix issue preventing the configuration of a bucket residing in us-east-1
29 |
30 | ### Version 1.0.2 (2017-03-17)
31 | * Add indicator when billing reports are currently being processed
32 | * When selecting hourly interval, the hour of the datapoint is properly displayed in the tooltip
33 | * Support report names and paths with special characters, and reports which did not configure a "report path prefix"
34 | * Eliminate some resource leaks
35 |
36 | ### Version 1.0.1 (2017-02-23)
37 | * Initial release
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile for the Claudia API/web server, and ingest daemon
2 |
3 | FROM debian:9.1
4 |
5 | RUN apt-get update && apt-get install -y \
6 | # ca-certificates required for AWS SDK
7 | ca-certificates && \
8 | apt-get clean && \
9 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
10 |
11 | EXPOSE 80 443
12 |
13 | COPY dist/bin/* /usr/bin/
14 | COPY ui/dist /var/lib/claudia/assets
15 |
16 | CMD ["/usr/bin/claudiad"]
17 |
--------------------------------------------------------------------------------
/Dockerfile-builder:
--------------------------------------------------------------------------------
1 | # This container creates the claudia builder image, which contains the environment necessary to build this project (e.g. golang, node)
2 |
3 | FROM debian:9.1
4 |
5 | # Install python, git, packer, mkdocs
6 | # NOTE: python2 is installed over python3 because python2 eventually gets installed
7 | # by the nodejs setup script anyways, and mkdocs prefers python2 over python3
8 | # because of click incompatibilites.
9 | RUN apt-get update && apt-get install -y \
10 | wget \
11 | python-minimal \
12 | git \
13 | curl \
14 | gcc \
15 | gnupg \
16 | unzip && \
17 | curl https://bootstrap.pypa.io/get-pip.py | python && \
18 | pip install --no-cache-dir mkdocs==0.16.1 && \
19 | wget https://releases.hashicorp.com/packer/0.12.2/packer_0.12.2_linux_amd64.zip && \
20 | unzip -d /usr/bin packer*.zip && \
21 | rm -f packer*.zip && \
22 | apt-get clean && \
23 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
24 |
25 | # Install docker (necessary for using this image as the build container during Argo CI)
26 | ENV DOCKER_BUCKET get.docker.com
27 | ENV DOCKER_VERSION 1.11.2
28 | ENV DOCKER_SHA256 8c2e0c35e3cda11706f54b2d46c2521a6e9026a7b13c7d4b8ae1f3a706fc55e1
29 |
30 | RUN set -x \
31 | && curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz" -o docker.tgz \
32 | && echo "${DOCKER_SHA256} *docker.tgz" | sha256sum -c - \
33 | && tar -xzvf docker.tgz \
34 | && mv docker/* /usr/local/bin/ \
35 | && rmdir docker \
36 | && rm docker.tgz \
37 | && docker -v
38 |
39 | # Install go
40 | ENV GO_VERSION 1.8.3
41 | ENV GO_ARCH amd64
42 | RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
43 | tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \
44 | rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz
45 |
46 | # Install nodejs
47 | RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && apt-get install -y nodejs && \
48 | apt-get clean && \
49 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
50 |
51 | ENV GOPATH /root/go
52 | ENV PATH ${GOPATH}/bin:/usr/local/go/bin:${PATH}
53 |
54 | # Install glide
55 | RUN mkdir -p ${GOPATH}/bin && \
56 | mkdir -p ${GOPATH}/pkg && \
57 | mkdir -p ${GOPATH}/src && \
58 | wget https://glide.sh/get && \
59 | chmod ugo+x ./get && \
60 | ./get && \
61 | rm -f get && \
62 | rm -rf /tmp/glide*
63 |
64 | # Install Go dependencies and some tooling
65 | COPY glide.yaml ${GOPATH}
66 | COPY glide.lock ${GOPATH}
67 | RUN cd ${GOPATH} && \
68 | glide install && \
69 | mv vendor/* src/ && \
70 | rmdir vendor && \
71 | go get -u -v github.com/derekparker/delve/cmd/dlv && \
72 | go get -u -v gopkg.in/alecthomas/gometalinter.v1 && \
73 | cd ${GOPATH}/bin && \
74 | ln -s gometalinter.v1 gometalinter && \
75 | gometalinter --install
76 |
77 | # Install Node dependencies
78 | COPY ui/package.json /root/node/package.json
79 | COPY ui/npm-shrinkwrap.json /root/node/npm-shrinkwrap.json
80 | RUN cd /root/node && npm install
81 |
82 | # MkDocs Themes
83 | RUN pip install mkdocs-bootswatch
84 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2017 Applatix, Inc.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License..
14 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 1.1.1
--------------------------------------------------------------------------------
/ami/docker-compose-ami.yml.in:
--------------------------------------------------------------------------------
1 | # AMI version of app which exposes only the http port and expects EBS volume mounted under /mnt/claudia
2 | version: '2'
3 | services:
4 | costdb:
5 | image: influxdb:1.2.0
6 | expose:
7 | - "8086"
8 | environment:
9 | - INFLUXDB_HTTP_MAX_ROW_LIMIT=40000
10 | - INFLUXDB_REPORTING_DISABLED=true
11 | volumes:
12 | - /mnt/claudia/influxdb:/var/lib/influxdb
13 | restart: always
14 | userdb:
15 | image: postgres:9.6.1
16 | expose:
17 | - "5432"
18 | volumes:
19 | - /mnt/claudia/postgres:/var/lib/postgresql/data
20 | env_file:
21 | - ./config.env
22 | restart: always
23 | ingestd:
24 | image: "${IMAGE_TAG}"
25 | entrypoint:
26 | - /usr/bin/ingestd
27 | - run
28 | depends_on:
29 | - costdb
30 | - userdb
31 | expose:
32 | - "8081"
33 | env_file:
34 | - ./config.env
35 | restart: always
36 | server:
37 | image: "${IMAGE_TAG}"
38 | depends_on:
39 | - costdb
40 | - userdb
41 | ports:
42 | - "80:80"
43 | - "443:443"
44 | env_file:
45 | - ./config.env
46 | restart: always
47 |
--------------------------------------------------------------------------------
/ami/docker-storage:
--------------------------------------------------------------------------------
1 | DOCKER_STORAGE_OPTIONS="--storage-driver=overlay2"
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Build the binary in native platform. This script is invoked inside the builder container
3 | # as well as for development purposes (e.g. Mac)
4 |
5 | set -xe
6 | SRCROOT=$(cd $(dirname $0);pwd)
7 |
8 | PACKAGE_NAME="github.com/applatix/claudia"
9 |
10 | echo "Linting source"
11 | gometalinter --config ${SRCROOT}/gometalinter.json --deadline 2m ${SRCROOT}/...
12 |
13 | CLAUDIA_VERSION=$(cat $SRCROOT/VERSION)
14 | CLAUDIA_REVISION=$(git -C $SRCROOT rev-parse --short=7 HEAD)
15 | dirty=$(git -C $SRCROOT status --porcelain)
16 | if [ ! -z "$dirty" ]; then
17 | CLAUDIA_REVISION="${CLAUDIA_REVISION}-dirty"
18 | fi
19 | BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%S')
20 |
21 | # GOBIN is manupulated so that binaries end up in the project directory,
22 | # so that we can subsequently package it into a container.
23 | export GOBIN="$SRCROOT/dist/bin"
24 | # -pkgdir is used to allow repeat builds to complete
25 | PKGDIR="$SRCROOT/dist/$(go env GOOS)_$(go env GOARCH)"
26 |
27 | LD_FLAGS="-X $PACKAGE_NAME.Version=$CLAUDIA_VERSION -X $PACKAGE_NAME.Revision=$CLAUDIA_REVISION -X $PACKAGE_NAME.BuildDate=$BUILD_DATE"
28 | # purge any precompiled claudia archive files in order to guarantee version
29 | # and build date information is updated in the resulting binaries.
30 | # `go install` will decide to skip the build if it thinks there were no
31 | # source code changes, despite passing different LD_FLAGS.
32 | rm -rf "$PKGDIR/$PACKAGE_NAME"
33 | go env
34 | go install -v -pkgdir "$PKGDIR" -ldflags "$LD_FLAGS" $PACKAGE_NAME/claudiad
35 | go install -v -pkgdir "$PKGDIR" -ldflags "$LD_FLAGS" $PACKAGE_NAME/ingestd
36 |
--------------------------------------------------------------------------------
/config.env:
--------------------------------------------------------------------------------
1 | POSTGRES_PASSWORD=my-secret-pw
2 | POSTGRES_DB=userdb
3 |
--------------------------------------------------------------------------------
/constants.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Applatix, Inc.
2 | package claudia
3 |
4 | import "time"
5 |
6 | // Application constants
7 | const (
8 | AWSMarketplaceAccountID = "679593333241"
9 | )
10 |
11 | // Specially treated services
12 | const (
13 | ServiceAWSS3 = "AWS S3"
14 | ServiceAWSEC2Instance = "AWS EC2 Instance"
15 | ServiceAWSMarketplace = "AWS Marketplace"
16 | ServiceAWSEBSVolume = "AWS EBS Volume"
17 | ServiceAWSCloudWatch = "AWS CloudWatch"
18 | ServiceAWSCloudFront = "AWS CloudFront"
19 | ServiceAWSEC2DataTransfer = "AWS EC2 Data Transfer"
20 | )
21 |
22 | // Application configuration settings
23 | var (
24 | ApplicationPort = 443
25 | ApplicationDir = "/var/lib/claudia"
26 | ApplicationAdminUsername = "admin"
27 | ApplicationAdminDefaultPassword = "password"
28 | IngestdURL = "http://ingestd:8081"
29 | IngestdPort = 8081
30 | IngestdWorkers = 2
31 | IngestdBatchInterval = 5000
32 | IngestStatusMeasurementName = "ingest_status"
33 | IngestdWriteRetryDelay = []time.Duration{10 * time.Second, 20 * time.Second, 40 * time.Second, 60 * time.Second, 120 * time.Second}
34 | CostDatabaseURL = "http://costdb:8086"
35 | CostDatabaseName = "cost_usage"
36 | ReportDefaultRetentionDays = 365
37 | )
38 |
39 | // ReportStatus is the status of a report. One of: "processing", "error", "current"
40 | type ReportStatus string
41 |
42 | // Valid report statuses
43 | const (
44 | ReportStatusCurrent ReportStatus = "current"
45 | ReportStatusError ReportStatus = "error"
46 | ReportStatusProcessing ReportStatus = "processing"
47 | )
48 |
--------------------------------------------------------------------------------
/docker-compose-debug.yml:
--------------------------------------------------------------------------------
1 | # "Debug" version of app which exposes database ports and also maps source directory to container and leaves container running
2 | version: '2'
3 | services:
4 | costdb:
5 | image: influxdb:1.2.0
6 | expose:
7 | - "8086"
8 | - "8083"
9 | ports:
10 | - "8086:8086"
11 | - "8083:8083"
12 | environment:
13 | - INFLUXDB_HTTP_MAX_ROW_LIMIT=40000
14 | - INFLUXDB_ADMIN_ENABLED=true
15 | volumes:
16 | - costdb_vol:/var/lib/influxdb
17 | userdb:
18 | image: postgres:9.6.1
19 | expose:
20 | - "5432"
21 | ports:
22 | - "5432:5432"
23 | volumes:
24 | - userdb_vol:/var/lib/postgresql/data
25 | env_file:
26 | - ./config.env
27 | ingestd:
28 | image: claudia
29 | entrypoint:
30 | - "sh"
31 | - "-c"
32 | - "/usr/bin/ingestd run & sleep 365d"
33 | depends_on:
34 | - costdb
35 | - userdb
36 | expose:
37 | - "8081"
38 | ports:
39 | - "8081:8081"
40 | env_file:
41 | - ./config.env
42 | server:
43 | image: claudia
44 | depends_on:
45 | - costdb
46 | - userdb
47 | expose:
48 | - "80"
49 | - "443"
50 | ports:
51 | - "80:80"
52 | - "443:443"
53 | entrypoint:
54 | - "sh"
55 | - "-c"
56 | - "/usr/bin/claudiad & sleep 365d"
57 | env_file:
58 | - ./config.env
59 | volumes:
60 | - .:/src
61 | volumes:
62 | costdb_vol:
63 | userdb_vol:
64 |
--------------------------------------------------------------------------------
/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | # Development version of compose file which starts only the database services and exposes their ports
2 | # to the host. This way api servers can be developed against them outside the context of linked containers.
3 | version: '2'
4 | services:
5 | costdb:
6 | image: influxdb:1.2.0
7 | expose:
8 | - "8086"
9 | - "8083"
10 | ports:
11 | - "8086:8086"
12 | - "8083:8083"
13 | environment:
14 | - INFLUXDB_HTTP_MAX_ROW_LIMIT=40000
15 | - INFLUXDB_ADMIN_ENABLED=true
16 | volumes:
17 | - costdb_vol:/var/lib/influxdb
18 | userdb:
19 | image: postgres:9.6.1
20 | expose:
21 | - "5432"
22 | ports:
23 | - "5432:5432"
24 | volumes:
25 | - userdb_vol:/var/lib/postgresql/data
26 | env_file:
27 | - ./config.env
28 | volumes:
29 | costdb_vol:
30 | userdb_vol:
31 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # "Production" version of app which exposes only the http port
2 | version: '2'
3 | services:
4 | costdb:
5 | image: influxdb:1.2.0
6 | expose:
7 | - "8086"
8 | environment:
9 | - INFLUXDB_HTTP_MAX_ROW_LIMIT=40000
10 | - INFLUXDB_REPORTING_DISABLED=true
11 | volumes:
12 | - costdb_vol:/var/lib/influxdb
13 | restart: always
14 | userdb:
15 | image: postgres:9.6.1
16 | expose:
17 | - "5432"
18 | volumes:
19 | - userdb_vol:/var/lib/postgresql/data
20 | env_file:
21 | - ./config.env
22 | restart: always
23 | ingestd:
24 | image: claudia
25 | entrypoint:
26 | - /usr/bin/ingestd
27 | - run
28 | depends_on:
29 | - costdb
30 | - userdb
31 | expose:
32 | - "8081"
33 | env_file:
34 | - ./config.env
35 | restart: always
36 | server:
37 | image: claudia
38 | depends_on:
39 | - costdb
40 | - userdb
41 | ports:
42 | - "80:80"
43 | - "443:443"
44 | env_file:
45 | - ./config.env
46 | restart: always
47 | volumes:
48 | costdb_vol:
49 | userdb_vol:
50 |
--------------------------------------------------------------------------------
/docs/about/contact.md:
--------------------------------------------------------------------------------
1 | Redirecting...
2 |
--------------------------------------------------------------------------------
/docs/about/release-notes.md:
--------------------------------------------------------------------------------
1 | ../../CHANGELOG.md
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | #### How frequently is the data updated?
4 | AWS generates and delivers a new Cost and Usage report at least once a day to your billing bucket. Claudia checks once an hour for any new reports.
5 |
6 | #### What instance type should I use to run the app?
7 | Choosing the appropriate instance type depends on several factors, which include
8 |
9 | * volume of data
10 | * retention length of the report
11 | * cardinality of the data (which is the uniqueness of values in a column)
12 | * performance expectations of queries
13 |
14 | In general, you want enough memory to ensure your application performs well. Applatix recommends a minimum of 8 GB of memory. So instance types such as m4.large, m3.large, and t2.large are good starting points.
15 |
16 | #### Why is there partially missing data on the last day of the month?
17 | That's because the data for the last day of the month has not been "finalized" by AWS. The process of finalizing the report can take up to several days. Until AWS delivers this report to the billing bucket, there can be partially missing data for the last day of each month.
18 |
19 | #### What is the difference between AWS Cost and Usage Reports vs. Detailed Billing Reports?
20 | AWS Cost and Usage Reports is a newer billing report format (introduced December 2015) versus the classic Detailed Billing Reports (DBR) format (introduced December 2012).
21 |
22 | Compared to the DBR, the Cost and Usage Reports provide additional columns, which enable more dimensions to explore the data.
23 |
24 | Most importantly, the Cost and Usage Reports now provide clear indications
25 | on what instance pricing (reserved versus "on demand") was applied to an instance during usage. This enables you to better understand the effectiveness of your reserved instance purchases.
26 |
27 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Claudia
2 |
3 | ## Introduction
4 |
5 | Claudia is a Cost Analytics solution that provides insights into your AWS cloud spending.
6 |
7 | 
8 |
9 | ## Key Features
10 | * Hourly to Monthly reporting of cost and usage
11 | * Deep drill down of spending by accounts, services, regions, and more
12 | * AWS Service level dimensions and metrics
13 | * Multiple cloud accounts organized into a single report
14 | * Tagging for categorizing your resources
15 |
16 | Try out Claudia by following our easy [install instructions](setup/claudia.md).
17 |
18 | ## Applatix
19 | In addition to using Claudia, you can also try our full cloud management solution including cost and resource optimization provided by Applatix. To request a demo, contact us at [https://applatix.com/contact/](https://applatix.com/contact/).
--------------------------------------------------------------------------------
/docs/report-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/report-screenshot.png
--------------------------------------------------------------------------------
/docs/setup/aws/attach-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/attach-policy.png
--------------------------------------------------------------------------------
/docs/setup/aws/copy-credentials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/copy-credentials.png
--------------------------------------------------------------------------------
/docs/setup/aws/create-report-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/create-report-1.png
--------------------------------------------------------------------------------
/docs/setup/aws/create-report-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/create-report-2.png
--------------------------------------------------------------------------------
/docs/setup/aws/create-report-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/create-report-3.png
--------------------------------------------------------------------------------
/docs/setup/aws/create-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/create-user.png
--------------------------------------------------------------------------------
/docs/setup/aws/enable-createdby-tag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/enable-createdby-tag.png
--------------------------------------------------------------------------------
/docs/setup/aws/enable-user-tags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/aws/enable-user-tags.png
--------------------------------------------------------------------------------
/docs/setup/claudia/account-aliases.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/claudia/account-aliases.png
--------------------------------------------------------------------------------
/docs/setup/claudia/add-bucket.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/claudia/add-bucket.png
--------------------------------------------------------------------------------
/docs/setup/claudia/bucket-information.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/claudia/bucket-information.png
--------------------------------------------------------------------------------
/docs/setup/claudia/http-sg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/claudia/http-sg.png
--------------------------------------------------------------------------------
/docs/setup/claudia/manage-certificates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/docs/setup/claudia/manage-certificates.png
--------------------------------------------------------------------------------
/glide.lock:
--------------------------------------------------------------------------------
1 | hash: 819f63d9b5527227d331f8d0e2af3a8323efa623c567772fd5b5a030db3e17ee
2 | updated: 2017-02-02T00:22:52.030554787-08:00
3 | imports:
4 | - name: github.com/aws/aws-sdk-go
5 | version: 3f8f870ec9939e32b3372abf74d24e468bcd285d
6 | subpackages:
7 | - aws
8 | - aws/awserr
9 | - aws/awsutil
10 | - aws/client
11 | - aws/client/metadata
12 | - aws/corehandlers
13 | - aws/credentials
14 | - aws/credentials/ec2rolecreds
15 | - aws/credentials/endpointcreds
16 | - aws/credentials/stscreds
17 | - aws/defaults
18 | - aws/ec2metadata
19 | - aws/endpoints
20 | - aws/request
21 | - aws/session
22 | - aws/signer/v4
23 | - private/protocol
24 | - private/protocol/ec2query
25 | - private/protocol/query
26 | - private/protocol/query/queryutil
27 | - private/protocol/rest
28 | - private/protocol/restxml
29 | - private/protocol/xml/xmlutil
30 | - private/waiter
31 | - service/ec2
32 | - service/s3
33 | - service/s3/s3iface
34 | - service/s3/s3manager
35 | - service/sts
36 | - name: github.com/go-ini/ini
37 | version: 2ba15ac2dc9cdf88c110ec2dc0ced7fa45f5678c
38 | - name: github.com/gorilla/context
39 | version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
40 | - name: github.com/gorilla/handlers
41 | version: ee54c7b44cab12289237fb8631314790076e728b
42 | - name: github.com/gorilla/mux
43 | version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
44 | - name: github.com/gorilla/securecookie
45 | version: 667fe4e3466a040b780561fe9b51a83a3753eefc
46 | - name: github.com/gorilla/sessions
47 | version: ca9ada44574153444b00d3fd9c8559e4cc95f896
48 | - name: github.com/influxdata/influxdb
49 | version: b7bb7e8359642b6e071735b50ae41f5eb343fd42
50 | subpackages:
51 | - client/v2
52 | - models
53 | - pkg/escape
54 | - toml
55 | - name: github.com/jmespath/go-jmespath
56 | version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
57 | - name: github.com/jmoiron/sqlx
58 | version: cac998c4f0959c19c638c523e374fa8e4e0bcfe3
59 | subpackages:
60 | - reflectx
61 | - name: github.com/lib/pq
62 | version: 5bf161122cd640c2a5a2c1d7fa49ea9befff31dd
63 | subpackages:
64 | - oid
65 | - name: github.com/pkg/errors
66 | version: 645ef00459ed84a119197bfb8d8205042c6df63d
67 | - name: github.com/urfave/cli
68 | version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
69 | - name: golang.org/x/crypto
70 | version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
71 | subpackages:
72 | - bcrypt
73 | - blowfish
74 | testImports: []
75 |
--------------------------------------------------------------------------------
/glide.yaml:
--------------------------------------------------------------------------------
1 | package: .
2 | import:
3 | - package: github.com/aws/aws-sdk-go
4 | version: ~1.6.9
5 | subpackages:
6 | - aws
7 | - aws/credentials
8 | - aws/session
9 | - service/s3
10 | - service/s3/s3manager
11 | - package: github.com/gorilla/handlers
12 | version: ~1.1.0
13 | - package: github.com/gorilla/mux
14 | version: ~1.1.0
15 | - package: github.com/gorilla/securecookie
16 | version: ~1.1.0
17 | - package: github.com/gorilla/sessions
18 | version: ~1.1.0
19 | - package: github.com/influxdata/influxdb
20 | version: ~1.2.0
21 | subpackages:
22 | - client/v2
23 | - models
24 | - package: github.com/jmoiron/sqlx
25 | - package: github.com/lib/pq
26 | - package: github.com/urfave/cli
27 | version: ~1.19.1
28 | - package: golang.org/x/crypto
29 | subpackages:
30 | - bcrypt
31 | - package: github.com/pkg/errors
32 | version: ~0.8.0
33 |
--------------------------------------------------------------------------------
/gometalinter.json:
--------------------------------------------------------------------------------
1 | {
2 | "DisableAll": true,
3 | "Enable": [
4 | "vet",
5 | "deadcode",
6 | "errcheck",
7 | "varcheck",
8 | "structcheck",
9 | "ineffassign",
10 | "aligncheck",
11 | "unconvert"
12 | ]
13 | }
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Claudia Docs
2 | site_url: https://applatix.github.io/claudia/
3 | theme: yeti
4 | copyright: Copyright © 2017 Applatix
5 | repo_url: https://github.com/applatix/claudia/
6 | strict: true
7 | pages:
8 | - Home: 'index.md'
9 | - Setup:
10 | - AWS: 'setup/aws.md'
11 | - Claudia: 'setup/claudia.md'
12 | - FAQ: 'faq.md'
13 | - About:
14 | - 'Release Notes': 'about/release-notes.md'
15 | - 'Contact': 'about/contact.md'
16 |
--------------------------------------------------------------------------------
/packer.json:
--------------------------------------------------------------------------------
1 | {
2 | "variables": {
3 | "aws_access_key": "",
4 | "aws_secret_key": "",
5 | "build_timestamp": "",
6 | "full_version": "",
7 | "version": ""
8 | },
9 | "builders": [{
10 | "type": "amazon-ebs",
11 | "access_key": "{{user `aws_access_key`}}",
12 | "secret_key": "{{user `aws_secret_key`}}",
13 | "region": "us-east-1",
14 | "source_ami": "ami-0b33d91d",
15 | "instance_type": "m3.medium",
16 | "ssh_username": "ec2-user",
17 | "ami_name": "claudia-v{{user `full_version`}}-{{user `build_timestamp`}}",
18 | "ami_description": "Claudia v{{user `version`}}",
19 |
20 | "ami_block_device_mappings": [
21 | {
22 | "device_name": "/dev/xvdl",
23 | "volume_size": 40,
24 | "volume_type": "gp2",
25 | "delete_on_termination": true
26 | }],
27 | "launch_block_device_mappings": [
28 | {
29 | "device_name": "/dev/xvdl",
30 | "volume_size": 40,
31 | "volume_type": "gp2",
32 | "delete_on_termination": true
33 | }]
34 | }],
35 | "provisioners": [
36 | {
37 | "type": "file",
38 | "source": "ami/docker-storage",
39 | "destination": "/tmp/docker-storage"
40 | },
41 | {
42 | "type": "file",
43 | "source": "ami/claudia.tar.gz",
44 | "destination": "/tmp/claudia.tar.gz"
45 | },
46 | {
47 | "type": "file",
48 | "source": "ami/docker-compose.yml",
49 | "destination": "/home/ec2-user/docker-compose.yml"
50 | },
51 | {
52 | "type": "file",
53 | "source": "config.env",
54 | "destination": "/home/ec2-user/config.env"
55 | },
56 | {
57 | "type": "shell",
58 | "inline": [
59 | "sudo yum update -y",
60 | "sudo pvcreate /dev/xvdl",
61 | "sudo vgcreate vgclaudia /dev/xvdl",
62 | "sudo lvcreate --wipesignatures y -l 100%VG -n lvclaudia vgclaudia",
63 | "sudo mkfs -t ext4 /dev/vgclaudia/lvclaudia",
64 | "sudo sh -c \"echo '/dev/vgclaudia/lvclaudia /mnt/claudia ext4 defaults,noatime 0 0' >> /etc/fstab\"",
65 | "sudo mkdir /mnt/claudia",
66 | "sudo mount /mnt/claudia",
67 | "sudo yum install -y docker",
68 | "sudo cp /tmp/docker-storage /etc/sysconfig/docker-storage",
69 | "sudo rm -f /tmp/docker-storage",
70 | "sudo usermod -a -G docker ec2-user",
71 | "sudo rm -rf /var/lib/docker/*",
72 | "sudo service docker start",
73 | "until sudo docker info; do sleep 3; done",
74 | "sudo docker load -i /tmp/claudia.tar.gz",
75 | "sudo rm -f /tmp/claudia.tar.gz",
76 | "sudo curl -L \"https://github.com/docker/compose/releases/download/1.11.1/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/bin/docker-compose",
77 | "sudo chmod ugo+x /usr/bin/docker-compose",
78 | "sudo docker network create ec2user_default",
79 | "sudo docker-compose -f /home/ec2-user/docker-compose.yml create",
80 | "sudo rm -f /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys",
81 | "sudo rm -f /root/docker/config.json /home/*/docker/config.json",
82 | "sudo rm -f /root/.*history /home/*/.*history"
83 | ]
84 | }]
85 | }
86 |
--------------------------------------------------------------------------------
/routers/auth.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Applatix, Inc.
2 | package routers
3 |
4 | import (
5 | "encoding/json"
6 | "net/http"
7 |
8 | "github.com/applatix/claudia/server"
9 | "github.com/applatix/claudia/userdb"
10 | "github.com/applatix/claudia/util"
11 | )
12 |
13 | // authIdentityHandler is a HTTP handler which simply validates & decodes the session cooke to return current logged in user
14 | func authIdentityHandler(sc *server.ServerContext) func(http.ResponseWriter, *http.Request) {
15 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 | si, err := sc.SessionManager.ValidateSession(w, r)
17 | if err != nil {
18 | return
19 | }
20 | identity := make(map[string]interface{}, 0)
21 | identity["username"] = si.Username
22 | util.SuccessHandler(identity, w)
23 | })
24 | }
25 |
26 | // authIdentityHandler is a HTTP handler which authenticates a user and sets a new session cookie
27 | func authLoginHandler(sc *server.ServerContext) func(http.ResponseWriter, *http.Request) {
28 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29 | decoder := json.NewDecoder(r.Body)
30 | var reqUser userdb.User
31 | err := decoder.Decode(&reqUser)
32 | if util.ErrorHandler(err, w) != nil {
33 | return
34 | }
35 | defer r.Body.Close()
36 | dbUser, err := sc.UserDB.AuthenticateUser(reqUser.Username, reqUser.Password)
37 | if util.ErrorHandler(err, w) != nil {
38 | return
39 | }
40 | err = sc.SessionManager.SetSession(dbUser, w, r)
41 | if util.ErrorHandler(err, w) != nil {
42 | return
43 | }
44 | util.SuccessHandler(dbUser, w)
45 | })
46 | }
47 |
48 | // authIdentityHandler is a HTTP handler which will delete and expire the user session cookie
49 | func authLogoutHandler(sc *server.ServerContext) func(http.ResponseWriter, *http.Request) {
50 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51 | sc.SessionManager.DeleteSession(w, r)
52 | util.SuccessHandler(nil, w)
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/ui/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/ui/.editorconfig:
--------------------------------------------------------------------------------
1 | # @AngularClass
2 | # http://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | indent_style = space
9 | indent_size = 4
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | insert_final_newline = false
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # Compiled binary addons (http://nodejs.org/api/addons.html)
22 | build/Release
23 |
24 | # Users Environment Variables
25 | .lock-wscript
26 |
27 | # OS generated files #
28 | .DS_Store
29 | ehthumbs.db
30 | Icon?
31 | Thumbs.db
32 |
33 | # Node Files #
34 | /node_modules
35 | /bower_components
36 | npm-debug.log
37 |
38 | # Coverage #
39 | /coverage/
40 |
41 | # Typing #
42 | /src/typings/tsd/
43 | /typings/
44 | /tsd_typings/
45 |
46 | # Dist #
47 | /dist
48 | /public/__build__/
49 | /src/*/__build__/
50 | /__build__/**
51 | /public/dist/
52 | /src/*/dist/
53 | /dist/**
54 | /.awcache
55 | .webpack.json
56 |
57 | # Doc #
58 | /doc/
59 |
60 | # IDE #
61 | .idea/
62 | *.swp
63 |
64 |
65 | !icons/
66 |
--------------------------------------------------------------------------------
/ui/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "indent": 4,
5 | "latedef": true,
6 | "maxlen": 180,
7 | "newcap": true,
8 | "quotmark": "single",
9 | "strict": true,
10 | "undef": true,
11 | "unused": true,
12 | "eqnull": true,
13 |
14 | "eqeqeq": false,
15 | "loopfunc": true,
16 | "camelcase": false
17 | }
--------------------------------------------------------------------------------
/ui/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/*
2 |
--------------------------------------------------------------------------------
/ui/Dockerfile:
--------------------------------------------------------------------------------
1 | # Builds a Docker to deliver dist/
2 | FROM nginx:latest
3 | COPY dist/ /usr/share/nginx/html
--------------------------------------------------------------------------------
/ui/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # This script is intended to be run inside the builder container
3 |
4 | set -e
5 | SRCROOT=`dirname $0`
6 | SRCROOT=`cd $SRCROOT;pwd`
7 |
8 | # Tried to be fancy by manipulating NODE_PATH before running the build, but some utilities (e.g webpack)
9 | # as well as npm installed asset files aren't able to resolve libraries and asset locations cleanly.
10 | # For now, we blow away any node_modules directory in the ui dir, and symlink the node_modules that are
11 | # already baked into this container.
12 | #export NODE_PATH="/usr/lib/node_modules/claudia/node_modules"
13 | #export PATH="/usr/lib/node_modules/claudia/node_modules/.bin:$PATH"
14 | cd $SRCROOT
15 | rm -rf $SRCROOT/node_modules
16 | ln -s /root/node/node_modules
17 | npm run build:prod
18 |
--------------------------------------------------------------------------------
/ui/config/head-config.common.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | link: [
3 |
4 | ],
5 | meta: [
6 | ]
7 | };
8 |
--------------------------------------------------------------------------------
/ui/config/helpers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 |
5 | // Helper functions
6 | var ROOT = path.resolve(__dirname, '..');
7 |
8 | function hasProcessFlag(flag) {
9 | return process.argv.join('').indexOf(flag) > -1;
10 | }
11 |
12 | function isWebpackDevServer() {
13 | return process.argv[1] && !! (/webpack-dev-server/.exec(process.argv[1]));
14 | }
15 |
16 | function root(args) {
17 | args = Array.prototype.slice.call(arguments, 0);
18 | return path.join.apply(path, [ROOT].concat(args));
19 | }
20 |
21 | exports.hasProcessFlag = hasProcessFlag;
22 | exports.isWebpackDevServer = isWebpackDevServer;
23 | exports.root = root;
24 |
--------------------------------------------------------------------------------
/ui/config/modules/angular2-hmr-prod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.HmrState = function() {
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/ui/config/protractor.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('ts-node/register');
4 | var helpers = require('./helpers');
5 |
6 | exports.config = {
7 | baseUrl: 'http://localhost:3000/',
8 |
9 | // use `npm run e2e`
10 | specs: [
11 | helpers.root('src/**/**.e2e.ts'),
12 | helpers.root('src/**/*.e2e.ts')
13 | ],
14 | exclude: [],
15 |
16 | framework: 'jasmine2',
17 |
18 | allScriptsTimeout: 110000,
19 |
20 | jasmineNodeOpts: {
21 | showTiming: true,
22 | showColors: true,
23 | isVerbose: false,
24 | includeStackTrace: false,
25 | defaultTimeoutInterval: 400000
26 | },
27 | directConnect: true,
28 |
29 | capabilities: {
30 | 'browserName': 'chrome',
31 | 'chromeOptions': {
32 | 'args': ['show-fps-counter=true']
33 | }
34 | },
35 |
36 | onPrepare: function () {
37 | browser.ignoreSynchronization = true;
38 | },
39 |
40 | useAllAngular2AppRoots: true
41 | };
42 |
--------------------------------------------------------------------------------
/ui/config/spec-bundle.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Error.stackTraceLimit = Infinity;
4 |
5 | require('core-js/es6');
6 | require('core-js/es7/reflect');
7 |
8 | // Typescript emit helpers polyfill
9 | require('ts-helpers');
10 |
11 | require('zone.js/dist/zone');
12 | require('zone.js/dist/long-stack-trace-zone');
13 | require('zone.js/dist/proxy'); // since zone.js 0.6.15
14 | require('zone.js/dist/sync-test');
15 | require('zone.js/dist/jasmine-patch'); // put here since zone.js 0.6.14
16 | require('zone.js/dist/async-test');
17 | require('zone.js/dist/fake-async-test');
18 |
19 | // RxJS
20 | require('rxjs/Rx');
21 |
22 | var testing = require('@angular/core/testing');
23 | var browser = require('@angular/platform-browser-dynamic/testing');
24 |
25 | testing.TestBed.initTestEnvironment(
26 | browser.BrowserDynamicTestingModule,
27 | browser.platformBrowserDynamicTesting()
28 | );
29 |
30 | /*
31 | * Ok, this is kinda crazy. We can use the context method on
32 | * require that webpack created in order to tell webpack
33 | * what files we actually want to require or import.
34 | * Below, context will be a function/object with file names as keys.
35 | * Using that regex we are saying look in ../src then find
36 | * any file that ends with spec.ts and get its path. By passing in true
37 | * we say do this recursively
38 | */
39 | var testContext = require.context('../src', true, /\.spec\.ts/);
40 |
41 | /*
42 | * get all the files, for each file, call the context function
43 | * that will require the file and load it up here. Context will
44 | * loop and require those spec files here
45 | */
46 | function requireAll(requireContext) {
47 | return requireContext.keys().map(requireContext);
48 | }
49 |
50 | // requires and returns all modules that match
51 | var modules = requireAll(testContext);
52 |
--------------------------------------------------------------------------------
/ui/karma.conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: @AngularClass
3 | */
4 |
5 | // Look in ./config for karma.conf.js
6 | module.exports = require('./config/karma.conf.js');
7 |
--------------------------------------------------------------------------------
/ui/protractor.conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: @AngularClass
3 | */
4 |
5 | // look in ./config for protractor.conf.js
6 | exports.config = require('./config/protractor.conf.js').config;
7 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/change-password/change-password-data.ts:
--------------------------------------------------------------------------------
1 | export class ChangePasswordData {
2 | currentPassword: string;
3 | newPassword: string;
4 | confirmPassword: string;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/change-password/change-password.component.html:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/change-password/change-password.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { UserService } from '../../services';
4 | import { AppError } from '../../common/shared';
5 | import { NotificationsService } from '../../common/notifications';
6 | import { ChangePasswordData } from './change-password-data';
7 |
8 | @Component({
9 | selector: 'axc-change-password',
10 | templateUrl: './change-password.component.html',
11 | })
12 | export class ChangePasswordComponent {
13 |
14 | public changePasswordData: ChangePasswordData;
15 |
16 | constructor(private userService: UserService, private notificationService: NotificationsService) {
17 | this.changePasswordData = new ChangePasswordData();
18 | }
19 |
20 | public async doChangePassword(form) {
21 | await this.userService.changePassword(this.changePasswordData).then((res) => {
22 | this.notificationService.success('Password has been updated');
23 | this.changePasswordData = new ChangePasswordData();
24 | form.reset();
25 | }).catch((err) => {
26 | let appErr = err;
27 | this.notificationService.error(appErr.message || 'Can not update password. Please try again later.');
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/index.ts:
--------------------------------------------------------------------------------
1 | export * from './user-management.module';
2 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/user-details/user-details.component.html:
--------------------------------------------------------------------------------
1 | Foo User
2 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/user-details/user-details.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .user-details {
4 |
5 | &__title-row {
6 | axc-avatar {
7 | display: inline-block;
8 | vertical-align: top;
9 | padding-top: 4px;
10 | }
11 |
12 | h3 {
13 | display: inline-block;
14 | margin: 0;
15 | line-height: 2em;
16 |
17 | i {
18 | color: $ax-color-gray;
19 | font-size: 1em;
20 | padding-left: 0.5em;
21 | }
22 | }
23 |
24 | .editNameForm {
25 | display: inline-block;
26 | line-height: 2em;
27 | }
28 | }
29 |
30 | &__enable-slide {
31 | padding-right: 7.14em;
32 | padding-top: 1em;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/user-details/user-details.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-user-details',
5 | templateUrl: './user-details.component.html',
6 | styles: [require('./user-details.component.scss')],
7 | })
8 | export class UserDetailsComponent {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/ui/src/app/+user-management/user-management.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
3 | import { NgModule } from '@angular/core';
4 | import { RouterModule } from '@angular/router';
5 |
6 | import { ChangePasswordComponent } from './change-password/change-password.component';
7 | import { UserDetailsComponent } from './user-details/user-details.component';
8 | import { AxLibModule, AxcCommonModule } from '../common';
9 | import { CollapseBoxModule } from '../common/collapse-box';
10 | import { ValidateOnBlurDirective } from '../common/validate-onblur';
11 | import { AxcPipeModule } from '../pipe/axc-pipe.module';
12 |
13 | export const routes = [
14 | { path: '', component: ChangePasswordComponent, data: { title: 'Users' } },
15 | { path: 'change-password', component: ChangePasswordComponent, data: { title: 'Change Password' } },
16 | { path: 'user-details/:id', component: UserDetailsComponent, data: { title: 'User details' } },
17 | ];
18 |
19 | @NgModule({
20 | declarations: [
21 | ChangePasswordComponent,
22 | UserDetailsComponent,
23 | ValidateOnBlurDirective,
24 | ],
25 | imports: [
26 | AxcPipeModule,
27 | AxLibModule,
28 | AxcCommonModule,
29 | CollapseBoxModule,
30 | CommonModule,
31 | FormsModule,
32 | ReactiveFormsModule,
33 | RouterModule.forChild(routes),
34 | ],
35 | })
36 | export default class UserManagementModule {
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/ui/src/app/accounts/accounts.component.html:
--------------------------------------------------------------------------------
1 |
2 | Account Alias Names
3 |
4 | No accounts are available to alias. Please complete report setup and accounts will start appearing as data processing completes.
5 |
6 | 0">
7 |
8 |
9 | AWS Account ID
10 | Alias name
11 |
12 |
13 |
14 |
15 | {{ account.aws_account_id }}
16 |
17 |
21 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/ui/src/app/accounts/accounts.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../assets/css/config';
2 |
3 | .input-edit {
4 | padding: 0;
5 | color: $ax-color-gray-7;
6 | background: transparent;
7 | border-color: transparent;
8 | outline: 0;
9 |
10 | &:not([readonly]) {
11 | padding: 8px;
12 | background: $ax-color-gray-1;
13 | border: 1px solid $ax-color-gray-4;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ui/src/app/accounts/accounts.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | import { Report, Account } from '../model';
5 | import { ReportingService, ConfigService } from '../services';
6 | import { NotificationsService } from '../common/notifications/notifications.service';
7 |
8 | @Component({
9 | selector: 'axc-accounts',
10 | templateUrl: './accounts.component.html',
11 | encapsulation: ViewEncapsulation.None,
12 | styles: [
13 | require('./accounts.component.scss'),
14 | ],
15 | })
16 | export class AccountsComponent implements OnInit {
17 |
18 | public report: Report;
19 | public editedAccount: Account;
20 |
21 | constructor(
22 | private route: ActivatedRoute,
23 | private notificationsService: NotificationsService,
24 | private reportingService: ReportingService,
25 | private configService: ConfigService,
26 | ) { }
27 |
28 | public async ngOnInit() {
29 | this.report = await this.configService.getReportConfig();
30 | }
31 |
32 | public editAccount(account: Account) {
33 | this.editedAccount = account;
34 | }
35 |
36 | public saveAccount(index: number) {
37 | let newAccountData = this.editedAccount;
38 | this.reportingService.saveAccountName(this.report.id, this.editedAccount.aws_account_id, { name: this.editedAccount.name })
39 | .then(() => {
40 | this.report.accounts[index] = newAccountData;
41 | this.editedAccount = null;
42 | this.notificationsService.success('Account alias is updated.');
43 | }, () => {
44 | this.notificationsService.error('Something went wrong.');
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/ui/src/app/accounts/index.ts:
--------------------------------------------------------------------------------
1 | export * from './accounts.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-app',
5 | styles: [
6 | require('assets/css/main.scss'),
7 | ],
8 | encapsulation: ViewEncapsulation.None,
9 | templateUrl: 'app.component.html',
10 | })
11 | export class AppComponent {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/app/app.error-handler.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, ErrorHandler, Injector } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { ERROR_CODES } from './common/shared';
4 |
5 | @Injectable()
6 | export class AppErrorHandler extends ErrorHandler {
7 | private cachedRouter: Router;
8 |
9 | constructor(private injector: Injector) {
10 | super(false);
11 | }
12 |
13 | public handleError(error: any): void {
14 | if (error.rejection && error.rejection.code) {
15 | switch (error.rejection.code) {
16 | case ERROR_CODES.UNAUTHENTICATED_REQUEST:
17 | this.process401Error();
18 | break;
19 | case ERROR_CODES.UNAUTHORIZED_REQUEST:
20 | this.process403Error();
21 | break;
22 | case ERROR_CODES.ENTITY_NOT_FOUND:
23 | this.process404Error();
24 | break;
25 | default:
26 | console.error(error);
27 | }
28 | } else {
29 | if (error.rejection) {
30 | let rejection = error.rejection;
31 | console.error('Unhandled Promise rejection:',
32 | rejection instanceof Error ? rejection.message : rejection,
33 | '; Zone:', error.zone.name,
34 | '; Task:', error.task && error.task.source,
35 | '; Value:', rejection, rejection instanceof Error ? rejection.stack : undefined);
36 | } else {
37 | super.handleError(error);
38 | }
39 | }
40 | }
41 |
42 | private get router(): Router {
43 | if (!this.cachedRouter) {
44 | this.cachedRouter = this.injector.get(Router);
45 | }
46 | return this.cachedRouter;
47 | }
48 | /**
49 | * We wrote our setTimeout call to redirect
50 | * We are pretty much outside of ngZone - this will bring us back into game on that.
51 | */
52 | private doRedirect(url) {
53 | setTimeout(() => {
54 | this.router.navigateByUrl(url);
55 | }, 1);
56 | }
57 | /**
58 | * Specific handler for taking care of 401 error code if needed
59 | */
60 | private process401Error() {
61 | if (window.location.pathname.indexOf('login') === -1) {
62 | let path = window.location.pathname;
63 | this.doRedirect('/base/login/' + encodeURIComponent(path));
64 | }
65 | }
66 |
67 | /**
68 | * Specific handler for taking care of 403 error code if needed
69 | */
70 | private process403Error() {
71 | this.doRedirect('/error/403/type/' + ERROR_CODES.UNAUTHORIZED_REQUEST);
72 | }
73 |
74 | /**
75 | * Specific handler for taking care of 404 error code if needed
76 | */
77 | private process404Error() {
78 | this.doRedirect('/error/404/type/' + ERROR_CODES.ENTITY_NOT_FOUND);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/ui/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { Routes } from '@angular/router';
2 |
3 | import { LoginComponent } from './login';
4 | import { BaseLayoutComponent, IntroLayoutComponent } from './layout';
5 | import { ErrorPageComponent } from './error-page/error-page.component';
6 | import { WhoAmIResolver } from './resolvers/who-am-i.resolver';
7 | import { HasNoSessionResolver } from './resolvers/has-no-session.resolver';
8 | import { ReportResolver } from './resolvers/report.resolver';
9 | import { ReportComponent } from './report';
10 | import { DashboardComponent } from './dashboard';
11 | import { ConfigComponent } from './config';
12 | import { AccountsComponent } from './accounts';
13 | import { EulaComponent } from './eula';
14 | import { SettingsComponent } from './settings';
15 |
16 | export const ROUTES: Routes = [
17 | {
18 | path: 'app',
19 | component: BaseLayoutComponent,
20 | resolve: {
21 | whoAmIResolver: WhoAmIResolver,
22 | },
23 | children: [
24 | {
25 | path: 'users', loadChildren: () => System.import('./+user-management').then((comp: any) => {
26 | return comp.default;
27 | }),
28 | data: { title: 'Users' },
29 | },
30 | {
31 | path: 'report', component: ReportComponent, resolve: {
32 | report: ReportResolver,
33 | },
34 | },
35 | {
36 | path: 'accounts', component: AccountsComponent, resolve: {
37 | report: ReportResolver,
38 | },
39 | },
40 | {
41 | path: 'dashboard', component: DashboardComponent,
42 | },
43 | {
44 | path: 'config', component: SettingsComponent,
45 | },
46 | {
47 | path: 'settings', component: SettingsComponent,
48 | },
49 | ],
50 | },
51 | {
52 | path: 'base',
53 | component: IntroLayoutComponent,
54 | children: [
55 | {
56 | path: 'login/:fwd', component: LoginComponent, resolve: {
57 | hasNoSessionResolver: HasNoSessionResolver,
58 | },
59 | },
60 | {
61 | path: 'login', component: LoginComponent, resolve: {
62 | hasNoSessionResolver: HasNoSessionResolver,
63 | },
64 | },
65 | {
66 | path: 'eula', component: EulaComponent,
67 | resolve: {
68 | whoAmIResolver: WhoAmIResolver,
69 | },
70 | },
71 | ],
72 | },
73 | { path: 'error/:code', component: ErrorPageComponent },
74 | { path: 'error/:code/type/:type', component: ErrorPageComponent },
75 | { path: '**', redirectTo: 'base/login' },
76 | ];
77 |
--------------------------------------------------------------------------------
/ui/src/app/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | export type InternalStateType = {
4 | [key: string]: any
5 | };
6 |
7 | @Injectable()
8 | export class AppState {
9 | private appState: InternalStateType = { };
10 |
11 | get state() {
12 | return this.appState = this._clone(this.appState);
13 | }
14 |
15 | set state(value) {
16 | throw new Error('do not mutate the `.state` directly');
17 | }
18 |
19 | public get(prop?: any) {
20 | // use our state getter for the clone
21 | const state = this.state;
22 | return state.hasOwnProperty(prop) ? state[prop] : state;
23 | }
24 |
25 | public set(prop: string, value: any) {
26 | // internally mutate our state
27 | return this.appState[prop] = value;
28 | }
29 |
30 | private _clone(object: InternalStateType) {
31 | // simple object clone
32 | return JSON.parse(JSON.stringify( object ));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/src/app/common/abstract-value-accessor/abstract-value-accessor.ts:
--------------------------------------------------------------------------------
1 | import { ControlValueAccessor } from '@angular/forms';
2 |
3 | export abstract class AbstractValueAccessor implements ControlValueAccessor {
4 |
5 | private newValue: any = '';
6 |
7 | get value(): any {
8 | return this.newValue;
9 | };
10 |
11 | set value(v: any) {
12 | if (v !== this.newValue) {
13 | this.newValue = v;
14 | this.onChange(v);
15 | }
16 | }
17 |
18 | public writeValue(value: any) {
19 | this.newValue = value;
20 | this.onChange(value);
21 | }
22 |
23 | public onChange = (_) => {}; // tslint:disable-line
24 |
25 | public onTouched = () => {}; // tslint:disable-line
26 |
27 | public registerOnChange(fn: (_: any) => void): void {
28 | this.onChange = fn;
29 | }
30 |
31 | public registerOnTouched(fn: () => void): void {
32 | this.onTouched = fn;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/ax-lib.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { CheckboxComponent } from './checkbox';
6 | import { CheckboxSlideComponent } from './checkbox-slide';
7 | import { DropDownComponent, DropdownContentDirective, DropdownAnchorDirective } from './dropdown/dropdown.component';
8 | import { DateRangeComponent } from './date-range';
9 | import { MultiselectDropdownComponent } from './dropdown-multiselect/dropdown-multiselect.component';
10 | import { MultiSelectSearchFilter } from './dropdown-multiselect/dropdown-multiselect.component';
11 | import { RadioButtonComponent } from './radio-button';
12 | import { SlidingPanelComponent } from './sliding-panel/sliding-panel.component';
13 | import { HttpService } from './services';
14 | import { SortArrowComponent } from './sort-arrow/sort-arrow.component';
15 | import { HelpPanelComponent } from './help-panel/help-panel.component';
16 | import { SwitchComponent } from './switch/switch.component';
17 |
18 | @NgModule({
19 | declarations: [
20 | CheckboxComponent,
21 | CheckboxSlideComponent,
22 | DropDownComponent,
23 | MultiselectDropdownComponent,
24 | MultiSelectSearchFilter,
25 | RadioButtonComponent,
26 | SlidingPanelComponent,
27 | SortArrowComponent,
28 | HelpPanelComponent,
29 | DropdownContentDirective,
30 | DropdownAnchorDirective,
31 | DateRangeComponent,
32 | SwitchComponent,
33 | ],
34 | imports: [
35 | CommonModule,
36 | FormsModule,
37 | ],
38 | exports: [
39 | CheckboxComponent,
40 | CheckboxSlideComponent,
41 | DropDownComponent,
42 | MultiselectDropdownComponent,
43 | RadioButtonComponent,
44 | SlidingPanelComponent,
45 | SortArrowComponent,
46 | HelpPanelComponent,
47 | DropdownContentDirective,
48 | DropdownAnchorDirective,
49 | DateRangeComponent,
50 | SwitchComponent,
51 | ],
52 | providers: [
53 | HttpService,
54 | ],
55 | })
56 | export class AxLibModule {
57 | }
58 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox-slide/checkbox-slide.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox-slide/checkbox-slide.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .ax-checkbox-slide {
4 | position: relative;
5 | display: inline-block;
6 |
7 | input {
8 | position: absolute;
9 | width: 30px;
10 | height: 20px;
11 | z-index: 1;
12 | opacity: 0;
13 | cursor: pointer;
14 |
15 | & + span {
16 | position: relative;
17 | top: 3px;
18 | display: inline-block;
19 | width: 30px;
20 | height: 14px;
21 | margin-right: 6px;
22 | background-color: $ax-color-bg;
23 | border-radius: 7px;
24 | box-shadow: inset 0px 1px 2px rgba(#000, .2);
25 |
26 | &::before {
27 | position: absolute;
28 | top: -2px;
29 | left: -2px;
30 | display: inline-block;
31 | vertical-align: middle;
32 | width: 18px;
33 | height: 18px;
34 | background-color: $ax-color-bg-light;
35 | border-radius: 50%;
36 | box-shadow: 1px 1px 2px #999;
37 | transition: left .2s;
38 | content: '';
39 | }
40 | }
41 |
42 | &:checked + span {
43 | background-color: rgba($ax-color-base-dlight, .3);
44 |
45 | &::before {
46 | left: 16px;
47 | background-color: $ax-color-base-dlight;
48 | }
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox-slide/checkbox-slide.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, forwardRef } from '@angular/core';
2 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | import { AbstractValueAccessor } from '../../abstract-value-accessor/abstract-value-accessor';
5 |
6 | const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = {
7 | provide: NG_VALUE_ACCESSOR,
8 | useExisting: forwardRef(() => CheckboxSlideComponent), // tslint:disable-line
9 | multi: true,
10 | };
11 |
12 | @Component({
13 | selector: 'axc-checkbox-slide',
14 | templateUrl: './checkbox-slide.component.html',
15 | styles: [require('./checkbox-slide.component.scss')],
16 | providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
17 | })
18 | export class CheckboxSlideComponent extends AbstractValueAccessor {
19 | }
20 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox-slide/index.ts:
--------------------------------------------------------------------------------
1 | export * from './checkbox-slide.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox/checkbox.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | L
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox/checkbox.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .ax-checkbox {
4 | position: relative;
5 | display: inline-block;
6 | line-height: 18px;
7 |
8 | input {
9 | position: absolute;
10 | z-index: 1;
11 | width: 18px;
12 | height: 18px;
13 | opacity: 0;
14 | cursor: pointer;
15 |
16 | & + .ax-checkbox__box {
17 | display: inline-block;
18 | vertical-align: middle;
19 | width: 18px;
20 | height: 18px;
21 | text-align: center;
22 | border-radius: 3px;
23 | border: 2px solid #999;
24 | transition: background-color .2s;
25 |
26 | .ax-checkbox__mark {
27 | position: relative;
28 | top: -3px;
29 | transform: scaleX(-1) rotate(-35deg);
30 | display: inline-block;
31 | visibility: hidden;
32 | font-size: 15px;
33 | font-family: 'Arial', sans-serif;
34 | color: $ax-color-bg-light;
35 | }
36 | }
37 |
38 | &:checked + .ax-checkbox__box {
39 | color: $ax-color-bg-light;
40 | background-color: $ax-color-base-dlight;
41 | border-color: transparent;
42 |
43 | .ax-checkbox__mark {
44 | visibility: visible;
45 | }
46 |
47 | }
48 | }
49 |
50 | &:not(:last-child) {
51 | input + span {
52 | margin-right: 6px;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox/checkbox.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, forwardRef, Input } from '@angular/core';
2 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | import { AbstractValueAccessor } from '../../abstract-value-accessor/abstract-value-accessor';
5 |
6 | const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = {
7 | provide: NG_VALUE_ACCESSOR,
8 | useExisting: forwardRef(() => CheckboxComponent), // tslint:disable-line
9 | multi: true,
10 | };
11 |
12 | @Component({
13 | selector: 'axc-checkbox',
14 | templateUrl: './checkbox.component.html',
15 | styles: [require('./checkbox.component.scss')],
16 | providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
17 | })
18 | export class CheckboxComponent extends AbstractValueAccessor {
19 |
20 | @Input()
21 | public disabled: boolean = false;
22 |
23 | @Input()
24 | set value(v: any) {
25 | super.value = v;
26 | }
27 |
28 | get value(): any {
29 | return super.value;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export * from './checkbox.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/date-range/_date-range.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .date-range-selector {
4 | display: block;
5 | width: 100%;
6 | padding: 0 16px;
7 | font-weight: 500;
8 | font-size: .875em;
9 | line-height: 40px;
10 | color: $ax-color-teal-7;
11 | text-transform: uppercase;
12 | background-color: $ax-color-gray-2;
13 | border-radius: 5px;
14 | cursor: pointer;
15 | outline: 0;
16 |
17 | &:hover {
18 | background-color: $ax-color-gray-1;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/date-range/date-range.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter, ElementRef, OnInit } from '@angular/core';
2 | import { DateRange } from './date-range';
3 |
4 | @Component({
5 | selector: 'ax-date-range',
6 | templateUrl: './date-range.html',
7 | styles: [
8 | require('./_date-range.scss'),
9 | ],
10 | })
11 | export class DateRangeComponent implements OnInit {
12 |
13 | @Input()
14 | public range: DateRange;
15 |
16 | @Output()
17 | public rangeChanged: EventEmitter = new EventEmitter();
18 |
19 | private target;
20 |
21 | constructor(private el: ElementRef) {
22 | }
23 |
24 | public ngOnInit() {
25 | let that = this;
26 | this.target = $(this.el.nativeElement).find('.date-range-selector');
27 | this.target.dateRangePicker({
28 | showShortcuts: true, autoClose: true,
29 | shortcuts: {
30 | 'prev-days': [3, 7, 30],
31 | prev: ['week', 'month', 'year'],
32 | 'next-days': null,
33 | next: null,
34 | },
35 | }).bind('datepicker-change', (evt, obj) => {
36 | window.setTimeout(() => {
37 | that.updateRange(obj);
38 | }, 1);
39 | });
40 |
41 | this.updatePicker();
42 | }
43 |
44 | private updatePicker() {
45 | this.target.data('dateRangePicker').setDateRange(this.range.startDate, this.range.endDate, true);
46 | }
47 |
48 | private updateRange(obj) {
49 | this.range.setRange(obj.date1, obj.date2);
50 | this.rangeChanged.next();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/date-range/date-range.html:
--------------------------------------------------------------------------------
1 | {{range.format()}}
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/date-range/date-range.ts:
--------------------------------------------------------------------------------
1 | import * as moment from 'moment';
2 |
3 | export class DateRange {
4 | private _startDate: moment.Moment;
5 | private _endDate: moment.Moment;
6 | private dateFormat: string = 'YYYY-MM-DD';
7 |
8 | constructor(startDate: string, endDate: string) {
9 | if (!startDate || !endDate) {
10 | throw 'Date range needs start and end dates';
11 | }
12 | this.startDate = startDate;
13 | this.endDate = endDate;
14 | }
15 |
16 | get startDate(): string {
17 | return this._startDate.format(this.dateFormat);
18 | }
19 | set startDate(val: string) {
20 | this._startDate = moment(val, this.dateFormat);
21 | }
22 |
23 | get endDate(): string {
24 | return this._endDate.format(this.dateFormat);
25 | }
26 | set endDate(val: string) {
27 | this._endDate = moment(val, this.dateFormat);
28 | }
29 |
30 | public setRange(startDate: Date, endDate: Date) {
31 | this._endDate = moment(this.getDateString(endDate), 'YYYY-MM-DD');
32 | this._startDate = moment(this.getDateString(startDate), 'YYYY-MM-DD');
33 | }
34 |
35 | public format(): string {
36 | let dateFormat = 'D MMM YYYY';
37 | return `${this._startDate.format(dateFormat)} - ${this._endDate.format(dateFormat)}`;
38 | }
39 |
40 | private getDateString(date: Date): string {
41 | return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/date-range/index.ts:
--------------------------------------------------------------------------------
1 | export { DateRangeComponent } from './date-range.component';
2 | export { DateRange } from './date-range';
3 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/dropdown-multiselect/dropdown-multiselect.html:
--------------------------------------------------------------------------------
1 |
2 | {{ title }}
3 |
35 |
36 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/dropdown-multiselect/dropdown-multiselect.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .dropdown-multiselect {
4 | a { outline: none !important; }
5 |
6 | height: 37px;
7 | width: 100%;
8 | max-width: 100%;
9 | padding: 8px 0;
10 | font-size: 16px;
11 | margin-top: 8px;
12 | border: none;
13 | border-bottom: 1px solid #7c818a;
14 | border-radius: 0;
15 | box-shadow: none !important;
16 | background-color: inherit;
17 | cursor: pointer;
18 | -webkit-appearance: none;
19 |
20 | button {
21 | height: 37px;
22 | width: 100%;
23 | text-align: left;
24 | .caret {
25 | position: absolute;
26 | right: 28px;
27 | margin-top: 8px;
28 | width: 0;
29 | height: 0;
30 | border-left: 6px solid transparent;
31 | border-right: 6px solid transparent;
32 | border-top: 6px solid #777;
33 | }
34 | }
35 |
36 | .dropdown-menu {
37 | background-color: $ax-color-bg-light;
38 | border: 2px solid $ax-color-bg-strong;
39 | z-index: $ax-zIndex-dropdown-menu;
40 | margin-left: 0px;
41 | position: absolute;
42 | min-width: 200px;
43 |
44 | li {
45 | margin: 10px;
46 | }
47 | }
48 |
49 | &:focus {
50 | box-shadow: none;
51 | border-color: $ax-color-base;
52 | }
53 |
54 | .ax-btn {
55 | padding: 0 0.5em;
56 | }
57 |
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/dropdown/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown__anchor {
2 | cursor: pointer;
3 | overflow: hidden;
4 | text-overflow: ellipsis;
5 | }
6 |
7 | .dropdown-pane {
8 | box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.2);
9 |
10 | &--menu {
11 | padding: 0;
12 |
13 | & .ax-dropdown-content > ul {
14 | margin: 0;
15 | list-style-type: none;
16 | white-space: nowrap;
17 | text-align: left;
18 | cursor: pointer;
19 | min-width: 150px;
20 |
21 | li {
22 | padding: 0.5em 1em;
23 | border-bottom: 1px solid #cacaca;
24 | color: #3E3E3E;
25 | cursor: pointer;
26 |
27 | &:hover {
28 | background-color: #eee;
29 | }
30 |
31 | &:last-child {
32 | border-bottom: none;
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/help-panel/help-panel.component.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/help-panel/help-panel.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .help-panel {
4 | margin: -20px;
5 |
6 | &__header {
7 | position: relative;
8 | padding: 20px 20px 20px 76px;
9 | color: $ax-color-base-strong;
10 | background-color: $ax-color-base-mlight;
11 | border-bottom: 1px solid $ax-color-base;
12 |
13 | h3 {
14 | margin: 0;
15 | font-weight: bold;
16 | font-size: 18px;
17 | line-height: 36px;
18 | text-transform: uppercase;
19 | }
20 | }
21 |
22 | &__body {
23 | max-width: 780px;
24 | padding: 20px 20px 20px 76px;
25 | }
26 |
27 | &__close {
28 | position: absolute;
29 | left: 20px;
30 | top: 20px;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/help-panel/help-panel.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { SlidingPanelComponent } from '../sliding-panel/sliding-panel.component';
3 |
4 | @Component({
5 | selector: 'axc-help-panel',
6 | templateUrl: './help-panel.component.html',
7 | styles: [require('./help-panel.component.scss')],
8 | })
9 | export class HelpPanelComponent extends SlidingPanelComponent {
10 | }
11 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/help-panel/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/app/common/ax-lib/help-panel/index.ts
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/index.ts:
--------------------------------------------------------------------------------
1 | export { DropDownComponent } from './dropdown/dropdown.component';
2 | export { AxLibModule } from './ax-lib.module';
3 | export { SlidingPanelComponent } from './sliding-panel/sliding-panel.component';
4 | export { SortArrowComponent } from './sort-arrow';
5 | export * from './services';
6 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/radio-button/index.ts:
--------------------------------------------------------------------------------
1 | export * from './radio-button.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/radio-button/radio-button.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/radio-button/radio-button.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .ax-radio {
4 | position: relative;
5 | display: inline-block;
6 |
7 | & + label {
8 | cursor: pointer;
9 | }
10 |
11 | input {
12 | position: absolute;
13 | width: 18px;
14 | height: 18px;
15 | opacity: 0;
16 | cursor: pointer;
17 |
18 | & + span {
19 | display: inline-block;
20 | vertical-align: middle;
21 | width: 18px;
22 | height: 18px;
23 | border-radius: 50%;
24 | border: 2px solid #999;
25 | transition: background-color .2s, border-color .2s;
26 | }
27 |
28 | &:checked + span {
29 | background-color: $ax-color-base-dlight;
30 | border-color: $ax-color-base-dlight;
31 | box-shadow: inset 0 0 0 3px $ax-color-bg;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/radio-button/radio-button.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, forwardRef, Input } from '@angular/core';
2 | import { NG_VALUE_ACCESSOR } from '@angular/forms';
3 |
4 | import { AbstractValueAccessor } from '../../abstract-value-accessor/abstract-value-accessor';
5 |
6 | const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = {
7 | provide: NG_VALUE_ACCESSOR,
8 | useExisting: forwardRef(() => RadioButtonComponent), // tslint:disable-line
9 | multi: true,
10 | };
11 |
12 | @Component({
13 | selector: 'axc-radio-button',
14 | templateUrl: './radio-button.component.html',
15 | styles: [require('./radio-button.component.scss')],
16 | providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR],
17 | })
18 | export class RadioButtonComponent extends AbstractValueAccessor {
19 |
20 | @Input()
21 | public name: string;
22 |
23 | @Input()
24 | public radioValue: string;
25 | }
26 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/services/http.service.ts:
--------------------------------------------------------------------------------
1 | import {Observable, Observer} from 'rxjs';
2 | import {Injectable, NgZone} from '@angular/core';
3 |
4 | declare let untar;
5 |
6 | interface Callback {(data: any): void; }
7 |
8 | declare class EventSource {
9 | /* tslint:disable */
10 | constructor(name: string, options?: any);
11 | public onmessage: Callback;
12 | public onerror: Callback;
13 | public close(): void;
14 | /* tslint:enable */
15 | }
16 |
17 | enum ReadyState {
18 | CONNECTING = 0,
19 | OPEN = 1,
20 | CLOSED = 2,
21 | DONE = 4
22 | }
23 |
24 | /**
25 | * Implements specific low level http requests e.g. reading blob or server sent events.
26 | */
27 | @Injectable()
28 | export class HttpService {
29 | constructor(private zone: NgZone) {}
30 |
31 | /**
32 | * Loads and unpack tarball.
33 | */
34 | public loadTar(url): Observable<{name: string, blob: Blob}> {
35 | return Observable.create((observer: Observer<{name: string, blob: Blob}>) => {
36 | let zone = this.zone;
37 |
38 | let xhr = new XMLHttpRequest();
39 | xhr.onreadystatechange = () => {
40 | if (xhr.readyState === ReadyState.DONE && xhr.status === 200) {
41 | untar(xhr.response).progress(file => {
42 | zone.run(() => observer.next(file));
43 | }).then(() => {
44 | zone.run(() => observer.complete());
45 | }).catch(err => {
46 | zone.run(() => observer.error(err));
47 | });
48 | }
49 | };
50 | xhr.open('GET', url);
51 | xhr.responseType = 'arraybuffer';
52 | xhr.send();
53 |
54 | return () => { xhr.abort(); };
55 | });
56 | }
57 |
58 | /**
59 | * Reads server sent messages from specified URL.
60 | */
61 | public loadEventSource(url): Observable {
62 | return Observable.create((observer: Observer) => {
63 | let eventSource = new EventSource(url, {withCredentials: true});
64 | eventSource.onmessage = msg => observer.next(msg.data);
65 | eventSource.onerror = e => {
66 | if (e.eventPhase === ReadyState.CLOSED) {
67 | observer.complete();
68 | } else {
69 | observer.error(e);
70 | }
71 | }
72 | return () => {
73 | eventSource.close();
74 | };
75 | });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './http.service';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sliding-panel/_sliding-panel.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | $animation-duration: .4s;
4 | $shadow: -4px -4px 6px rgba(0, 0, 0, .2);
5 |
6 | .sliding-panel {
7 | top: 0;
8 | bottom: 0;
9 | position: fixed;
10 | box-shadow: $shadow;
11 | z-index: $ax-zIndex-sliding-panel;
12 | background-color: #FFF;
13 | overflow-y: auto;
14 | padding: 20px;
15 | visibility: hidden;
16 |
17 | &__close-button {
18 | position: absolute;
19 | cursor: pointer;
20 | }
21 |
22 | &--left {
23 | transition: left $animation-duration, visibility $animation-duration;
24 | left: 100%;
25 | right: 0;
26 | }
27 |
28 | &--right {
29 | transition: right $animation-duration, visibility $animation-duration;
30 | left: 0;
31 | right: 100%;
32 | }
33 |
34 | &--opened {
35 | visibility: visible;
36 | }
37 | }
38 |
39 | .sliding-panel--opened.sliding-panel--left {
40 | right: 0;
41 | left: 40%;
42 |
43 | .sliding-panel__close-button {
44 | left: 5px;
45 | top: 3px;
46 | }
47 | }
48 |
49 | .sliding-panel--opened.sliding-panel--right {
50 | right: 20%;
51 | left: 0;
52 |
53 | .sliding-panel__close-button {
54 | right: 5px;
55 | top: 3px;
56 | }
57 | }
58 |
59 | .popup-overlay {
60 | position: fixed;
61 | z-index: 2;
62 | top: 0;
63 | left: 0;
64 | width: 100%;
65 | height: 100%;
66 | background-color: $ax-color-gray-3;
67 | transition: opacity #{$animation-duration}, visibility #{$animation-duration};
68 |
69 | &:not(.opened) {
70 | opacity: 0;
71 | visibility: hidden;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sliding-panel/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sliding-panel.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sliding-panel/sliding-panel.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sliding-panel/sliding-panel.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, Output } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'ax-sliding-panel',
5 | templateUrl: './sliding-panel.component.html',
6 | styles: [require('./_sliding-panel.component.scss')],
7 | })
8 | export class SlidingPanelComponent {
9 | @Input()
10 | public show: boolean = false;
11 | @Input()
12 | public position: 'left' | 'right' = 'left';
13 | @Input()
14 | public hasCloseButton: boolean = true;
15 | @Output()
16 | public closeButtonClick: EventEmitter = new EventEmitter();
17 |
18 | public onCloseButtonClick() {
19 | this.closeButtonClick.emit(this);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sort-arrow/index.ts:
--------------------------------------------------------------------------------
1 | export * from './sort-arrow.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sort-arrow/sort-arrow.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sort-arrow/sort-arrow.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/app/common/ax-lib/sort-arrow/sort-arrow.component.scss
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/sort-arrow/sort-arrow.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-sort-arrow',
5 | templateUrl: './sort-arrow.component.html',
6 | styles: [require('./sort-arrow.component.scss')],
7 | })
8 | export class SortArrowComponent {
9 | @Input()
10 | private columnName: string;
11 |
12 | @Input()
13 | private sort: string;
14 |
15 | @Input()
16 | private sortBy: string;
17 | }
18 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/switch/switch.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-switch',
5 | templateUrl: './switch.html',
6 | styles: [require('./switch.scss')],
7 | })
8 | export class SwitchComponent {
9 |
10 | @Input()
11 | public options = [];
12 | @Output()
13 | public onOptionChanged: EventEmitter = new EventEmitter();
14 | @Input()
15 | public selectedValue: string;
16 |
17 | protected selectOption(value: string) {
18 | let changed = value !== this.selectedValue;
19 | this.selectedValue = value;
20 | if (changed) {
21 | this.onOptionChanged.emit(value);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/switch/switch.html:
--------------------------------------------------------------------------------
1 |
2 |
4 | {{option.title}}
5 |
6 |
--------------------------------------------------------------------------------
/ui/src/app/common/ax-lib/switch/switch.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .ax-switch {
4 | display: inline-block;
5 | padding: 2px;
6 | background-color: $ax-color-teal-6;
7 | border-radius: 6px;
8 |
9 | &__button {
10 | display: inline-block;
11 | padding: 0 24px;
12 | font-weight: 500;
13 | font-size: .875em;
14 | line-height: 36px;
15 | color: $ax-color-gray-1;
16 | text-transform: uppercase;
17 | border-radius: 5px;
18 | outline: 0;
19 |
20 | &.active {
21 | color: $ax-color-gray-7;
22 | background-color: $ax-color-gray-1;
23 | }
24 |
25 | &:not(.active) {
26 | cursor: pointer;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ui/src/app/common/collapse-box/collapse-box.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ui/src/app/common/collapse-box/collapse-box.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .collapse-box {
4 | overflow: hidden;
5 |
6 | &__button {
7 | position: relative;
8 | top: 25px;
9 | right: 20px;
10 | float: right;
11 | font-size: 15px;
12 | }
13 |
14 | &--collapsed {
15 | height: 100px;
16 | }
17 | &--expanded {
18 | height: auto;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/common/collapse-box/collapse-box.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-collapse-box',
5 | templateUrl: './collapse-box.component.html',
6 | styles: [require('./collapse-box.component.scss')],
7 | })
8 | export class CollapseBoxComponent {
9 | @Input() public collapsed: boolean = false;
10 |
11 | public toggle() {
12 | if (this.collapsed) {
13 | this.show();
14 | } else {
15 | this.hide();
16 | }
17 | }
18 |
19 | private hide() {
20 | this.collapsed = true;
21 | }
22 |
23 | private show() {
24 | this.collapsed = false;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ui/src/app/common/collapse-box/collapse-box.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { RouterModule } from '@angular/router';
4 |
5 | import { CollapseBoxComponent } from './collapse-box.component';
6 |
7 | @NgModule({
8 | declarations: [
9 | CollapseBoxComponent,
10 | ],
11 | imports: [
12 | RouterModule,
13 | CommonModule,
14 | ],
15 | exports: [
16 | CollapseBoxComponent,
17 | ],
18 | })
19 | export class CollapseBoxModule {
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/common/collapse-box/index.ts:
--------------------------------------------------------------------------------
1 | export * from './collapse-box.module';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/http-interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '@angular/http';
3 | import { Observable } from 'rxjs/Rx';
4 | import { ERROR_CODES, AppError } from '../common/shared';
5 |
6 | @Injectable()
7 | export class HttpInterceptor extends Http {
8 | protected basePath = `${process.env.API_PROTOCOL}${process.env.API_URI}`;
9 | constructor(
10 | private backend: XHRBackend,
11 | private defaultOptions: RequestOptions) {
12 | super(backend, defaultOptions);
13 | }
14 |
15 | public request(url: string | Request, options?: RequestOptionsArgs): Observable {
16 | if (url instanceof Request) {
17 | url.url = this.basePath + url.url;
18 | } else {
19 | url = this.basePath + url;
20 | }
21 |
22 | options = options || {};
23 | options.withCredentials = true;
24 | let headers = new Headers();
25 | headers.set('Content-Type', 'application/json');
26 | options.headers = headers;
27 | return super.request(url, options).catch((res: Response) => {
28 | try {
29 | // Try to parse REST API error
30 | return Observable.throw(res.json());
31 | } catch (e) {
32 | // Fallback to HTTP response code if API response does not have properly formatter error
33 | let code = '';
34 | switch (res.status) {
35 | case 400:
36 | code = ERROR_CODES.INVALID_REQUEST;
37 | break;
38 | case 401:
39 | code = ERROR_CODES.UNAUTHENTICATED_REQUEST;
40 | break;
41 | case 403:
42 | code = ERROR_CODES.UNAUTHORIZED_REQUEST;
43 | break;
44 | case 404:
45 | code = ERROR_CODES.ENTITY_NOT_FOUND;
46 | break;
47 | default:
48 | code = ERROR_CODES.INTERNAL_ERROR;
49 | }
50 | return Observable.throw( { code, message: res.text() });
51 | }
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ui/src/app/common/index.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Http, XHRBackend, RequestOptions } from '@angular/http';
3 | import { AxLibModule } from './ax-lib';
4 |
5 | import { HttpInterceptor } from './http-interceptor';
6 | import { PasswordMatcherDirective } from './password-macher';
7 |
8 | export * from './ax-lib';
9 |
10 | @NgModule({
11 | providers: [{
12 | provide: Http,
13 | useFactory: (backend: XHRBackend, options: RequestOptions) => new HttpInterceptor(backend, options),
14 | deps: [XHRBackend, RequestOptions],
15 | }, {
16 | provide: '',
17 | useValue: `${process.env.API_PROTOCOL}${process.env.API_URI}`,
18 | }],
19 | imports: [
20 | AxLibModule,
21 | ],
22 | declarations: [
23 | PasswordMatcherDirective,
24 | ],
25 | exports: [
26 | PasswordMatcherDirective,
27 | ],
28 | })
29 | export class AxcCommonModule {
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/app/common/msg-box/index.ts:
--------------------------------------------------------------------------------
1 | export * from './msg-box.module';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/msg-box/msg-box.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/src/app/common/msg-box/msg-box.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .msg-box {
4 | max-width: 460px;
5 | margin: 80px auto;
6 | padding: 50px 50px 80px;
7 | text-align: center;
8 | background-color: $ax-color-bg-light;
9 | box-shadow: 3px 3px 4px rgba(#000, .07);
10 | border-radius: 3px;
11 | }
12 |
--------------------------------------------------------------------------------
/ui/src/app/common/msg-box/msg-box.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-msg-box',
5 | templateUrl: './msg-box.component.html',
6 | styles: [require('./msg-box.component.scss')],
7 | })
8 | export class MsgBoxComponent {
9 | }
10 |
--------------------------------------------------------------------------------
/ui/src/app/common/msg-box/msg-box.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | import { MsgBoxComponent } from './msg-box.component';
5 |
6 | @NgModule({
7 | declarations: [
8 | MsgBoxComponent,
9 | ],
10 | imports: [
11 | RouterModule,
12 | ],
13 | exports: [
14 | MsgBoxComponent,
15 | ],
16 | })
17 | export class MsgBoxModule {
18 | }
19 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/index.ts:
--------------------------------------------------------------------------------
1 | export * from './notifications.service';
2 | export * from './notifications.module';
3 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notification.model.ts:
--------------------------------------------------------------------------------
1 | export enum NotificationTypes {
2 | Success,
3 | Warning,
4 | Error,
5 | }
6 |
7 | export class Notification {
8 | public content: string;
9 | public type: NotificationTypes;
10 | }
11 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notifications.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
9 |
11 | {{ notification.content }}
12 |
x
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notifications.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .ax-notifications-list {
4 | position: fixed;
5 | top: 0;
6 | left: 0;
7 | right: 0;
8 | z-index: $ax-zIndex-notification;
9 | box-shadow: 2px 0 6px rgba(#000, .3);
10 | }
11 |
12 | .ax-notification {
13 | position: absolute;
14 | top: 0;
15 | right: 0;
16 | left: 0;
17 | height: $ax-topbar-height;
18 | padding: 0 60px 0 30px;
19 | line-height: $ax-topbar-height;
20 | font-size: 20px;
21 | color: #fff;
22 |
23 | &.success {
24 | background-color: $ax-color-success;
25 | }
26 |
27 | &.warning {
28 | background-color: $ax-color-warning;
29 | }
30 |
31 | &.error {
32 | background-color: $ax-color-failed;
33 | }
34 |
35 | &__ico {
36 | vertical-align: middle;
37 | margin-right: 10px;
38 | border-radius: 50%;
39 | border: 1px solid rgba(#fff, .4);
40 | }
41 |
42 | &__close {
43 | position: absolute;
44 | top: 50%;
45 | right: 30px;
46 | transform: translateY(-50%);
47 | cursor: pointer;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notifications.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NotificationsService } from '.';
3 | import { NotificationTypes, Notification } from './notification.model';
4 |
5 | @Component({
6 | selector: 'axc-notifications',
7 | templateUrl: './notifications.component.html',
8 | styles: [require('./notifications.component.scss')],
9 | })
10 | export class NotificationsComponent {
11 |
12 | public notifications: Notification[];
13 | public notificationTypes = NotificationTypes;
14 |
15 | constructor(private notificationsService: NotificationsService) {
16 | this.notifications = notificationsService.notifications;
17 | }
18 |
19 | public close(index: number) {
20 | this.notificationsService.close(index);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notifications.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { NotificationsComponent } from './notifications.component';
5 | import { NotificationsService } from './notifications.service';
6 |
7 | @NgModule({
8 | declarations: [
9 | NotificationsComponent,
10 | ],
11 | imports: [
12 | CommonModule,
13 | ],
14 | exports: [
15 | NotificationsComponent,
16 | ],
17 | providers: [
18 | NotificationsService,
19 | ],
20 | })
21 | export class NotificationsModule {
22 | }
23 |
--------------------------------------------------------------------------------
/ui/src/app/common/notifications/notifications.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | import { NotificationTypes, Notification } from './notification.model';
4 |
5 | @Injectable()
6 | export class NotificationsService {
7 |
8 | public notifications: Notification[] = [];
9 |
10 | public success(content: string) {
11 | this.createNotification(NotificationTypes.Success, content);
12 | }
13 |
14 | public warning(content: string) {
15 | this.createNotification(NotificationTypes.Warning, content);
16 | }
17 |
18 | public error(content: string) {
19 | this.createNotification(NotificationTypes.Error, content);
20 | }
21 |
22 | public close(index: number) {
23 | delete this.notifications[index];
24 | }
25 |
26 | private createNotification(type: NotificationTypes, content: string) {
27 | let newNotificationIndex = this.notifications.push({ content, type }) - 1;
28 |
29 | // Autohide
30 | setTimeout(() => {
31 | delete this.notifications[newNotificationIndex];
32 | }, 5000);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ui/src/app/common/password-macher.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from '@angular/core';
2 | import { AbstractControl, NG_VALIDATORS } from '@angular/forms';
3 |
4 | function passwordMatcher(c: AbstractControl) {
5 | if (!c.get('password') || !c.get('confirm')) {
6 | return null;
7 | }
8 | return c.get('password').value === c.get('confirm').value
9 | ? null : {noMatch: true};
10 |
11 | }
12 |
13 | @Directive({
14 | selector: '[axc-password-matcher]',
15 | providers: [
16 | {provide: NG_VALIDATORS, multi: true, useValue: passwordMatcher},
17 | ],
18 | })
19 | export class PasswordMatcherDirective {
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/ui/src/app/common/shared/constants.ts:
--------------------------------------------------------------------------------
1 | export const FIELD_PATTERNS = {
2 | CIDR: '(^$|\\d{1,3}(\\.\\d{1,3}){3}\\/\\d{1,2}$)',
3 | ENV_NAME: '^[A-Za-z0-9]([-A-Za-z0-9_]*)?[A-Za-z0-9]$',
4 | // Password will match only: 8+ letters, at least 1 lower case letter,
5 | // at least 1 upper case letter, and at least 1 special character
6 | PASSWORD: '^(?=.*?[A-Za-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$',
7 | // Email regex
8 | EMAIL: '^[a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,15})$',
9 | };
10 |
11 | export const ERROR_CODES = {
12 | INTERNAL_ERROR: 'INTERNAL_ERROR',
13 | INVALID_REQUEST: 'INVALID_REQUEST',
14 | UNAUTHENTICATED_REQUEST: 'UNAUTHENTICATED_REQUEST',
15 | UNAUTHORIZED_REQUEST: 'UNAUTHORIZED_REQUEST',
16 | ENTITY_NOT_FOUND: 'ENTITY_NOT_FOUND',
17 | };
18 |
19 | export interface AppError {
20 | code?: string;
21 |
22 | message?: string;
23 | }
24 |
--------------------------------------------------------------------------------
/ui/src/app/common/shared/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants';
2 |
--------------------------------------------------------------------------------
/ui/src/app/common/validate-onblur.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostListener } from '@angular/core';
2 | import { NgControl } from '@angular/forms';
3 |
4 | @Directive({
5 | selector: '[axc-validate-onblur]',
6 | })
7 | export class ValidateOnBlurDirective {
8 | constructor(public formControl: NgControl) {
9 | }
10 |
11 | @HostListener('focus')
12 | public onFocus() {
13 | this.formControl.control.markAsUntouched(false);
14 | }
15 |
16 | @HostListener('blur')
17 | public onBlur() {
18 | this.formControl.control.markAsTouched(true);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/config/aws-config.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Create a report to get started
5 |
6 |
7 |
Report Settings
8 | You should have at least one bucket configured to see data
9 |
10 |
11 |
12 | Report Name
13 |
14 |
15 |
25 |
26 | Create
27 | Update
28 | +Add Bucket
29 |
30 |
31 |
32 |
33 |
34 |
Manage Buckets
35 |
Enter one or more S3 buckets containing your
36 | AWS Cost & Usage Reports , along with access credentials to the bucket.
38 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ui/src/app/config/aws-config.component.scss:
--------------------------------------------------------------------------------
1 | .aws-config {
2 | .ax-form-row {
3 | padding: 0em 0.5em;
4 | }
5 | .action-item {
6 | margin:1em 0;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/ui/src/app/config/aws-config.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 |
4 | import { ConfigService } from '../services';
5 | import { NotificationsService } from '../common/notifications/notifications.service';
6 | import { FIELD_PATTERNS } from '../common/shared';
7 | import { Bucket, Report } from '../model';
8 |
9 | const DEFAULT_LOGIN_ERROR = 'Unable to login. Please try again later.';
10 |
11 | @Component({
12 | selector: 'axc-aws-config',
13 | templateUrl: './aws-config.component.html',
14 | styles: [
15 | require('./aws-config.component.scss'),
16 | ],
17 | })
18 | export class AWSConfigComponent implements OnInit {
19 |
20 | public config: Report;
21 | public buckets: Bucket[];
22 | public report_name: string = 'default';
23 | public retention_days: number = 365;
24 |
25 | constructor(
26 | private router: Router,
27 | private activatedRoute: ActivatedRoute,
28 | private notificationsService: NotificationsService,
29 | private configService: ConfigService) {
30 | }
31 |
32 | public async ngOnInit() {
33 | let data: Report = await this.configService.getReportConfig();
34 | if (data) {
35 | this.updateConfig(data);
36 | }
37 | }
38 |
39 | public updateConfig(data: Report) {
40 | this.config = data;
41 | this.report_name = this.config.report_name;
42 | this.retention_days = this.config.retention_days;
43 | this.buckets = this.config.buckets;
44 | if (this.buckets.length === 0) {
45 | this.addBucket();
46 | }
47 | }
48 |
49 | public async saveReportObj() {
50 | try {
51 | let report: Report;
52 | if (!this.config) {
53 | report = await this.configService.createReportConfig({
54 | report_name: this.report_name,
55 | retention_days: this.retention_days,
56 | });
57 | } else {
58 | report = await this.configService.updateReportConfig(this.config.id, {
59 | report_name: this.report_name,
60 | retention_days: this.retention_days,
61 | });
62 | }
63 |
64 | this.updateConfig(report);
65 | } catch (e) {
66 | this.notificationsService.error(e.message);
67 | }
68 | }
69 |
70 | public onBucketDelete(data) {
71 | this.buckets.splice(data.identifier, 1);
72 | if (this.buckets.length === 0) {
73 | this.addBucket();
74 | }
75 | }
76 |
77 | public onBucketSave(data: { bucket: Bucket, identifier: any }) {
78 | this.buckets[data.identifier] = data.bucket;
79 | }
80 | public addBucket() {
81 | this.buckets.unshift(new Bucket());
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/ui/src/app/config/bucket.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
49 |
50 |
--------------------------------------------------------------------------------
/ui/src/app/config/bucket.component.scss:
--------------------------------------------------------------------------------
1 | .bucket-config {
2 | margin:1em 0;
3 | .ax-form-row {
4 | padding: 0em 0.5em;
5 | }
6 | .action-item {
7 | margin:1em 0;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ui/src/app/config/config.component.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/ui/src/app/config/config.component.scss:
--------------------------------------------------------------------------------
1 | @import './../../assets/css/config';
2 |
3 | .aws-config {
4 | width:80%;
5 | margin:0px auto;
6 | }
7 |
--------------------------------------------------------------------------------
/ui/src/app/config/config.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-config',
5 | templateUrl: './config.component.html',
6 | styles: [
7 | require('./config.component.scss'),
8 | ],
9 | })
10 | export class ConfigComponent {
11 | }
12 |
--------------------------------------------------------------------------------
/ui/src/app/config/index.ts:
--------------------------------------------------------------------------------
1 | export * from './config.component';
2 | export * from './aws-config.component';
3 | export * from './bucket.component';
4 |
--------------------------------------------------------------------------------
/ui/src/app/config/keys-config/keys-config.component.html:
--------------------------------------------------------------------------------
1 | Manage Private Key & Public Certificates
2 |
3 |
18 |
--------------------------------------------------------------------------------
/ui/src/app/config/keys-config/keys-config.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/app/config/keys-config/keys-config.component.scss
--------------------------------------------------------------------------------
/ui/src/app/config/keys-config/keys-config.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ConfigService } from '../../services/config.service';
3 | import { Config } from '../../model/config.model';
4 | import { NotificationsService } from '../../common/notifications/notifications.service';
5 |
6 | @Component({
7 | selector: 'axc-keys-config',
8 | templateUrl: './keys-config.component.html',
9 | styles: [
10 | require('./keys-config.component.scss'),
11 | ],
12 | })
13 | export class KeysConfigComponent implements OnInit {
14 |
15 | public config: Config = new Config();
16 |
17 | constructor(private configService: ConfigService, private notificationsService: NotificationsService) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.configService.getConfig().then((data: Config) => this.config = data);
22 | }
23 |
24 | public update() {
25 | this.configService.updateConfig(this.config).then((data: Config) => {
26 | this.config = data;
27 | this.notificationsService.success('Config was updated.');
28 | }, () => {
29 | this.notificationsService.error('Something went wrong.');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ui/src/app/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 | Dashboard Foo
2 |
--------------------------------------------------------------------------------
/ui/src/app/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-dashboard',
5 | templateUrl: './dashboard.component.html',
6 | })
7 | export class DashboardComponent {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/ui/src/app/dashboard/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dashboard.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/environment.ts:
--------------------------------------------------------------------------------
1 | import { enableDebugTools, disableDebugTools } from '@angular/platform-browser';
2 | import { enableProdMode, ApplicationRef } from '@angular/core';
3 |
4 | let PROVIDERS: any[] = [
5 | // common env directives
6 | ];
7 |
8 | // Angular debug tools in the dev console
9 | // https://github.com/angular/angular/blob/86405345b781a9dc2438c0fbe3e9409245647019/TOOLS_JS.md
10 |
11 | /* tslint:disable */
12 | let decorateModuleRefInternal = function identity(value: T): T { return value; };
13 | /* tslint:enable */
14 |
15 | if ('production' === ENV) {
16 | // Production
17 | disableDebugTools();
18 | enableProdMode();
19 |
20 | PROVIDERS = [
21 | ...PROVIDERS,
22 | ];
23 |
24 | } else {
25 | decorateModuleRefInternal = (modRef: any) => {
26 | const appRef = modRef.injector.get(ApplicationRef);
27 | const cmpRef = appRef.components[0];
28 |
29 | let ng = ( window).ng;
30 | enableDebugTools(cmpRef);
31 | ( window).ng.probe = ng.probe;
32 | ( window).ng.coreTokens = ng.coreTokens;
33 | return modRef;
34 | };
35 |
36 | // Development
37 | PROVIDERS = [
38 | ...PROVIDERS,
39 | ];
40 |
41 | }
42 |
43 | export const decorateModuleRef = decorateModuleRefInternal;
44 |
45 | export const ENV_PROVIDERS = [
46 | ...PROVIDERS,
47 | ];
48 |
--------------------------------------------------------------------------------
/ui/src/app/error-page/error-page.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'axc-error-page',
6 | templateUrl: './error-page.html',
7 | })
8 | export class ErrorPageComponent implements OnInit {
9 | private msg: string = '';
10 | private errorCode: string = '';
11 |
12 | private errorType: string = 'System Error';
13 | constructor(
14 | private activatedRoute: ActivatedRoute) {
15 | }
16 |
17 | public ngOnInit() {
18 | this.activatedRoute.params.subscribe((params) => {
19 | if (params && params['code']) {
20 | this.errorCode = params['code'];
21 | }
22 | if (params && params['msg']) {
23 | this.msg = decodeURIComponent(params['msg']);
24 | }
25 | if (params && params['type']) {
26 | this.errorType = params['type'];
27 | }
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/app/error-page/error-page.html:
--------------------------------------------------------------------------------
1 |
2 |
{{errorCode}}
3 | Error Type - {{errorType}}
4 | {{msg}}
5 |
6 |
--------------------------------------------------------------------------------
/ui/src/app/error-page/index.ts:
--------------------------------------------------------------------------------
1 | export * from './error-page.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/eula/eula.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ALL PRODUCTS ARE PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND FROM ANYONE, INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT. FURTHER, APPLATIX DOES NOT WARRANT RESULTS OF USE OR THAT THE PRODUCTS ARE BUG FREE OR THAT THE PRODUCT’S USE WILL BE UNINTERRUPTED.
4 |
5 | Accept
6 | Deny
7 | You have already accepted the terms.
8 | Ok
9 |
10 |
--------------------------------------------------------------------------------
/ui/src/app/eula/eula.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 |
4 | import { Config } from '../model/config.model';
5 | import { ConfigService, AuthenticationService } from '../services';
6 | import { NotificationsService } from '../common/notifications/notifications.service';
7 |
8 | @Component({
9 | selector: 'axc-eula',
10 | templateUrl: './eula.component.html',
11 | styles: [
12 | require('./eula.scss'),
13 | ],
14 | })
15 | export class EulaComponent implements OnInit {
16 | public config: Config;
17 | public fwdUrl: string = '/app/report';
18 |
19 | constructor(
20 | private router: Router,
21 | private activatedRoute: ActivatedRoute,
22 | private notificationsService: NotificationsService,
23 | private configService: ConfigService,
24 | private authenticationService: AuthenticationService) {
25 | }
26 |
27 | public async ngOnInit() {
28 | this.config = await this.configService.getConfig();
29 | this.activatedRoute.params.subscribe((params) => {
30 | this.fwdUrl = params['fwd'] ? params['fwd'] : this.fwdUrl;
31 | });
32 | }
33 |
34 | public async onAccept() {
35 | let c: Config = new Config();
36 | c.eula_accepted = true;
37 | await this.configService.updateConfig(c);
38 | this.router.navigateByUrl(this.fwdUrl);
39 | }
40 |
41 | public async onDeny() {
42 | await this.authenticationService.doLogout();
43 | this.router.navigateByUrl('base/login');
44 | }
45 |
46 | public onAlreadyAccepted() {
47 | this.router.navigateByUrl(this.fwdUrl);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/ui/src/app/eula/eula.scss:
--------------------------------------------------------------------------------
1 |
2 | .eula {
3 | p {
4 | font-size: 1em;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ui/src/app/eula/index.ts:
--------------------------------------------------------------------------------
1 | export * from './eula.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app.module';
2 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/base-layout.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/base-layout.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .ax-layout {
4 | padding: #{$ax-topbar-height} 0 0 0;
5 |
6 | &__main {
7 | padding: 20px;
8 | margin: 0px auto;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/base-layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild, OnInit } from '@angular/core';
2 | import { Router, RouterOutlet, ActivatedRouteSnapshot, NavigationEnd } from '@angular/router';
3 |
4 | export interface LayoutSettings {
5 |
6 | }
7 |
8 | export interface HasLayoutSettings {
9 | layoutSettings: LayoutSettings;
10 | }
11 |
12 | @Component({
13 | selector: 'axc-base-layout',
14 | templateUrl: './base-layout.component.html',
15 | styles: [
16 | require('./base-layout.component.scss'),
17 | ],
18 | })
19 | export class BaseLayoutComponent implements OnInit {
20 |
21 | public title: string;
22 | public layoutSettings: LayoutSettings;
23 | @ViewChild(RouterOutlet)
24 | public routerOutlet: RouterOutlet;
25 |
26 | constructor(private router: Router) { }
27 |
28 | public ngOnInit() {
29 | this.router.events.subscribe((event) => {
30 | if (event instanceof NavigationEnd) {
31 | this.title = this.getDeepestTitle(this.router.routerState.snapshot.root);
32 | if (this.routerOutlet.isActivated) {
33 | let component: any = this.routerOutlet.component;
34 | this.layoutSettings = component ? component.layoutSettings || {} : {};
35 | } else {
36 | this.layoutSettings = null;
37 | }
38 | }
39 | });
40 | }
41 |
42 | private getDeepestTitle(routeSnapshot: ActivatedRouteSnapshot) {
43 | let title = routeSnapshot.data ? routeSnapshot.data['title'] : '';
44 |
45 | if (routeSnapshot.firstChild) {
46 | title = this.getDeepestTitle(routeSnapshot.firstChild) || title;
47 | }
48 |
49 | return title;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/base-layout.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 | import { CommonModule } from '@angular/common';
4 |
5 | import { AxLibModule, AxcCommonModule } from '../../common';
6 | import { BaseLayoutComponent } from '.';
7 | import { TopbarComponent } from './topbar';
8 | import { NotificationsModule } from '../../common/notifications';
9 |
10 | @NgModule({
11 | declarations: [
12 | BaseLayoutComponent,
13 | TopbarComponent,
14 | ],
15 | imports: [
16 | CommonModule,
17 | RouterModule,
18 | NotificationsModule,
19 | AxLibModule,
20 | AxcCommonModule,
21 | ],
22 | exports: [
23 | TopbarComponent,
24 | AxcCommonModule,
25 | ],
26 | })
27 | export class BaseLayoutModule {}
28 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base-layout.component';
2 | export * from './base-layout.module';
3 | export * from './topbar';
4 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/topbar/index.ts:
--------------------------------------------------------------------------------
1 | export * from './topbar.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/topbar/topbar.component.html:
--------------------------------------------------------------------------------
1 |
23 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/topbar/topbar.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../../assets/css/config';
2 |
3 | .ax-topbar {
4 | position: fixed;
5 | top: 0;
6 | z-index: $ax-zIndex-topbar;
7 | width: 100%;
8 | height: $ax-topbar-height;
9 | line-height: $ax-topbar-height;
10 | background-color: $ax-color-base-dark;
11 |
12 | &__logo {
13 | position: relative;
14 | color: #fff;
15 | font-size: 1.5em;
16 |
17 | &::after {
18 | position: absolute;
19 | top: 10px;
20 | right: 0;
21 | bottom: 10px;
22 | width: 1px;
23 | background-color: rgba($ax-color-gray-5, .5);
24 | content: '';
25 | }
26 | }
27 |
28 | &__menu {
29 | font-size: 1.2em;
30 | padding-right: 10px;
31 |
32 | a {
33 | color: $ax-color-gray-2;
34 | }
35 | }
36 |
37 | &__menu-right {
38 | font-size: .8em;
39 |
40 | a {
41 | position: relative;
42 | color: $ax-color-gray-2;
43 |
44 | &:not(:last-child) {
45 | margin-right: 20px;
46 |
47 | // &::after {
48 | // position: absolute;
49 | // top: -2px;
50 | // right: -10px;
51 | // bottom: -2px;
52 | // width: 1px;
53 | // background-color: rgba($ax-color-gray-5, .5);
54 | // content: '';
55 | // }
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ui/src/app/layout/base/topbar/topbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 |
4 | import * as layout from '../../../layout';
5 | import { AuthenticationService, UserService } from '../../../services';
6 |
7 | @Component({
8 | selector: 'axc-topbar',
9 | templateUrl: './topbar.component.html',
10 | styles: [
11 | require('./topbar.component.scss'),
12 | ],
13 | })
14 | export class TopbarComponent {
15 |
16 | @Input()
17 | public settings: layout.LayoutSettings;
18 |
19 | @Input()
20 | public title: string;
21 |
22 | constructor(
23 | private router: Router,
24 | private activatedRoute: ActivatedRoute,
25 | private authenticationService: AuthenticationService,
26 | private userService: UserService) {
27 | }
28 |
29 | public async doLogout() {
30 | await this.authenticationService.doLogout();
31 | }
32 | public goToReport() {
33 | window.location.href = 'app/report';
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ui/src/app/layout/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base';
2 | export * from './intro';
3 |
--------------------------------------------------------------------------------
/ui/src/app/layout/intro/index.ts:
--------------------------------------------------------------------------------
1 | export * from './intro-layout.component';
2 | export * from './intro-layout.module';
3 |
--------------------------------------------------------------------------------
/ui/src/app/layout/intro/intro-layout.component.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/ui/src/app/layout/intro/intro-layout.component.scss:
--------------------------------------------------------------------------------
1 | @import './../../../assets/css/config';
2 |
3 | .ax-layout {
4 | height: calc(100%);
5 | padding-top: $ax-topbar-height;
6 |
7 | &__main {
8 | max-width: 500px;
9 | margin: 100px auto;
10 | padding: 40px;
11 | background-color: $ax-color-bg-light;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/app/layout/intro/intro-layout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-intro-layout',
5 | templateUrl: './intro-layout.component.html',
6 | styles: [
7 | require('./intro-layout.component.scss'),
8 | ],
9 | })
10 | export class IntroLayoutComponent {
11 | }
12 |
--------------------------------------------------------------------------------
/ui/src/app/layout/intro/intro-layout.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | import { IntroLayoutComponent } from '.';
5 | import { NotificationsModule } from '../../common/notifications';
6 |
7 | @NgModule({
8 | declarations: [
9 | IntroLayoutComponent,
10 | ],
11 | imports: [
12 | RouterModule,
13 | NotificationsModule,
14 | ],
15 | })
16 | export class IntroLayoutModule {}
17 |
--------------------------------------------------------------------------------
/ui/src/app/login/index.ts:
--------------------------------------------------------------------------------
1 | export * from './login.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/login/login.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
24 |
25 | Sign in
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/ui/src/app/login/login.component.scss:
--------------------------------------------------------------------------------
1 | @import './../../assets/css/config';
2 |
3 | .login {
4 | &__action-link {
5 | text-transform: uppercase;
6 | font-size: 0.813em;
7 | }
8 | .logo {
9 | text-align: center;
10 | margin-bottom: 25px;
11 |
12 | img {
13 | height: 40px;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/src/app/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router, ActivatedRoute } from '@angular/router';
3 |
4 | import { AuthenticationService, UserService } from '../services';
5 | import { NotificationsService } from '../common/notifications/notifications.service';
6 | import { FIELD_PATTERNS } from '../common/shared';
7 |
8 | const DEFAULT_LOGIN_ERROR = 'Unable to login. Please try again later.';
9 |
10 | @Component({
11 | selector: 'axc-login',
12 | templateUrl: './login.component.html',
13 | styles: [
14 | require('./login.component.scss'),
15 | ],
16 | })
17 | export class LoginComponent implements OnInit {
18 | public username: string;
19 | public password: string;
20 | public newPassword: string;
21 | public confirmPassword: string;
22 | public token: string;
23 | public fwdUrl: string = '/app/report';
24 |
25 | public passwordRegex: string = FIELD_PATTERNS.PASSWORD;
26 |
27 | constructor(
28 | private router: Router,
29 | private activatedRoute: ActivatedRoute,
30 | private authenticationService: AuthenticationService,
31 | private notificationsService: NotificationsService,
32 | private userService: UserService) {
33 | }
34 |
35 | public ngOnInit() {
36 | // Read the url for any forward urls
37 | this.activatedRoute.params.subscribe((params) => {
38 | this.fwdUrl = params['fwd'] ? params['fwd'] : this.fwdUrl;
39 | });
40 | }
41 |
42 | public async doLogin() {
43 | try {
44 | let success: any = await this.authenticationService.doLogin(this.username, this.password);
45 | if (success.config && success.config.eula_accepted) {
46 | this.router.navigateByUrl(this.fwdUrl);
47 | } else {
48 | this.router.navigate(['/base/eula', {fwdUrl: this.fwdUrl}]);
49 | }
50 |
51 | } catch (err) {
52 | this.notificationsService.error(err.message || DEFAULT_LOGIN_ERROR);
53 | }
54 | }
55 |
56 | public get loginUrl(): string {
57 | let url = '/base/login';
58 | if (this.fwdUrl && this.fwdUrl !== '') {
59 | url += '/' + encodeURIComponent(this.fwdUrl);
60 | }
61 | return url;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/ui/src/app/model/account.model.ts:
--------------------------------------------------------------------------------
1 | export interface Account {
2 | aws_account_id: string;
3 | name: string;
4 | }
5 |
--------------------------------------------------------------------------------
/ui/src/app/model/bucket.model.ts:
--------------------------------------------------------------------------------
1 | export class Bucket {
2 | public id?: string;
3 | public bucketname: string;
4 | public report_path: string;
5 | public aws_access_key_id: string;
6 | public aws_secret_access_key: string;
7 | }
8 |
--------------------------------------------------------------------------------
/ui/src/app/model/config.model.ts:
--------------------------------------------------------------------------------
1 | export class Config {
2 | public private_key: string;
3 | public public_certificate: string;
4 | public eula_accepted: boolean;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/src/app/model/graph.model.ts:
--------------------------------------------------------------------------------
1 | export class Graph {
2 | public columns: string[] = [];
3 | public tags: Map = new Map();
4 | public name: string = '';
5 | public values: Array = Array();
6 | constructor(data) {
7 | if (typeof data === 'object') {
8 | for (let key in data) {
9 | if (data.hasOwnProperty(key)) {
10 |
11 | this[key] = data[key];
12 | }
13 | }
14 | }
15 | }
16 |
17 | public getSeriesDisplayName(): string {
18 | let displayName = this.tags['display_name'];
19 | if (displayName === '') {
20 | return 'Misc. Charges';
21 | } else {
22 | return displayName;
23 | }
24 | }
25 |
26 | public getSumOfAllValues(): number {
27 | let sum = 0;
28 |
29 | for (let i = 0; i < this.values.length; i++) {
30 | let v = this.values[i];
31 | if (v.length === 2) {
32 | sum = sum + v[1];
33 | }
34 | }
35 | return sum;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/ui/src/app/model/index.ts:
--------------------------------------------------------------------------------
1 | export { User } from './user.model';
2 | export { Dimension } from './dimension.model';
3 | export { Graph } from './graph.model';
4 | export { Bucket } from './bucket.model';
5 | export { Account } from './account.model';
6 | export { Report } from './report.model';
7 | export { UsageUnit } from './usage.model';
8 | export { Config } from './config.model';
9 |
--------------------------------------------------------------------------------
/ui/src/app/model/report.model.ts:
--------------------------------------------------------------------------------
1 | import { Bucket } from './bucket.model';
2 | import { Account } from './account.model';
3 |
4 | export interface Report {
5 | id?: string;
6 | ctime?: string;
7 | owner_user_id?: string;
8 | report_name?: string;
9 | aws_secret_access_key?: string;
10 | retention_days?: number;
11 | buckets?: Bucket[];
12 | accounts?: Account[];
13 | status?: string;
14 | status_detail?: string;
15 | }
16 |
--------------------------------------------------------------------------------
/ui/src/app/model/usage.model.ts:
--------------------------------------------------------------------------------
1 | export interface UsageUnit {
2 | name: string;
3 | usagefamilies: string[];
4 | }
5 |
--------------------------------------------------------------------------------
/ui/src/app/model/user.model.ts:
--------------------------------------------------------------------------------
1 | export class User {
2 | public email: string;
3 |
4 | constructor(data?) {
5 | if (typeof data === 'object') {
6 | for (let key in data) {
7 | if (data.hasOwnProperty(key)) {
8 | this[key] = data[key];
9 | }
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/app/no-content/index.ts:
--------------------------------------------------------------------------------
1 | export * from './no-content.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/no-content/no-content.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-no-content',
5 | template: `
6 |
7 |
404: page missing
8 |
9 | `,
10 | })
11 | export class NoContentComponent {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/ui/src/app/password/index.ts:
--------------------------------------------------------------------------------
1 | export * from './password.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/password/password-data.ts:
--------------------------------------------------------------------------------
1 | export class PasswordData {
2 | public currentPassword: string;
3 | public newPassword: string;
4 | public confirmPassword: string;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/src/app/password/password.component.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
25 |
38 |
39 | Apply
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ui/src/app/password/password.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { UserService } from '../services';
4 | import { AppError } from '../common/shared';
5 | import { NotificationsService } from '../common/notifications';
6 | import { PasswordData } from './password-data';
7 |
8 | @Component({
9 | selector: 'axc-password',
10 | templateUrl: './password.component.html',
11 | })
12 | export class PasswordComponent {
13 |
14 | public changePasswordData: PasswordData;
15 |
16 | constructor(private userService: UserService, private notificationService: NotificationsService) {
17 | this.changePasswordData = new PasswordData();
18 | }
19 |
20 | public async doChangePassword(form) {
21 | await this.userService.changePassword(this.changePasswordData).then((res) => {
22 | this.notificationService.success('Password has been updated');
23 | this.changePasswordData = new PasswordData();
24 | form.reset();
25 | }).catch((err) => {
26 | let appErr = err;
27 | this.notificationService.error(appErr.message || 'Can not update password. Please try again later.');
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/app/pipe/axc-pipe.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { AxcDatePipe } from './date.pipe';
3 | import { DatePipe } from '@angular/common';
4 | import { TrimTextPipe } from './trim-text.pipe';
5 |
6 | @NgModule({
7 | declarations: [
8 | AxcDatePipe,
9 | TrimTextPipe,
10 | ],
11 | exports: [
12 | AxcDatePipe,
13 | TrimTextPipe,
14 | ],
15 | providers: [
16 | DatePipe,
17 | ],
18 | })
19 | export class AxcPipeModule {
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/pipe/date.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { DatePipe } from '@angular/common';
3 |
4 | @Pipe({
5 | name: 'axcDate',
6 | })
7 |
8 | export class AxcDatePipe implements PipeTransform {
9 | private dateFormat: string = 'yyyy/MM/dd HH:mm';
10 |
11 | constructor (private datePipe: DatePipe) {
12 | }
13 |
14 | public transform(value: string, pattern: string) {
15 | return this.datePipe.transform(value, pattern ? pattern : this.dateFormat);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ui/src/app/pipe/index.ts:
--------------------------------------------------------------------------------
1 | export { TrimTextPipe } from './trim-text.pipe';
2 | export { AxcDatePipe } from './date.pipe';
3 |
--------------------------------------------------------------------------------
/ui/src/app/pipe/trim-text.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'trimText',
5 | })
6 |
7 | export class TrimTextPipe implements PipeTransform {
8 | public transform(value: string, letters = 30) {
9 | let maxLength = letters;
10 | let ret = value;
11 | if (ret.length > maxLength) {
12 | ret = ret.substr(0, maxLength - 3) + '…';
13 | }
14 | return ret;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/src/app/report/advanced-filter.component.html:
--------------------------------------------------------------------------------
1 |
2 |
{{filter.display_name}}
3 |
4 | All {{filter.display_name}}
5 | {{option.display_name}}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ui/src/app/report/advanced-filter.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit, EventEmitter, Output } from '@angular/core';
2 | import { Dimension } from '../model';
3 |
4 | @Component({
5 | selector: 'axc-advanced-filter',
6 | templateUrl: './advanced-filter.component.html',
7 | styles: [
8 | require('./advanced-filter.scss'),
9 | ],
10 | })
11 | export class AdvancedFilterComponent implements OnInit {
12 | @Input()
13 | public filter: Dimension;
14 |
15 | @Output()
16 | public onChange: EventEmitter = new EventEmitter();
17 |
18 | public selectedFilters: Map = new Map();
19 |
20 | public ngOnInit() {
21 | // do nothing
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/ui/src/app/report/advanced-filter.scss:
--------------------------------------------------------------------------------
1 | .sub-filters {
2 | float:left;
3 | width:33%;
4 | padding:15px;
5 | ul {
6 | margin: 0px;
7 | width: 100%;
8 |
9 | li {
10 | margin:10px 0px;
11 | border-radius: 5px;
12 | padding: 0px;
13 | list-style-type: none;
14 | border: 1px solid #C2D9F2;
15 | line-height: 2em;
16 | font-size: 1em;
17 | padding-left: 1em;
18 | overflow: hidden;
19 | text-overflow: ellipsis;
20 | white-space: nowrap;
21 | cursor: pointer;
22 | }
23 | .selectedTab {
24 | background: #D8E7F5;
25 | }
26 | .hide {
27 | display: none;
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/app/report/config.ts:
--------------------------------------------------------------------------------
1 | export interface GroupBy {
2 | display_name: string;
3 | key: string;
4 | }
5 |
6 | export interface Interval {
7 | display_name: string;
8 | key: string;
9 | }
10 |
11 | export const resourceMetricKey = 'Resource Count';
12 | export const DefaultMetrics = ['Unblended Cost', 'Blended Cost', resourceMetricKey];
13 |
14 | export const groupByDefaults: GroupBy[] = [
15 | {
16 | display_name: 'None',
17 | key: '',
18 | },
19 | {
20 | display_name: 'Account',
21 | key: 'accounts',
22 | },
23 | {
24 | display_name: 'Region',
25 | key: 'regions',
26 | },
27 | {
28 | display_name: 'Services',
29 | key: 'services',
30 | },
31 | ];
32 |
33 | export const intervalDefaults: Interval[] = [
34 | {
35 | display_name: 'Hrs',
36 | key: '1h',
37 | },
38 | {
39 | display_name: 'Day',
40 | key: '1d',
41 | },
42 | {
43 | display_name: 'Week',
44 | key: '1w',
45 | },
46 | {
47 | display_name: 'Month',
48 | key: '1M',
49 | },
50 | ];
51 |
--------------------------------------------------------------------------------
/ui/src/app/report/data-chart/data-chart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/src/app/report/data-chart/data-chart.scss:
--------------------------------------------------------------------------------
1 | text {
2 | font-size:12px;
3 | }
4 | svg {
5 | display: block;
6 | float: left;
7 | }
8 |
9 | .nvd3.nv-pie.nv-chart-donut1 .nv-pie-title {
10 | opacity: 0.4;
11 | fill: rgba(224, 116, 76, 0.91);
12 | }
13 |
14 | #reportDonut {
15 | height: 550px !important;
16 | width: 100% !important;
17 | svg {
18 | .c3-line {
19 | stroke-width: 2px;
20 | }
21 | .c3-circle {
22 | stroke-width: 0px;
23 | visibility: hidden;
24 | }
25 | .c3-circle._expanded_ {
26 | stroke-width: 1px;
27 | visibility: visible;
28 | }
29 | }
30 | .c3-tooltip-container {
31 | pointer-events: auto !important;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ui/src/app/report/data-chart/index.ts:
--------------------------------------------------------------------------------
1 | export { DataChartComponent } from './data-chart.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/report/index.ts:
--------------------------------------------------------------------------------
1 | export * from './report.component';
2 | import { NgModule } from '@angular/core';
3 | import { CommonModule } from '@angular/common';
4 | import { DataChartComponent } from './data-chart';
5 | import { AdvancedFilterComponent } from './advanced-filter.component';
6 | import { TagsFilterComponent } from './tags-filter/tags-filter.component';
7 |
8 | @NgModule({
9 | declarations: [
10 | DataChartComponent,
11 | AdvancedFilterComponent,
12 | TagsFilterComponent,
13 | ],
14 | exports: [
15 | DataChartComponent,
16 | AdvancedFilterComponent,
17 | TagsFilterComponent,
18 | ],
19 | imports: [
20 | CommonModule,
21 | ],
22 | })
23 |
24 | export class ReportGraphModule {
25 | }
26 |
--------------------------------------------------------------------------------
/ui/src/app/report/tags-filter/tags-filter.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ tag.display_name }}
4 |
5 |
6 |
7 |
8 |
9 | = 10">
10 |
{{ typeName }}: {{ selectedTags.size }}
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ui/src/app/report/tags-filter/tags-filter.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Output, EventEmitter } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-tags-filter',
5 | templateUrl: './tags-filter.component.html',
6 | })
7 | export class TagsFilterComponent {
8 |
9 | @Input()
10 | public tags: any[];
11 |
12 | @Input()
13 | public selectedTags: any;
14 |
15 | @Input()
16 | public typeName: string;
17 |
18 | @Output()
19 | public onRemoveTag: EventEmitter = new EventEmitter();
20 |
21 | @Output()
22 | public onRefresh: EventEmitter = new EventEmitter();
23 |
24 | close(tag) {
25 | this.onRemoveTag.next(tag);
26 | this.onRefresh.next({});
27 | }
28 |
29 | closeAll() {
30 | this.tags.forEach((tag: any) => {
31 | if (this.selectedTags.has(tag.name)) {
32 | this.onRemoveTag.next(tag);
33 | }
34 | });
35 | this.onRefresh.next({});
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ui/src/app/resolvers/has-no-session.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AuthenticationService } from '../services/authentication.service';
5 | import { User } from '../model/user.model';
6 |
7 | @Injectable()
8 | export class HasNoSessionResolver implements Resolve {
9 | constructor(private authenticationService: AuthenticationService) { }
10 |
11 | public resolve(
12 | route: ActivatedRouteSnapshot,
13 | state: RouterStateSnapshot
14 | ): Promise {
15 | return this.authenticationService.hasNoSession(route, state);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ui/src/app/resolvers/report.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { ConfigService } from '../services/config.service';
5 | import { User } from '../model/user.model';
6 |
7 | /**
8 | * This resolves that the report configured has at least one bucket
9 | */
10 | @Injectable()
11 | export class ReportResolver implements Resolve {
12 | constructor(private configService: ConfigService) { }
13 |
14 | public async resolve(
15 | route: ActivatedRouteSnapshot,
16 | state: RouterStateSnapshot,
17 | ): Promise {
18 | return await this.configService.getReportConfig();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/resolvers/who-am-i.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 |
4 | import { AuthenticationService } from '../services/authentication.service';
5 | import { User } from '../model/user.model';
6 |
7 | /**
8 | * This resolves the WhoAmI information after reload
9 | */
10 | @Injectable()
11 | export class WhoAmIResolver implements Resolve {
12 | constructor(private authenticationService: AuthenticationService) { }
13 |
14 | public resolve(
15 | route: ActivatedRouteSnapshot,
16 | state: RouterStateSnapshot
17 | ): Promise {
18 | return this.authenticationService.hasSession(route, state);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ui/src/app/services/authentication.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http } from '@angular/http';
3 | import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
4 | import { ConfigService } from './config.service';
5 | import { User, Config } from '../model';
6 |
7 | @Injectable()
8 | export class AuthenticationService {
9 | public currentUser: User = new User({});
10 | private fwdUrl: string = 'app/report';
11 |
12 | constructor(private router: Router, private http: Http, private configService: ConfigService) {
13 | }
14 |
15 | public getUser(): User {
16 | return this.currentUser || null;
17 | }
18 |
19 | public async doLogin(username: string, password: string): Promise<{ user: User, config: Config }> {
20 | let data = await this.http.post('/auth/login', { username, password }).map((res) => new User(res.json())).toPromise();
21 | this.currentUser = new User(data);
22 | let configData = await this.configService.getConfig();
23 | return { user: this.currentUser, config: configData };
24 | }
25 |
26 | public async doLogout(): Promise {
27 | await this.http.post('/auth/logout', {}).toPromise();
28 | this.router.navigate(['/base/login']);
29 | this.currentUser = null;
30 | return { success: true };
31 | }
32 |
33 | public async whoAmI(): Promise {
34 | let user = await this.http.get('/auth/identity').map((res) => new User(res.json())).toPromise();
35 | this.currentUser = new User(user);
36 | return user;
37 | }
38 |
39 | public async hasSession(
40 | route: ActivatedRouteSnapshot,
41 | state: RouterStateSnapshot): Promise {
42 | let flag = false;
43 | try {
44 | await this.whoAmI();
45 | let configData = await this.configService.getConfig();
46 |
47 | if (configData.eula_accepted) {
48 | flag = true;
49 | } else {
50 | this.redirectToEULA();
51 | }
52 | return flag;
53 | } catch (e) {
54 | this.redirectIfSessionNotFound();
55 | return false;
56 | }
57 | }
58 |
59 | public async hasNoSession(
60 | route: ActivatedRouteSnapshot,
61 | state: RouterStateSnapshot): Promise {
62 | try {
63 | this.currentUser = new User(await this.whoAmI());
64 | if (this.currentUser) {
65 | this.redirectIfSessionExists(decodeURIComponent(route.params['fwd'] ? route.params['fwd'] : ''));
66 | }
67 | return false;
68 | } catch (e) {
69 | return true;
70 | }
71 | }
72 |
73 | private redirectIfSessionExists(fwdUrl = '') {
74 | this.router.navigateByUrl(fwdUrl || this.fwdUrl);
75 | }
76 |
77 | private redirectIfSessionNotFound() {
78 | this.router.navigateByUrl('/base/login/' + encodeURIComponent(window.location.pathname));
79 | }
80 |
81 | private redirectToEULA() {
82 | this.router.navigate(['/base/eula', { fwdUrl: encodeURIComponent(window.location.pathname) }]);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/ui/src/app/services/config.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, URLSearchParams } from '@angular/http';
3 | import { Bucket, Report } from '../model';
4 | import { Config } from '../model/config.model';
5 |
6 | @Injectable()
7 | export class ConfigService {
8 |
9 | constructor(private http: Http) {
10 | // do something
11 | }
12 |
13 | public async getReportConfig(): Promise {
14 | return await this.http.get(`/reports`).map((res) => res.json().data[0]).toPromise();
15 | }
16 |
17 |
18 | public async createReportConfig(data: Report): Promise {
19 | return await this.http.post('/reports', data).map((res) => {
20 | return res.json();
21 | }).toPromise();
22 | }
23 |
24 | public async updateReportConfig(id: string, data: Report): Promise {
25 | return await this.http.put(`/reports/${id}`, data).map((res) => {
26 | return res.json();
27 | }).toPromise();
28 | }
29 |
30 | public async deleteBucketFromReport(reportId: string, id: string): Promise {
31 | return await this.http.delete(`/reports/${reportId}/buckets/${id}`).map((res) => {
32 | return res.json();
33 | }).toPromise();
34 | }
35 |
36 | public async createBucket(reportId: string, data: Bucket): Promise {
37 | return await this.http.post(`/reports/${reportId}/buckets`, data).map((res) => {
38 | return res.json();
39 | }).toPromise();
40 | }
41 |
42 | public async updateBucket(reportId: string, id: string, data: Bucket): Promise {
43 | return await this.http.put(`/reports/${reportId}/buckets/${id}`, data).map((res) => {
44 | return res.json();
45 | }).toPromise();
46 | }
47 |
48 | public async getConfig(): Promise {
49 | return await this.http.get(`/config`).map((res) => res.json()).toPromise();
50 | }
51 |
52 | public async updateConfig(data: Config): Promise {
53 | return await this.http.put(`/config`, data).map((res) => res.json()).toPromise();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/ui/src/app/services/index.ts:
--------------------------------------------------------------------------------
1 | export * from './authentication.service';
2 | export * from './user.service';
3 | export * from './reporting.service';
4 | export * from './config.service';
5 |
6 | import { NgModule } from '@angular/core';
7 | import { AuthenticationService, UserService, ReportingService, ConfigService } from '../services';
8 |
9 | @NgModule({
10 | providers: [
11 | AuthenticationService,
12 | UserService,
13 | ReportingService,
14 | ConfigService,
15 | ],
16 | })
17 | export class ServicesModule {
18 | }
19 |
--------------------------------------------------------------------------------
/ui/src/app/services/user.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Jsonp, Http } from '@angular/http';
3 |
4 | import { User } from '../model';
5 | import { ChangePasswordData } from '../+user-management/change-password/change-password-data';
6 |
7 | @Injectable()
8 | export class UserService {
9 |
10 | constructor(private jsonp: Jsonp, private http: Http) {
11 | }
12 |
13 | public async changePassword(changePasswordData: ChangePasswordData): Promise {
14 | return await this.http.put('/account', { password: changePasswordData.newPassword,
15 | current_password: changePasswordData.currentPassword }).map((res) => res.json()).toPromise();
16 | }
17 |
18 | public getUser(idForUser: number): Promise {
19 | return new Promise((resolve) => {
20 | resolve(new User({
21 | firstName: 'Foo',
22 | lastName: 'Bar',
23 | email: 'foo@bar.com',
24 | status: 2,
25 | id: idForUser,
26 | }));
27 | });
28 | }
29 |
30 | public putUser(idForUser: number, user: User): Promise {
31 | return new Promise((resolve) => {
32 | resolve(new User({
33 | firstName: 'Foo',
34 | lastName: 'Bar',
35 | email: 'foo@bar.com',
36 | status: 2,
37 | id: idForUser,
38 | }));
39 | });
40 | }
41 |
42 | public resetPassword(resetPassword: any): Promise {
43 | return new Promise((resolve) => {
44 | resolve(new User({
45 | firstName: 'Foo',
46 | lastName: 'Bar',
47 | email: 'foo@bar.com',
48 | status: 2,
49 | id: 1,
50 | }));
51 | });
52 | }
53 |
54 | public confirmResetPassword(token: string, password: string): Promise {
55 | return new Promise((resolve) => {
56 | resolve(new User({
57 | firstName: 'Foo',
58 | lastName: 'Bar',
59 | email: 'foo@bar.com',
60 | status: 2,
61 | id: 1,
62 | }));
63 | });
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ui/src/app/settings/index.ts:
--------------------------------------------------------------------------------
1 | export { SettingsComponent } from './settings.component';
2 |
--------------------------------------------------------------------------------
/ui/src/app/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Report Setup
6 |
7 |
8 | Accounts Alias
9 |
10 |
11 | SSL Keys
12 |
13 |
14 | Change Password
15 |
16 |
17 | About
18 |
19 |
20 |
21 |
24 |
27 |
28 |
29 |
30 |
About
31 |
32 |
33 | Version:
34 |
35 |
36 | {{appVersion}}
37 |
38 |
39 |
40 |
47 |
54 |
55 |
--------------------------------------------------------------------------------
/ui/src/app/settings/settings.component.scss:
--------------------------------------------------------------------------------
1 | @import './../../assets/css/config';
2 |
3 |
--------------------------------------------------------------------------------
/ui/src/app/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'axc-settings',
5 | templateUrl: './settings.component.html',
6 | styles: [
7 | require('./settings.component.scss'),
8 | ],
9 | })
10 | export class SettingsComponent {
11 | public appVersion: string = `${process.env.VERSION}`;
12 | public settingType: string = 'SETUP';
13 |
14 | public setTab(type: string) {
15 | this.settingType = type;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ui/src/assets/css/_base.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | }
5 |
6 | body {
7 | font: 16px/1.23 'Heebo', sans-serif;
8 | background-color: $ax-color-gray-3;
9 | }
10 |
11 | .ax-container {
12 | max-width: $ax-width-container;
13 | margin: 0 auto;
14 | padding: 0 20px;
15 | }
16 |
17 | .text-left {
18 | text-align: left;
19 | }
20 |
21 | .text-center {
22 | text-align: center;
23 | }
24 |
25 | .text-right {
26 | text-align: right;
27 | }
28 |
29 | .pull-left {
30 | float: left;
31 | }
32 |
33 | .pull-right {
34 | float: right;
35 | }
36 |
37 | .cursor {
38 | cursor: pointer;
39 | }
40 |
41 | .margin-left-regular {
42 | margin-left: 1em;
43 | }
44 |
45 | .margin-top-regular {
46 | margin-top: 1em;
47 | }
48 |
49 | .margin-bottom-regular {
50 | margin-bottom: 1em;
51 | }
52 |
53 | .margin-bottom-large {
54 | margin-bottom: 3em;
55 | }
56 | .margin-top-large {
57 | margin-top: 3em;
58 | }
59 |
60 | .capitalize {
61 | text-transform: capitalize;
62 | }
63 |
64 | a, a:hover {
65 | color: $ax-color-teal-6;
66 | }
67 |
68 | .hide {
69 | display: none;
70 | }
71 |
72 | .show {
73 | display: block;
74 | }
75 |
76 | .dib{
77 | display: inline-block;
78 | }
79 |
--------------------------------------------------------------------------------
/ui/src/assets/css/_config.scss:
--------------------------------------------------------------------------------
1 | // Colors and font sizes defined in https://applatix.atlassian.net/wiki/display/UX/Typography
2 |
3 | $ax-color-white: #fff;
4 | $ax-color-teal: #00BDCE;
5 | $ax-color-gray: #778E94;
6 | $ax-color-dark-gray: #475458;
7 | $ax-storm-dust: #626362;
8 | $ax-color-dark-teal: #07656E;
9 | $ax-color-light-border: #c6cfd4;
10 | $ax-color-bg: #fff;
11 | $ax-intro-layout-bg: #f1f1f1;
12 | $ax-color-failed: #dd2c00;
13 | $ax-color-success: #4daf51;
14 | $ax-color-warning: #f4c01f;
15 |
16 | // To be documented:
17 | $ax-color-base-mlight: #c0eef1; // medium light
18 | $ax-color-base-slight: #e7f9fa; // super light
19 | $ax-color-base-dlight: #46888e; // dark light
20 |
21 | // colors
22 | $ax-color-base: $ax-color-teal;
23 | $ax-color-base-2: $ax-storm-dust;
24 | $ax-color-base-strong: $ax-color-dark-teal;
25 | $ax-color-bg-strong: $ax-color-light-border;
26 | $ax-color-secondary: $ax-storm-dust;
27 |
28 | $ax-color-bg-light: $ax-color-white;
29 |
30 | // Foundation settings
31 | $global-width: 100%;
32 | $dropdown-width: auto;
33 | $breakpoint-classes: (small medium large xlarge xxlarge);
34 | $body-font-family: 'Open Sans', sans-serif;
35 |
36 | $fa-font-path: 'assets/font-awesome/fonts';
37 |
38 | // Z indexes
39 | $ax-zIndex-dropdown-menu: 1;
40 | $ax-zIndex-topbar: 2;
41 | $ax-zIndex-sliding-panel: 3;
42 | $ax-zIndex-notification: 4;
43 |
44 | // Button Colors
45 | $button-primary: #4A90E2;
46 | $button-primary-font: $ax-color-white;
47 | $button-default: $ax-color-white;
48 | $button-default-font: #4A90E2;
49 |
50 | // Others
51 | $ax-topbar-height: 80px;
52 | $ax-nav-width: 140px;
53 | $ax-width-container: 1284px;
54 | $ax-toolbar-height: 80px;
55 |
56 | // New colors
57 | $ax-color-gray-1: #F8FBFB;
58 | $ax-color-gray-2: #EFF3F5;
59 | $ax-color-gray-3: #DEE6EB;
60 | $ax-color-gray-4: #CCD6DD;
61 | $ax-color-gray-5: #8FA4B1;
62 | $ax-color-gray-6: #6D7F8B;
63 | $ax-color-gray-7: #495763;
64 | $ax-color-gray-8: #363C4A;
65 | $ax-color-gray-9: #000000;
66 |
67 | $ax-color-teal-1: #F5FBFD;
68 | $ax-color-teal-2: #DFF6F9;
69 | $ax-color-teal-3: #BDECF2;
70 | $ax-color-teal-4: #99E1EA;
71 | $ax-color-teal-5: #1FBDD0;
72 | $ax-color-teal-6: #00A2B3;
73 | $ax-color-teal-7: #006F8A;
74 | $ax-color-teal-8: #004C67;
75 |
76 | $ax-color-yellow: #FFD100;
77 | $ax-color-green: #7ED321;
78 | $ax-color-orange: #FF8046;
79 | $ax-color-red: #F00052;
80 | $ax-color-blue: #48A0FF;
81 | $ax-color-purple: #623C98;
82 | $ax-color-cyan: #3DE3FF;
83 |
84 | $ax-color-base-dark: #2b303a;
85 |
86 | $ax-color-error: #E96D76;
87 | $ax-color-success: #18BE94;
88 | $ax-color-progress: #0DADEA;
89 |
--------------------------------------------------------------------------------
/ui/src/assets/css/_reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
49 |
50 |
51 | a {
52 | text-decoration: none;
53 | }
54 |
--------------------------------------------------------------------------------
/ui/src/assets/css/_typography.scss:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size: 2em;
3 | color: $ax-color-teal-7;
4 | }
5 |
6 | h2 {
7 | margin-bottom: 20px;
8 | font-size: 1.250em;
9 | color: $ax-color-teal-7;
10 | }
11 |
12 | h3 {
13 | margin-bottom: 16px;
14 | font-size: 1.250em;
15 | color: $ax-color-teal-7;
16 | }
17 |
18 | h4 {
19 | margin-bottom: 16px;
20 | font-size: 1em;
21 | color: $ax-color-teal-7;
22 | }
23 |
24 | p {
25 | margin-bottom: 16px;
26 | }
27 |
28 | small {
29 | font-size: .9em;
30 | }
31 |
32 | strong {
33 | font-weight: bold;
34 | }
35 |
36 | a {
37 | color: $ax-color-teal-7;
38 |
39 | &:hover {
40 | color: $ax-color-teal-6;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_buttons.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .ax-btn {
4 | display: inline-block;
5 | padding: 0 18px;
6 | font-weight: bold;
7 | line-height: 38px;
8 | font-size: 0.8em;
9 | text-transform: uppercase;
10 | cursor: pointer;
11 | outline: 0;
12 | background-color: $button-default;
13 | color: $ax-color-teal-6;
14 | border: 1px solid #ddd;
15 | border-radius: 24px;
16 |
17 | &--base {
18 | color: $ax-color-gray-1;
19 | background-color: $ax-color-teal-6;
20 |
21 | &:not([disabled]):hover {
22 | background-color: $ax-color-teal-5;
23 | color: $ax-color-white;
24 | }
25 | }
26 |
27 | &--base-o {
28 | color: $ax-color-teal-6;
29 | background-color: transparent;
30 | border: 1px solid $ax-color-teal-4;
31 | }
32 |
33 | &--full-width {
34 | width: 100%;
35 | }
36 |
37 | &--wide {
38 | padding: 0 50px;
39 | }
40 |
41 | &--radius-2 {
42 | width: 36px;
43 | height: 36px;
44 | border-radius: 50%;
45 | border: 2px solid $ax-color-bg-strong;
46 | color: $ax-color-dark-gray;
47 | padding: 0;
48 | font-size: 15px;
49 | }
50 | }
51 |
52 | .ax-btn-radius-o {
53 | position: relative;
54 | display: inline-block;
55 | vertical-align: middle;
56 | width: 2em;
57 | height: 2em;
58 | border: 1px solid $ax-color-base-dlight;
59 | border-radius: 50%;
60 | outline: 0;
61 | cursor: pointer;
62 |
63 | &::before,
64 | &::after {
65 | position: absolute;
66 | top: 50%;
67 | left: 0.475em;
68 | width: 1em;
69 | height: 1px;
70 | background: $ax-color-base-strong;
71 | content: '';
72 | }
73 |
74 | &--plus {
75 | &::before {
76 | transform: rotate(90deg);
77 | }
78 | }
79 |
80 | &--close {
81 | &::before {
82 | transform: rotate(45deg);
83 | }
84 |
85 | &::after {
86 | transform: rotate(-45deg);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .dropdown-list {
2 | margin: 0;
3 | list-style: none;
4 |
5 | li {
6 | padding: 14px 20px;
7 | cursor: pointer;
8 |
9 | &:hover {
10 | background-color: $ax-color-bg;
11 | }
12 | }
13 | }
14 |
15 | .dropdown-anchor-border {
16 | position: relative;
17 | display: inline-block;
18 | overflow: hidden;
19 | padding: 12px 40px 12px 14px;
20 | color: $ax-color-base-strong;
21 | cursor: pointer;
22 | text-overflow: ellipsis;
23 | border: 1px solid $ax-color-base-dlight;
24 | border-radius: 26px;
25 |
26 | > i {
27 | margin-right: 6px;
28 | }
29 |
30 | &__arrow {
31 | position: absolute;
32 | right: 16px;
33 | top: 50%;
34 | transform: translateY(-50%);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_list-group.scss:
--------------------------------------------------------------------------------
1 | .axc-list-group {
2 | margin: 0;
3 | padding: 2px;
4 | background-color: #fff;
5 | border-radius: 6px;
6 | list-style: none;
7 |
8 | &__item {
9 | padding: 8px 15px;
10 | font-weight: 400;
11 | font-size: 13px;
12 | color: $ax-color-gray-7;
13 | text-transform: uppercase;
14 | border-radius: 5px;
15 | cursor: pointer;
16 |
17 | &.active {
18 | color: #fff;
19 | background-color: $ax-color-teal-6;
20 | cursor: default;
21 | }
22 |
23 | &:not(.active):hover {
24 | background-color: $ax-color-gray-2;
25 | }
26 |
27 | &:not(:last-child) {
28 | margin-bottom: 2px;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_table.scss:
--------------------------------------------------------------------------------
1 | .ax-table {
2 | width: 100%;
3 | table-layout: fixed;
4 | font-size: .85em;
5 | border-collapse: separate;
6 | border-spacing: 0px;
7 |
8 | th {
9 | padding: .75em 1.4em;
10 | color: $ax-color-base-strong;
11 | text-align: left;
12 | }
13 |
14 | td {
15 | padding: 0 1.4em;
16 | line-height: 80px;
17 | color: $ax-color-secondary;
18 | white-space: nowrap;
19 | text-overflow: ellipsis;
20 | overflow: hidden;
21 | vertical-align: middle;
22 | border-bottom: 1px solid $ax-color-bg-strong;
23 | border-top: 3px solid $ax-color-bg;
24 | }
25 |
26 | tbody tr {
27 | background-color: $ax-color-bg-light;
28 |
29 | &.selected {
30 | background-color: $ax-color-base-slight;
31 | }
32 |
33 | &:first-child td {
34 | border-top: 0;
35 | }
36 | }
37 |
38 | &--interactive {
39 | tbody tr:hover {
40 | background-color: $ax-color-base-slight;
41 | cursor: pointer;
42 | }
43 | }
44 |
45 | &--statuses {
46 | tbody tr {
47 | td:first-child {
48 | position: relative;
49 |
50 | &::before {
51 | position: absolute;
52 | left: 0;
53 | top: 0;
54 | bottom: -1px;
55 | width: 4px;
56 | content: '';
57 | }
58 | }
59 |
60 | &.success td:first-child::before {
61 | background-color: $ax-color-success;
62 | }
63 |
64 | &.pending td:first-child::before {
65 | background-color: $ax-color-warning;
66 | }
67 |
68 | &.fail td:first-child::before {
69 | background-color: $ax-color-failed;
70 | }
71 | }
72 | }
73 |
74 | &--with-expaneding {
75 | tr:not(.expanded):nth-child(2n) {
76 | display: none;
77 | }
78 |
79 | tr.expanded:nth-child(2n - 1) td {
80 | border-bottom: 0;
81 | }
82 |
83 | tr:nth-child(2n) td {
84 | border-top: 0 !important;
85 | }
86 | }
87 |
88 | &--with-expaneding.ax-table--statuses {
89 | tbody tr {
90 | &.success + tr td:first-child::before {
91 | background-color: $ax-color-success;
92 | }
93 |
94 | &.pending + tr td:first-child::before {
95 | background-color: $ax-color-warning;
96 | }
97 |
98 | &.fail + tr td:first-child::before {
99 | background-color: $ax-color-failed;
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_tag.scss:
--------------------------------------------------------------------------------
1 | .ax-tag {
2 | display: inline-block;
3 | margin: 0 5px 5px 0;
4 | padding: 2px;
5 | font-size: 12px;
6 | color: $ax-color-gray-6;
7 | background: $ax-color-gray-4;
8 | border-radius: 5px;
9 |
10 | &__name {
11 | display: inline-block;
12 | padding: 3px 4px;
13 | background: #fff;
14 | border-radius: 4px;
15 | }
16 |
17 | &__remove {
18 | display: inline-block;
19 | padding: 0 3px;
20 | cursor: pointer;
21 |
22 | &:hover {
23 | color: $ax-color-teal-6;
24 | }
25 | }
26 |
27 | &[hidden] {
28 | display: none;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_toolbar.scss:
--------------------------------------------------------------------------------
1 | .ax-toolbar {
2 | padding: 0 0 20px;
3 | font-size: 16px;
4 | font-weight: bold;
5 |
6 | &::after,
7 | .ax-container::after {
8 | display: inline-block;
9 | vertical-align: middle;
10 | content: "";
11 | }
12 |
13 | &__item {
14 | display: inline-block;
15 | vertical-align: middle;
16 | line-height: 1.5;
17 | color: $ax-color-teal-6;
18 |
19 | &:not(:last-child) {
20 | margin-right: 20px;
21 | }
22 | }
23 |
24 | &__button {
25 | padding: 0 16px;
26 | font-weight: 500;
27 | font-size: .875em;
28 | line-height: 40px;
29 | color: $ax-color-teal-7;
30 | text-transform: uppercase;
31 | background-color: $ax-color-gray-2;
32 | border-radius: 5px;
33 | cursor: pointer;
34 | outline: 0;
35 |
36 | &:hover {
37 | background-color: $ax-color-gray-1;
38 | }
39 |
40 | i {
41 | font-size: 1.2em;
42 |
43 | &:first-child {
44 | margin-right: 4px;
45 | }
46 |
47 | &:last-child {
48 | margin-left: 4px;
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ui/src/assets/css/elements/_white-box.scss:
--------------------------------------------------------------------------------
1 | @import '../../../assets/css/config';
2 |
3 | .white-box {
4 | background-color: $ax-color-bg-light;
5 | padding: 1.5em 2em;
6 | border-radius: 6px;
7 | border: 2px solid $ax-color-bg-strong;
8 | }
9 |
--------------------------------------------------------------------------------
/ui/src/assets/css/main.scss:
--------------------------------------------------------------------------------
1 | @import './reset';
2 | @import './config';
3 |
4 | @import 'node_modules/font-awesome/scss/font-awesome';
5 | @import 'node_modules/foundation-sites/scss/foundation';
6 |
7 | @include foundation-global-styles;
8 | @include foundation-flex-classes;
9 | @include foundation-flex-grid;
10 | @include foundation-dropdown;
11 | @include foundation-typography;
12 |
13 | @import 'node_modules/c3/c3.min';
14 | @import 'node_modules/jquery-date-range-picker/dist/daterangepicker.min';
15 | @import './base';
16 | @import './typography';
17 | @import './elements/buttons';
18 | @import './elements/dropdown';
19 | @import './elements/form';
20 | @import './elements/list-group';
21 | @import './elements/table';
22 | @import './elements/tag';
23 | @import './elements/white-box';
24 | @import './elements/toolbar';
25 |
--------------------------------------------------------------------------------
/ui/src/assets/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/android-chrome-256x256.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #603cba
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ui/src/assets/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/favicon.ico
--------------------------------------------------------------------------------
/ui/src/assets/favicon/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "icons": [
4 | {
5 | "src": "/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | },
9 | {
10 | "src": "/android-chrome-256x256.png",
11 | "sizes": "256x256",
12 | "type": "image/png"
13 | }
14 | ],
15 | "theme_color": "#363c4a",
16 | "background_color": "#363c4a",
17 | "display": "standalone"
18 | }
--------------------------------------------------------------------------------
/ui/src/assets/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/ui/src/assets/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
23 |
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ui/src/assets/icons/AXClaudia_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/icons/AXClaudia_logo.png
--------------------------------------------------------------------------------
/ui/src/assets/icons/full_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ui/src/assets/icons/ico_failed.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | i_failed
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ui/src/assets/icons/ico_success.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | i_complete
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ui/src/assets/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Applatix/claudia/8052d2cb5081c4ee9eb5773628500f050787ca8d/ui/src/assets/icons/logo.png
--------------------------------------------------------------------------------
/ui/src/custom-typings.d.ts:
--------------------------------------------------------------------------------
1 | declare var ENV: string;
2 | declare var HMR: boolean;
3 | declare var System: SystemJS;
4 |
5 | interface SystemJS {
6 | import: (path?: string) => Promise;
7 | }
8 |
9 | interface GlobalEnvironment {
10 | ENV: string;
11 | HMR: boolean;
12 | SystemJS: SystemJS;
13 | System: SystemJS;
14 | }
15 |
16 | interface Es6PromiseLoader {
17 | (id: string): (exportName?: string) => Promise;
18 | }
19 |
20 | type FactoryEs6PromiseLoader = () => Es6PromiseLoader;
21 | type FactoryPromise = () => Promise;
22 |
23 | type AsyncRoutes = {
24 | [component: string]: Es6PromiseLoader |
25 | Function |
26 | FactoryEs6PromiseLoader |
27 | FactoryPromise
28 | };
29 |
30 |
31 | type IdleCallbacks = Es6PromiseLoader |
32 | Function |
33 | FactoryEs6PromiseLoader |
34 | FactoryPromise;
35 |
36 | interface WebpackModule {
37 | hot: {
38 | data?: any,
39 | idle: any,
40 | accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void;
41 | decline(deps?: any | string | string[]): void;
42 | dispose(callback?: (data?: any) => void): void;
43 | addDisposeHandler(callback?: (data?: any) => void): void;
44 | removeDisposeHandler(callback?: (data?: any) => void): void;
45 | check(autoApply?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
46 | apply(options?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
47 | status(callback?: (status?: string) => void): void | string;
48 | removeStatusHandler(callback?: (status?: string) => void): void;
49 | };
50 | }
51 |
52 |
53 | interface WebpackRequire {
54 | (id: string): any;
55 | (paths: string[], callback: (...modules: any[]) => void): void;
56 | ensure(ids: string[], callback: (req: WebpackRequire) => void, chunkName?: string): void;
57 | context(directory: string, useSubDirectories?: boolean, regExp?: RegExp): WebpackContext;
58 | }
59 |
60 | interface WebpackContext extends WebpackRequire {
61 | keys(): string[];
62 | }
63 |
64 | interface ErrorStackTraceLimit {
65 | stackTraceLimit: number;
66 | }
67 |
68 |
69 | // Extend typings
70 | interface NodeRequire extends WebpackRequire { }
71 | interface ErrorConstructor extends ErrorStackTraceLimit { }
72 | interface NodeRequireFunction extends Es6PromiseLoader { }
73 | interface NodeModule extends WebpackModule { }
74 | interface Global extends GlobalEnvironment { }
75 |
--------------------------------------------------------------------------------
/ui/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | <% if (webpackConfig.htmlElements.headTags) { %>
18 |
19 | <%= webpackConfig.htmlElements.headTags %>
20 | <% } %>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Loading...
29 |
30 |
31 | <% if (htmlWebpackPlugin.options.metadata.isDevServer && htmlWebpackPlugin.options.metadata.HMR !== true) { %>
32 |
33 |
34 | <% } %>
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ui/src/main.browser.ts:
--------------------------------------------------------------------------------
1 | import 'rxjs/Rx';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { decorateModuleRef } from './app/environment';
4 | import { bootloader } from '@angularclass/hmr';
5 | import { AppModule } from './app';
6 |
7 | export function main(): Promise {
8 | return platformBrowserDynamic()
9 | .bootstrapModule(AppModule)
10 | .then(decorateModuleRef)
11 | .catch(err => console.error(err));
12 | }
13 |
14 | bootloader(main);
15 |
--------------------------------------------------------------------------------
/ui/src/polyfills.browser.ts:
--------------------------------------------------------------------------------
1 | require('babel-core/register');
2 | require('babel-polyfill');
3 |
4 | import 'core-js/es6/symbol';
5 | import 'core-js/es6/object';
6 | import 'core-js/es6/function';
7 | import 'core-js/es6/parse-int';
8 | import 'core-js/es6/parse-float';
9 | import 'core-js/es6/number';
10 | import 'core-js/es6/math';
11 | import 'core-js/es6/string';
12 | import 'core-js/es6/date';
13 | import 'core-js/es6/array';
14 | import 'core-js/es6/regexp';
15 | import 'core-js/es6/map';
16 | import 'core-js/es6/set';
17 | import 'core-js/es6/weak-map';
18 | import 'core-js/es6/weak-set';
19 | import 'core-js/es6/typed';
20 | import 'core-js/es6/reflect';
21 | import 'core-js/es7/reflect';
22 | import 'zone.js/dist/zone';
23 | import 'ts-helpers';
24 |
25 | if ('production' !== ENV) {
26 | Error.stackTraceLimit = Infinity;
27 | /* tslint:disable */
28 | require('zone.js/dist/long-stack-trace-zone');
29 | /* tslint:enable */
30 | }
31 |
--------------------------------------------------------------------------------
/ui/src/vendor.browser.ts:
--------------------------------------------------------------------------------
1 | let $ = require('jquery');
2 | window['$'] = $;
3 | window['jQuery'] = $;
4 |
5 | let moment = require('moment');
6 | window['moment'] = moment;
7 | import 'd3/d3.min';
8 | import 'c3/c3.min';
9 |
10 | import '@angular/platform-browser';
11 | import '@angular/platform-browser-dynamic';
12 | import '@angular/core';
13 | import '@angular/common';
14 | import '@angular/forms';
15 | import '@angular/http';
16 | import '@angular/router';
17 |
18 | import '@angularclass/hmr';
19 |
20 | import 'rxjs/add/operator/map';
21 | import 'rxjs/add/operator/mergeMap';
22 | import 'jquery-date-range-picker/dist/jquery.daterangepicker.min';
23 |
24 | // import 'nvd3/build/nv.d3';
25 |
--------------------------------------------------------------------------------
/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "sourceMap": true,
10 | "noEmitHelpers": true,
11 | "strictNullChecks": false,
12 | "baseUrl": "./src",
13 | "paths": {
14 | },
15 | "lib": [
16 | "dom",
17 | "es6"
18 | ],
19 | "types": [
20 | "hammerjs",
21 | "node",
22 | "source-map",
23 | "uglify-js",
24 | "webpack",
25 | "jquery"
26 | ]
27 | },
28 | "exclude": [
29 | "node_modules",
30 | "dist"
31 | ],
32 | "awesomeTypescriptLoaderOptions": {
33 | "forkChecker": true,
34 | "useWebpackText": true
35 | },
36 | "compileOnSave": false,
37 | "buildOnSave": false,
38 | "atom": { "rewriteTsconfig": false }
39 | }
40 |
--------------------------------------------------------------------------------
/ui/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:latest",
3 | "rulesDirectory": [
4 | "node_modules/codelyzer"
5 | ],
6 | "rules": {
7 | "quotemark": [
8 | true,
9 | "single"
10 | ],
11 | "no-string-literal": false,
12 | "interface-name": [
13 | false,
14 | "always-prefix"
15 | ],
16 | "no-var-requires": false,
17 | "object-literal-sort-keys": false,
18 | "max-line-length": [
19 | true,
20 | 180
21 | ],
22 | "ordered-imports": [
23 | false
24 | ],
25 | "component-selector-name": [
26 | true,
27 | "kebab-case"
28 | ],
29 | "directive-selector-type": [
30 | true,
31 | "attribute"
32 | ],
33 | "component-selector-type": [
34 | true,
35 | "element"
36 | ],
37 | "directive-selector-prefix": [
38 | true,
39 | "axc"
40 | ],
41 | "component-selector-prefix": [
42 | true,
43 | "axc"
44 | ],
45 | "pipe-naming": [
46 | true,
47 | "camelCase"
48 | ],
49 | "use-input-property-decorator": true,
50 | "use-output-property-decorator": true,
51 | "use-host-property-decorator": true,
52 | "no-attribute-parameter-decorator": true,
53 | "no-input-rename": false,
54 | "no-output-rename": true,
55 | "no-forward-ref": true,
56 | "use-life-cycle-interface": true,
57 | "use-pipe-transform-interface": true,
58 | "component-class-suffix": true,
59 | "variable-name": false,
60 | "directive-class-suffix": true
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ui/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "modules",
3 | "out": "doc",
4 | "theme": "default",
5 | "ignoreCompilerErrors": "true",
6 | "experimentalDecorators": "true",
7 | "emitDecoratorMetadata": "true",
8 | "target": "ES5",
9 | "moduleResolution": "node",
10 | "preserveConstEnums": "true",
11 | "stripInternal": "true",
12 | "suppressExcessPropertyErrors": "true",
13 | "suppressImplicitAnyIndexErrors": "true",
14 | "module": "commonjs"
15 | }
16 |
--------------------------------------------------------------------------------
/ui/webpack.config.js:
--------------------------------------------------------------------------------
1 | switch (process.env.NODE_ENV) {
2 | case 'prod':
3 | case 'production':
4 | module.exports = require('./config/webpack.prod')({env: 'production'});
5 | break;
6 | case 'test':
7 | case 'testing':
8 | module.exports = require('./config/webpack.test')({env: 'test'});
9 | break;
10 | default:
11 | module.exports = require('./config/webpack.dev')({env: 'development'});
12 | }
13 |
--------------------------------------------------------------------------------
/userdb/schema.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Applatix, Inc.
2 | package userdb
3 |
4 | // SchemaVersion is the user database schema version of this version of the app
5 | const SchemaVersion = 1
6 |
7 | var schemaV1 = []string{`
8 | -- single row table to store configuration & system information
9 | CREATE TABLE configuration (
10 | id INT NOT NULL PRIMARY KEY DEFAULT 1,
11 | schema_version INT NOT NULL,
12 | session_auth_key BYTEA NOT NULL,
13 | session_crypt_key BYTEA NOT NULL,
14 | private_key TEXT NOT NULL,
15 | public_certificate TEXT NOT NULL,
16 | eula_accepted BOOLEAN NOT NULL DEFAULT false,
17 | CONSTRAINT single_row CHECK (id = 1)
18 | );
19 | `, `
20 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
21 | `, `
22 | CREATE EXTENSION IF NOT EXISTS "citext";
23 | `, `
24 | CREATE TABLE appuser (
25 | id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
26 | ctime TIMESTAMP NOT NULL DEFAULT current_timestamp,
27 | mtime TIMESTAMP NOT NULL DEFAULT current_timestamp,
28 | username CITEXT NOT NULL UNIQUE,
29 | password_hash TEXT NOT NULL
30 | );
31 | `, `
32 | CREATE TABLE report (
33 | id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
34 | ctime TIMESTAMP NOT NULL DEFAULT current_timestamp,
35 | mtime TIMESTAMP NOT NULL DEFAULT current_timestamp,
36 | report_name CITEXT NOT NULL,
37 | retention_days INT NOT NULL,
38 | status TEXT NOT NULL,
39 | status_detail TEXT NOT NULL,
40 | -- currently only one report per user is supported. remove unique constraint when this restriction is lifted
41 | owner_user_id UUID NOT NULL UNIQUE REFERENCES appuser(id) ON DELETE CASCADE
42 | );
43 | `, `
44 | CREATE TABLE bucket (
45 | id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
46 | ctime TIMESTAMP NOT NULL DEFAULT current_timestamp,
47 | report_id UUID NOT NULL REFERENCES report(id) ON DELETE CASCADE,
48 | bucketname TEXT NOT NULL,
49 | region TEXT NOT NULL,
50 | report_path TEXT NOT NULL,
51 | aws_access_key_id TEXT NOT NULL,
52 | aws_secret_access_key TEXT NOT NULL,
53 | CONSTRAINT unique_s3path UNIQUE (bucketname, report_path)
54 | );
55 | `, `
56 | -- Table of aws account ids associated with a report. Used primarily translating aws account ids to user defined display names
57 | CREATE TABLE aws_account (
58 | report_id UUID NOT NULL REFERENCES report(id) ON DELETE CASCADE,
59 | aws_account_id TEXT NOT NULL,
60 | name TEXT NOT NULL,
61 | CONSTRAINT unique_account UNIQUE (report_id, aws_account_id)
62 | );
63 | `, `
64 | -- Table of product codes mapped to AWS product information about the product. This is global information, not tied to a specific user
65 | CREATE TABLE aws_product (
66 | product_code TEXT NOT NULL UNIQUE,
67 | name TEXT NOT NULL,
68 | description TEXT NOT NULL
69 | );
70 | `,
71 | }
72 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Applatix, Inc.
2 | package claudia
3 |
4 | import "fmt"
5 |
6 | // Version information set by link flags during build
7 | var (
8 | Version = "unknown"
9 | Revision = "unknown"
10 | FullVersion = fmt.Sprintf("%s-%s", Version, Revision)
11 | BuildDate = "unknown"
12 | DisplayVersion = fmt.Sprintf("%s (Build Date: %s)", FullVersion, BuildDate)
13 | )
14 |
--------------------------------------------------------------------------------