Merge pull request #290 from zpencer/zpages

Add zpages gateway and webapp
This commit is contained in:
zpencer 2018-06-07 15:33:26 -07:00 committed by GitHub
commit b808b9cbe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 14380 additions and 0 deletions

201
grpc-zpages/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

71
grpc-zpages/README.md Normal file
View File

@ -0,0 +1,71 @@
Experimental warning
====================
This tool is a proof of concept, but feedback is welcome.
Description
===========
gRPC provides debug stats in the form of an RPC service. For example,
[channelz](https://github.com/grpc/proposal/blob/master/A14-channelz.md)
is a service that provides channel level debug information. This repo
contains a tool that connects to a remote gRPC `channelz` service and
displays the data as a web page using a local `golang` web server.
The goal is to provide a single CLI tool that can display all gRPC
debug pages.
A screenshot of servers page:
![image](github/screenshots/servers.png)
A screenshot of a detailed socket page:
![image](github/screenshots/socket.png)
Design
======
The tool has two components: an Angular web app and a CLI tool. The
web app is responsible for the core GUI logic, and the CLI acts as a
proxy that transforms the Angular app's web requests to gRPC
requests. The goal is to do the heavy lifting in the web browser in a
backend agnostic way. Other translation gateways can be created
easily created to suit different operating environments, e.g. as a
shared service or on a web server co-located on the gRPC host. The
complete set of web requests is defined in:
`web/channelzui/src/app/channelz.service.ts`
Running the tool
================
This example shows how to connect the tool to a gRPC service runnning
[channelz](https://github.com/grpc/proposal/blob/master/A14-channelz.md)
at `127.0.0.1:5001`. The local web server runs on port `8080`.
Tip: The `GOPATH` environment variable determines where `go get`
downloads its packages. This is useful if you would like the CLI
tool's dependencies to go to a specific directory.
```bash
$ cd cli
$ go get -u github.com/golang/protobuf/protoc-gen-go
$ go get -u google.golang.org/grpc
$ go run gateway.go -serverAddr=127.0.0.1:50051 -port=8080
```
Compile instructions for the web app
====================================
Normally, rebuilding the web app is not necessary. The compiled
javascript and HTML files are already present in the repo.
`npm` is required to be on your `PATH`. This code has been verified to
work with version `5.8.0` of `npm`.
To rebuild and copy the distributable files:
```bash
$ buildscripts/update_angular.sh
```

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -eux -o pipefail
readonly GRPC_ZPAGES_DIR="$(cd "$(dirname "$0")"/.. && pwd)"
cd $GRPC_ZPAGES_DIR/web/channelzui/
npm install
ng build --prod --build-optimizer --base-href=/dist_channelz/
rm $GRPC_ZPAGES_DIR/cli/dist_channelz/*
cp $GRPC_ZPAGES_DIR/web/channelzui/dist/* $GRPC_ZPAGES_DIR/cli/dist_channelz/

View File

@ -0,0 +1,45 @@
core-js@2.5.5
MIT
Copyright (c) 2014-2018 Denis Pushkarev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
zone.js@0.8.26
MIT
The MIT License
Copyright (c) 2016-2018 Google, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Channelz</title>
<base href="/dist_channelz/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.32f0d240d4c6d000c235.css"></head>
<body>
<app-root></app-root>
<script type="text/javascript" src="runtime.a66f828dca56eeb90e02.js"></script><script type="text/javascript" src="polyfills.7f9d473f1d5c840b0bca.js"></script><script type="text/javascript" src="main.5dde8b72505d5a34f8e5.js"></script></body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(r){function e(e){for(var t,p,c=e[0],a=e[1],f=e[2],l=0,s=[];l<c.length;l++)o[p=c[l]]&&s.push(o[p][0]),o[p]=0;for(t in a)Object.prototype.hasOwnProperty.call(a,t)&&(r[t]=a[t]);for(i&&i(e);s.length;)s.shift()();return u.push.apply(u,f||[]),n()}function n(){for(var r,e=0;e<u.length;e++){for(var n=u[e],t=!0,c=1;c<n.length;c++)0!==o[n[c]]&&(t=!1);t&&(u.splice(e--,1),r=p(p.s=n[0]))}return r}var t={},o={0:0},u=[];function p(e){if(t[e])return t[e].exports;var n=t[e]={i:e,l:!1,exports:{}};return r[e].call(n.exports,n,n.exports,p),n.l=!0,n.exports}p.m=r,p.c=t,p.d=function(r,e,n){p.o(r,e)||Object.defineProperty(r,e,{configurable:!1,enumerable:!0,get:n})},p.r=function(r){Object.defineProperty(r,"__esModule",{value:!0})},p.n=function(r){var e=r&&r.__esModule?function(){return r.default}:function(){return r};return p.d(e,"a",e),e},p.o=function(r,e){return Object.prototype.hasOwnProperty.call(r,e)},p.p="";var c=window.webpackJsonp=window.webpackJsonp||[],a=c.push.bind(c);c.push=e,c=c.slice();for(var f=0;f<c.length;f++)e(c[f]);var i=a;n()}([]);

View File

@ -0,0 +1 @@
body{margin:0;font-family:Roboto,sans-serif}

121
grpc-zpages/cli/gateway.go Normal file
View File

@ -0,0 +1,121 @@
package main
import (
"flag"
"fmt"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/channelz/grpc_channelz_v1"
"log"
"net/http"
"os"
"strings"
"time"
)
var marshaler = jsonpb.Marshaler{EmitDefaults: true, Indent: " "}
var unmarshaler = jsonpb.Unmarshaler{}
func printHelper(w http.ResponseWriter, pb proto.Message) {
w.WriteHeader(http.StatusOK)
json, _ := marshaler.MarshalToString(pb)
fmt.Fprintf(w, "%s", json)
}
func errorHelper(w http.ResponseWriter, errstr string) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "%s", errstr)
}
func main() {
var serverAddr = flag.String("serverAddr", "", "the target server address")
var port = flag.Int("port", 8080, "local port")
flag.Parse()
fmt.Println(os.Stdout, "Address: %s", *serverAddr)
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial(*serverAddr, opts...)
defer conn.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to %s\n", serverAddr)
}
client := grpc_channelz_v1.NewChannelzClient(conn)
mux := http.NewServeMux()
mux.HandleFunc("/grpcz_internal", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
json := strings.NewReader(q["request"][0])
switch method := q["full_method_name"][0]; method {
case "grpc.channelz.v1.Channelz/GetServers":
req := grpc_channelz_v1.GetServersRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetServers(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
case "grpc.channelz.v1.Channelz/GetServerSockets":
req := grpc_channelz_v1.GetServerSocketsRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetServerSockets(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
case "grpc.channelz.v1.Channelz/GetTopChannels":
req := grpc_channelz_v1.GetTopChannelsRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetTopChannels(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
case "grpc.channelz.v1.Channelz/GetSubchannel":
req := grpc_channelz_v1.GetSubchannelRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetSubchannel(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
case "grpc.channelz.v1.Channelz/GetChannel":
req := grpc_channelz_v1.GetSubchannelRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetSubchannel(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
case "grpc.channelz.v1.Channelz/GetSocket":
req := grpc_channelz_v1.GetSocketRequest{}
unmarshaler.Unmarshal(json, &req)
response, err := client.GetSocket(ctx, &req)
if err != nil {
errorHelper(w, err.Error())
return
}
printHelper(w, response)
default:
errorHelper(w, "Invalid method")
}
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if (strings.HasSuffix(r.URL.Path, ".css") || strings.HasSuffix(r.URL.Path, ".js")) {
http.ServeFile(w, r, r.URL.Path[1:])
} else {
http.ServeFile(w, r, "dist_channelz/index.html")
}
})
fmt.Printf("zpages now serving at: http://localhost:%d for %s\n", *port, *serverAddr)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), mux))
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,27 @@
# Channelzui
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.7.4.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,131 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"channelzui": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"showCircularDependencies": true,
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets",
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "channelzui:build"
},
"configurations": {
"production": {
"browserTarget": "channelzui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "channelzui:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.css"
],
"assets": [
"src/assets",
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"channelzui-e2e": {
"root": "",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "channelzui:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "channelzui",
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "css"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
}

View File

@ -0,0 +1,14 @@
import { AppPage } from './app.po';
describe('channelzui App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!');
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,33 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

11840
grpc-zpages/web/channelzui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
{
"name": "channelzui",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/common": "6.0.3",
"@angular/compiler": "6.0.3",
"@angular/core": "6.0.3",
"@angular/forms": "6.0.3",
"@angular/http": "6.0.3",
"@angular/platform-browser": "6.0.3",
"@angular/platform-browser-dynamic": "6.0.3",
"@angular/router": "6.0.3",
"core-js": "^2.4.1",
"rxjs": "^6.2.0",
"ts-loader": "^4.1.0",
"webpack": "^4.3.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.6.6",
"@angular/cli": "6.0.7",
"@angular/compiler-cli": "6.0.3",
"@angular/language-service": "6.0.3",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "^6.0.105",
"codelyzer": "^4.0.1",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "2.7.2"
}
}

View File

@ -0,0 +1,28 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,36 @@
licenses(["notice"]) # Apache 2.0
package(default_visibility = ["//visibility:public"])
load("//javascript/angular2:build_defs.bzl", "ng_module")
ng_module(
name = "app",
srcs = glob(["**/*.ts"]),
assets = glob(["**/*.css"]) + glob(["**/*.html"]),
deps = [
"//third_party/javascript/angular2:common",
"//third_party/javascript/angular2:common_http",
"//third_party/javascript/angular2:core",
"//third_party/javascript/angular2:forms",
"//third_party/javascript/angular2:platform_browser",
"//third_party/javascript/angular2:router",
"//third_party/javascript/rxjs",
],
)
load("//javascript/typescript:build_defs.bzl", "ts_config")
ts_config(
name = "tsconfig",
deps = [
":app",
"//third_party/javascript/angular2:common",
"//third_party/javascript/angular2:common_http",
"//third_party/javascript/angular2:core",
"//third_party/javascript/angular2:forms",
"//third_party/javascript/angular2:platform_browser",
"//third_party/javascript/angular2:router",
"//third_party/javascript/rxjs",
],
)

View File

@ -0,0 +1,53 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ServersComponent } from './servers/servers.component';
import { SocketComponent } from './socket/socket.component';
import { TopChannelsComponent } from './top-channels/top-channels.component';
import { SubchannelComponent } from './subchannel/subchannel.component';
import { ChannelComponent } from './channel/channel.component';
import { ServerSocketsComponent } from './server-sockets/server-sockets.component';
import { HelpComponent } from './help/help.component';
const routes: Routes = [
{ path: 'channelz/servers/:startId', component: ServersComponent },
{ path: 'channelz/servers', component: ServersComponent },
{ path: 'channelz/servers/:startId', component: ServersComponent },
{ path: 'channelz/servers', component: ServersComponent },
{ path: 'channelz/topchannels/:startId', component: TopChannelsComponent },
{ path: 'channelz/topchannels', component: TopChannelsComponent },
{ path: 'channelz/socket/:id', component: SocketComponent},
{ path: 'channelz/socket', component: SocketComponent},
{ path: 'channelz/channel/:id', component: ChannelComponent },
{ path: 'channelz/channel', component: ChannelComponent },
{ path: 'channelz/subchannel/:id', component: SubchannelComponent },
{ path: 'channelz/subchannel', component: SubchannelComponent },
{ path: 'channelz/serversockets/:serverId/:socketStartId', component: ServerSocketsComponent },
{ path: 'channelz/serversockets/:serverId', component: ServerSocketsComponent },
{ path: 'channelz/serversockets', component: ServerSocketsComponent },
{ path: 'channelz/help', component: HelpComponent },
{ path: 'channelz', redirectTo: '/', pathMatch: 'full' },
{ path: '', redirectTo: '/', pathMatch: 'full' },
];
@NgModule({
exports: [ RouterModule ],
imports: [ RouterModule.forRoot(routes) ],
})
export class AppRoutingModule {}

View File

@ -0,0 +1,11 @@
<h2>Channelz</h2>
<nav>
<a routerLink="/channelz/servers" routerLinkActive="active">Servers</a>&nbsp;&nbsp;
<a routerLink="/channelz/topchannels" routerLinkActive="active">TopChannels</a>&nbsp;&nbsp;
<a routerLink="/channelz/serversockets" routerLinkActive="active">Server sockets</a>&nbsp;&nbsp;
<a routerLink="/channelz/channel" routerLinkActive="active">Channel</a>&nbsp;&nbsp;
<a routerLink="/channelz/subchannel" routerLinkActive="active">Subchannel</a>&nbsp;&nbsp;
<a routerLink="/channelz/socket" routerLinkActive="active">Socket</a>&nbsp;&nbsp;
<a routerLink="/channelz/help" routerLinkActive="active">Help</a>&nbsp;&nbsp;
</nav>
<router-outlet></router-outlet>

View File

@ -0,0 +1,27 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'Channelz';
}

View File

@ -0,0 +1,60 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { ServersComponent } from './servers/servers.component';
import { SocketComponent } from './socket/socket.component';
import { ChannelComponent } from './channel/channel.component';
import { ChannelzService } from './channelz.service';
import { HelpComponent } from './help/help.component';
import { JsonPretty, AddrPretty } from './utils';
import { ServerSocketsComponent } from './server-sockets/server-sockets.component';
import { SubchannelComponent } from './subchannel/subchannel.component';
import { TopChannelsComponent } from './top-channels/top-channels.component';
@NgModule({
declarations: [
AppComponent,
AddrPretty,
ChannelComponent,
HelpComponent,
JsonPretty,
ServerSocketsComponent,
ServersComponent,
SocketComponent,
SubchannelComponent,
TopChannelsComponent,
],
imports: [
AppRoutingModule,
BrowserModule,
CommonModule,
FormsModule,
HttpClientModule,
],
providers: [ChannelzService],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,48 @@
<h3>Showing channel:</h3>
<input #textbox type="text" [(ngModel)]="enteredData" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<div *ngIf="channel">
<table>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
<tr>
<td>ChannelRef</td>
<td><pre>{{channel['ref']['channelId']}}[{{channel['ref']['name']}}]</pre></td>
</tr>
<tr>
<td>Data</td>
<td><pre>{{channel['data'] | jsonpretty}}</pre></td>
</tr>
<tr>
<td>Channels</td>
<td>
<div *ngFor="let ref of channel['channelRef']">
<a routerLink="/channelz/channel/{{ref['channelId']}}">{{ref['channelId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
<tr>
<td>Subchannels</td>
<td>
<div *ngFor="let ref of channel['subchannelRef']">
<a routerLink="/channelz/subchannel/{{ref['subchannelId']}}">{{ref['subchannelId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
<tr>
<td>Sockets</td>
<td>
<div *ngFor="let ref of channel['socketRef']">
<a routerLink="/channelz/socket/{{ref['socketId']}}">{{ref['socketId']}}[{{ref.name}}]</a>,
</div>
</td>
</tr>
</table>
</div>
<div *ngIf="!channel && id">
<p>Channel does not exist</p>
</div>
</div>

View File

@ -0,0 +1,58 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChannelzService } from '../channelz.service';
@Component({
selector: 'app-channel',
templateUrl: './channel.component.html',
styleUrls: ['./channel.component.css']
})
export class ChannelComponent implements OnInit {
enteredData: string = "0";
channel: any;
id: number = 0;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl('/channelz/channel/' + this.enteredData);
}
private render(): void {
const idParam = this.route.snapshot.paramMap.get('id');
this.id = idParam == null ? 0 : +idParam;
this.enteredData = this.id.toString();
this.channelzService.getChannel(this.id)
.subscribe((response: any) => this.handleResponse(response));
}
private handleResponse(resp: any): void {
this.channel = resp['channel'];
}
}

View File

@ -0,0 +1,83 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { HttpClient } from "@angular/common/http";
import { map } from "rxjs/operators";
@Injectable()
export class ChannelzService {
baseUrl = "/grpcz_internal";
constructor(private http: HttpClient) {}
private makeUrl(method: string, req: {}): string {
const encodedMethod = encodeURIComponent(method);
const encodedReq = encodeURIComponent(JSON.stringify(req));
return `${this.baseUrl}?full_method_name=${encodedMethod}&request=${encodedReq}`;
}
getHelper(url: string) {
console.log("getting: " + url);
return this.http.get(url, {responseType: "text"})
.pipe(map((buf: string) => {
console.log(`Received: ${buf}`);
// TODO: only parse if 200, otherwise put error into UI
const json = JSON.parse(buf);
console.log(`Parsed into: ${json}`);
return json;
}));
}
getServers(startId: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetServers", {"startServerId": startId});
return this.getHelper(url);
}
getServerSockets(serverId: number, startId: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetServerSockets",
{"serverId": serverId, "startSocketId": startId});
return this.getHelper(url);
}
getTopChannels(startId: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetTopChannels", {"startChannelId": startId});
return this.getHelper(url);
}
getSubchannel(id: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetSubchannel", {"subchannelId": id});
return this.getHelper(url);
}
getChannel(id: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetChannel", {"channelId": id});
return this.getHelper(url);
}
getSocket(id: number): Observable<{}> {
const url = this.makeUrl(
"grpc.channelz.v1.Channelz/GetSocket", {"socketId": id});
return this.getHelper(url);
}
}

View File

@ -0,0 +1,10 @@
<div>
<h2>Java notes:</h2>
<ul>
<li>InProcessTransport is not a real socket, so in process servers and channels will
not provide socket level stats. If you try to load an InProcesTransport, channelz will
report that the socket is not found. This is normal. Use the provided channel, subchannel, or
server level stats instead.</li>
<li>Not all stats specified by channelz.proto are exported yet, but they will be soon.</li>
</ul>
</div>

View File

@ -0,0 +1,30 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.css']
})
export class HelpComponent implements OnInit {
constructor() { }
ngOnInit() {}
}

View File

@ -0,0 +1,24 @@
<h3>Showing server sockets starting from:</h3>
Server id:
<input #serverIdTextBox type="text" [(ngModel)]="serverId" (keyup.enter)="processEntry()" required>
Starting socket:
<input #socketStartIdTextBox type="text" [(ngModel)]="socketStartId" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<table>
<tr>
<th>Sockets</th>
</tr>
<tr *ngFor="let s of sockets">
<td>
<a routerLink="/channelz/socket/{{s['socketId']}}">{{s['socketId']}}[{{s['name']}}]</a>
</td>
</tr>
</table>
</div>
<div *ngIf="nextSocketId">
<a routerLink="/channelz/serversockets/{{serverId}}/{{nextSocketId}}">Click here for next page</a>
</div>
<div *ngIf="!nextSocketId">
<p>End of results</p>
</div>

View File

@ -0,0 +1,64 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChannelzService } from '../channelz.service';
@Component({
selector: 'app-server-sockets',
templateUrl: './server-sockets.component.html',
styleUrls: ['./server-sockets.component.css']
})
export class ServerSocketsComponent implements OnInit {
sockets: any[];
serverId: number;
socketStartId: number;
nextSocketId: number;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService
) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl(`/channelz/serversockets/${this.serverId}/${this.socketStartId}`);
}
private render(): void {
const serverIdParam = this.route.snapshot.paramMap.get('serverId');
this.serverId = serverIdParam == null ? 0 : +serverIdParam;
const socketStartIdParam = this.route.snapshot.paramMap.get('socketStartId');
this.socketStartId = socketStartIdParam == null ? 0 : +socketStartIdParam;
this.channelzService.getServerSockets(this.serverId, this.socketStartId)
.subscribe((resp: any) => this.handleResponse(resp));
}
private handleResponse(resp: any): void {
this.sockets = resp['socketRef'];
if (!resp['end']) {
const last = this.sockets[this.sockets.length - 1];
this.nextSocketId = last['socketId'];
}
}
}

View File

@ -0,0 +1,34 @@
<h3>Showing servers starting from:</h3>
Server id:
<input #textbox type="text" [(ngModel)]="startId" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<table>
<tr>
<th>Server</th>
<th>Calls started</th>
<th>Calls succeeded</th>
<th>Calls failed</th>
<th>Last call started ts</th>
<th>Listen Sockets</th>
</tr>
<tr *ngFor="let server of serversList">
<td><a routerLink="/channelz/serversockets/{{server['ref']['serverId']}}">{{server['ref']['serverId']}}[{{server['ref']['name']}}]</a></td>
<td>{{server['data']['callsStarted']}}</td>
<td>{{server['data']['callsSucceeded']}}</td>
<td>{{server['data']['callsFailed']}}</td>
<td>{{server['data']['lastCallStartedTimestamp'] | json}}</td>
<td>
<div *ngFor="let lsock of server['listenSocket']">
<a routerLink="/channelz/socket/{{lsock['socketId']}}">{{lsock['socketId']}}[{{lsock['name']}}]</a>
</div>
</td>
</tr>
</table>
</div>
<div *ngIf="nextId">
<a routerLink="/channelz/servers/{{nextId}}">Click here for next page</a>
</div>
<div *ngIf="!nextId">
<p>End of results</p>
</div>

View File

@ -0,0 +1,59 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import { ChannelzService } from '../channelz.service';
@Component({
selector: 'app-servers',
templateUrl: './servers.component.html',
styleUrls: ['./servers.component.css']
})
export class ServersComponent implements OnInit {
startId: number = 0;
serversList: any[];
nextId: string;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl(`/channelz/servers/${this.startId}`);
}
private render(): void {
const startIdParam = this.route.snapshot.paramMap.get('startId');
this.startId = startIdParam == null ? 0 : +startIdParam;
this.channelzService.getServers(this.startId)
.subscribe((resp: any) => this.handleResponse(resp));
}
private handleResponse(resp: any): void {
this.serversList = resp['server'];
if (!resp['end']) {
const last = this.serversList[this.serversList.length - 1];
this.nextId = last['ref']['serverId'];
}
}
}

View File

@ -0,0 +1,41 @@
<h3>Showing socket:</h3>
<input #textbox type="text" [(ngModel)]="enteredData" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<div *ngIf="socket">
<table>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
<tr>
<td>SocketRef</td>
<td><pre>{{socket['ref']['socketId']}}[{{socket['ref']['name']}}]</pre></td>
</tr>
<tr>
<td>Local Address</td>
<td><pre>{{socket['local'] | addrpretty}}</pre></td>
</tr>
<tr>
<td>Remote Address</td>
<td><pre>{{socket['remote'] | addrpretty}}</pre></td>
</tr>
<tr>
<td>Security</td>
<td><pre>{{socket['security'] | jsonpretty}}</pre></td>
</tr>
<tr>
<td>Data</td>
<td><pre>{{simplifiedData | jsonpretty}}</pre></td>
</tr>
<tr *ngFor="let opt of socketOptions()">
<td>Socket Option: {{opt['name']}}</td>
<!-- opt is an typescript object, and for some reason, bracket notation does not work here -->
<td><pre>{{opt.value | jsonpretty}}</pre></td>
</tr>
</table>
</div>
<div *ngIf="!socket">
<p>Socket does not exist</p>
</div>
</div>

View File

@ -0,0 +1,90 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChannelzService } from '../channelz.service';
import { JsonPretty } from '../utils'
export class SocketOpt {
constructor(public name: string, public value: string) {}
}
@Component({
selector: 'app-socket',
templateUrl: './socket.component.html',
styleUrls: ['./socket.component.css']
})
export class SocketComponent implements OnInit {
enteredData: string;
socket: any;
id: number;
simplifiedData: any;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl(`/channelz/socket/${this.enteredData}`);
}
private render(): void {
const idParam = this.route.snapshot.paramMap.get('id');
this.id = idParam == null ? 0 : +idParam;
this.enteredData = this.id.toString();
this.channelzService.getSocket(+this.id)
.subscribe((response: any) => this.handleResponse(response));
}
private handleResponse(socketResponse: any): void {
this.socket = socketResponse['socket'];
this.simplifiedData = this.simplifiedDataHelper();
}
/**
* Returns a copy of the socket data, but with socket options removed.
*/
simplifiedDataHelper(): any {
if (this.socket['data'] == null) {
return {};
}
const copy: any = JSON.parse(JSON.stringify(this.socket.data));
copy['option'] = null;
return copy;
}
socketOptions(): SocketOpt[] {
if (this.socket['data'] == null || this.socket['data']['option'] == null) {
return [];
}
return this.socket['data']['option'].map((opt: any) => {
// value OR additional is set
if (opt['value']) {
return new SocketOpt(opt['name'], opt['value']);
} else {
return new SocketOpt(opt['name'], opt['additional']);
}
});
}
}

View File

@ -0,0 +1,48 @@
<h3>Showing subchannel:</h3>
<input #textbox type="text" [(ngModel)]="enteredData" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<div *ngIf="subchannel">
<table>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
<tr>
<td>SubchannelRef</td>
<td><pre>{{subchannel['ref']['subchannelId']}}[{{subchannel['ref']['name']}}]</pre></td>
</tr>
<tr>
<td>Data</td>
<td><pre>{{subchannel['data'] | jsonpretty}}</pre></td>
</tr>
<tr>
<td>Channels</td>
<td>
<div *ngFor="let ref of subchannel['channelRef']">
<a routerLink="/channelz/channel/{{ref['channelId']}}">{{ref['channelId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
<tr>
<td>Subchannels</td>
<td>
<div *ngFor="let ref of subchannel['subchannelRef']">
<a routerLink="/channelz/subchannel/{{ref['channelId']}}">{{ref['subchannelId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
<tr>
<td>Sockets</td>
<td>
<div *ngFor="let ref of subchannel['socketRef']">
<a routerLink="/channelz/socket/{{ref['socketId']}}">{{ref['socketId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
</table>
</div>
<div *ngIf="!subchannel && id">
<p>Subchannel does not exist</p>
</div>
</div>

View File

@ -0,0 +1,57 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChannelzService } from '../channelz.service';
@Component({
selector: 'app-subchannel',
templateUrl: './subchannel.component.html',
styleUrls: ['./subchannel.component.css']
})
export class SubchannelComponent implements OnInit {
enteredData: string;
subchannel: any;
id: number;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl('/channelz/subchannel/' + this.enteredData);
}
private render(): void {
const idParam = this.route.snapshot.paramMap.get('id');
this.id = idParam == null ? 0 : +idParam;
this.enteredData = this.id.toString();
this.channelzService.getSubchannel(this.id)
.subscribe((response: any) => this.handleResponse(response));
}
private handleResponse(resp: any): void {
this.subchannel = resp['subchannel'];
}
}

View File

@ -0,0 +1,39 @@
<h3>Showing top level channels starting from:</h3>
<input #textbox type="text" [(ngModel)]="startId" (keyup.enter)="processEntry()" required>
<button (click)="processEntry()">Refresh</button>
<div>
<table>
<tr>
<th>Channel</th>
<th>Data</th>
<th>Channels</th>
<th>Subchannels</th>
<th>Sockets</th>
</tr>
<tr *ngFor="let c of topChannelsList">
<td>{{c['ref']['channelId']}}[{{c['ref']['name']}}]</td>
<td><pre>{{c.data | jsonpretty}}</pre></td>
<td>
<div *ngFor="let ref of c['channelRef']">
<a routerLink="/channelz/channel/{{ref['channelId']}}">{{ref['channelId']}}[{{ref['name']}}]</a>,
</div>
</td>
<td>
<div *ngFor="let ref of c['subchannelRef']">
<a routerLink="/channelz/subchannel/{{ref['subchannelId']}}">{{ref['subchannelId']}}[{{ref['name']}}]</a>,
</div>
</td>
<td>
<div *ngFor="let ref of c['socketRef']">
<a routerLink="/channelz/socket/{{ref['socketId']}}">{{ref['socketId']}}[{{ref['name']}}]</a>,
</div>
</td>
</tr>
</table>
</div>
<div *ngIf="nextId">
<a routerLink="/channelz/topchannels/{{nextId}}">Click here for next page</a>
</div>
<div *ngIf="!nextId">
<p>End of results</p>
</div>

View File

@ -0,0 +1,60 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChannelzService } from '../channelz.service';
@Component({
selector: 'app-top-channels',
templateUrl: './top-channels.component.html',
styleUrls: ['./top-channels.component.css']
})
export class TopChannelsComponent implements OnInit {
startId: number = 0;
topChannelsList: any[];
nextId:number;
constructor(
private route: ActivatedRoute,
private router: Router,
private channelzService: ChannelzService) { }
ngOnInit() {
this.render();
this.route.params.subscribe(e => this.render());
}
processEntry(): void {
this.router.navigateByUrl(`/channelz/topchannels/${this.startId}`)
}
private render(): void {
const startIdParam = this.route.snapshot.paramMap.get('startId');
this.startId = startIdParam == null ? 0 : +startIdParam;
this.channelzService.getTopChannels(this.startId)
.subscribe((resp: any) => this.handleResponse(resp));
}
private handleResponse(resp: any): void {
this.topChannelsList = resp['channel'];
if (!resp['end']) {
const last = this.topChannelsList[this.topChannelsList.length - 1];
this.nextId = last['ref']['channelId'];
}
}
}

View File

@ -0,0 +1,67 @@
/*
* @license
* Copyright 2018, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({name: 'jsonpretty'})
export class JsonPretty implements PipeTransform {
transform(value: any): string {
return JsonPretty.transform(value);
}
static transform(value: any): string {
if (value == null) {
return "NULL";
}
return JSON.stringify(value, null, 2);
}
}
@Pipe({name: 'addrpretty'})
export class AddrPretty implements PipeTransform {
transform(value: any): string {
return AddrPretty.transform(value);
}
static transform(addr: {}): string {
if (addr['tcpipAddress'] == null) {
return JsonPretty.transform(addr);
}
const copy = JSON.parse(JSON.stringify(addr));
const tcpIp = copy['tcpipAddress'];
const ipAddress = tcpIp['ipAddress'];
const binary = atob(ipAddress);
const addressLen = binary.length;
if (binary.length === 4) {
// ipv4: turn bytes into decimal form
tcpIp['ipAddress'] = binary
.split("")
.map((c: string) => c.charCodeAt(0))
.join(".");
} else if (binary.length === 16) {
// ipv6: turn bytes into hex, pad with a 0 so always 2 digits long
const hex = binary.split("")
.map((c: string) => ("0" + c.charCodeAt(0).toString(16)).substr(-2))
.join("");
// use regex to split into 4 character chunks
tcpIp['ipAddress'] = hex.match(/..../g).join(":");
}
return JsonPretty.transform(copy);
}
}

View File

@ -0,0 +1,412 @@
syntax = "proto3";
package grpc.channelz;
import "google/protobuf/any.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
option java_multiple_files = true;
option java_package = "io.grpc.channelz.v1";
option java_outer_classname = "ChannelzProto";
// Channel is a logical grouping of channels, subchannels, and sockets.
message Channel {
// The identifier for this channel.
ChannelRef ref = 1;
// Data specific to this channel.
ChannelData data = 2;
// At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
// There are no ordering guarantees on the order of channel refs.
// There may not be cycles in the ref graph.
// A channel ref may be present in more than one channel or subchannel.
repeated ChannelRef channel_ref = 3;
// At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
// There are no ordering guarantees on the order of subchannel refs.
// There may not be cycles in the ref graph.
// A sub channel ref may be present in more than one channel or subchannel.
repeated SubchannelRef subchannel_ref = 4;
// There are no ordering guarantees on the order of sockets.
repeated SocketRef socket_ref = 5;
}
// Subchannel is a logical grouping of channels, subchannels, and sockets.
// A subchannel is load balanced over by it's ancestor
message Subchannel {
// The identifier for this channel.
SubchannelRef ref = 1;
// Data specific to this channel.
ChannelData data = 2;
// At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
// There are no ordering guarantees on the order of channel refs.
// There may not be cycles in the ref graph.
// A channel ref may be present in more than one channel or subchannel.
repeated ChannelRef channel_ref = 3;
// At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
// There are no ordering guarantees on the order of subchannel refs.
// There may not be cycles in the ref graph.
// A sub channel ref may be present in more than one channel or subchannel.
repeated SubchannelRef subchannel_ref = 4;
// There are no ordering guarantees on the order of sockets.
repeated SocketRef socket_ref = 5;
}
message ChannelData {
enum State {
UNKNOWN = 0;
IDLE = 1;
CONNECTING = 2;
READY = 3;
TRANSIENT_FAILURE = 4;
SHUTDOWN = 5;
}
State state = 1;
// The target this channel originally tried to connect to. May be absent
string target = 2;
ChannelTrace trace = 3;
// The number of calls started on the channel
int64 calls_started = 4;
// The number of calls that have completed with an OK status
int64 calls_succeeded = 5;
// The number of calls that have a completed with a non-OK status
int64 calls_failed = 6;
// The last time a call was started on the channel.
google.protobuf.Timestamp last_call_started_timestamp = 7;
}
message ChannelTrace {
// TODO: fill this in.
}
message ChannelRef {
// The globally unique id for this channel. Must be a positive number.
int64 channel_id = 1;
// An optional name associated with the channel.
string name = 2;
// Intentionally don't use field numbers from other refs.
reserved 3, 4, 5, 6;
}
message SubchannelRef {
// The globally unique id for this subchannel. Must be a positive number.
int64 subchannel_id = 7;
// An optional name associated with the subchannel.
string name = 8;
// Intentionally don't use field numbers from other refs.
reserved 1, 2, 3, 4, 5, 6;
}
message SocketRef {
int64 socket_id = 3;
// An optional name associated with the socket.
string name = 4;
// Intentionally don't use field numbers from other refs.
reserved 1, 2, 5, 6, 7, 8;
}
message ServerRef {
// A globally unique identifier for this server. Must be a positive number.
int64 server_id = 5;
// An optional name associated with the server.
string name = 6;
// Intentionally don't use field numbers from other refs.
reserved 1, 2, 3, 4, 7, 8;
}
message Server {
ServerRef ref = 1;
ServerData data = 2;
// The sockets that the server is listening on. There are no ordering
// guarantees.
repeated SocketRef listen_socket = 3;
}
message ServerData {
ServerChannelTrace trace = 1;
// The number of incoming calls started on the server
int64 calls_started = 2;
// The number of incoming calls that have completed with an OK status
int64 calls_succeeded = 3;
// The number of incoming calls that have a completed with a non-OK status
int64 calls_failed = 4;
// The last time a call was started on the server.
google.protobuf.Timestamp last_call_started_timestamp = 5;
}
message ServerChannelTrace {
// TODO: fill this in.
}
// Information about an actual connection. Pronounced "sock-ay".
message Socket {
SocketRef ref = 1;
SocketData data = 2;
// The locally bound address.
Address local = 3;
// The remote bound address. May be absent.
Address remote = 4;
Security security = 5;
// Optional, represents the name of the remote endpoint, if different than
// the original target name.
string remote_name = 6;
}
message SocketData {
// The number of streams that have been started.
int64 streams_started = 1;
// The number of streams that have ended successfully with the EoS bit set for
// both end points
int64 streams_succeeded = 2;
// The number of incoming streams that have a completed with a non-OK status
int64 streams_failed = 3;
// The number of messages successfully sent on this socket.
int64 messages_sent = 4;
int64 messages_received = 5;
// The number of keep alives sent. This is typically implemented with HTTP/2
// ping messages.
int64 keep_alives_sent = 6;
// The last time a stream was created by this endpoint. Usually unset for
// servers.
google.protobuf.Timestamp last_local_stream_created_timestamp = 7;
// The last time a stream was created by the remote endpoint. Usually unset
// for clients.
google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;
// The last time a message was sent by this endpoint.
google.protobuf.Timestamp last_message_sent_timestamp = 9;
// The last time a message was received by this endpoint.
google.protobuf.Timestamp last_message_received_timestamp = 10;
// The amount of window, granted to the local endpoint by the remote endpoint.
// This may be slightly out of date due to network latency. This does NOT
// include stream level or TCP level flow control info.
google.protobuf.Int64Value local_flow_control_window = 11;
// The amount of window, granted to the remote endpoint by the local endpoint.
// This may be slightly out of date due to network latency. This does NOT
// include stream level or TCP level flow control info.
google.protobuf.Int64Value remote_flow_control_window = 12;
repeated SocketOption option = 13;
}
message Address {
message TcpIpAddress {
// Either the IPv4 or IPv6 address in bytes. Will either be 4 bytes or 16
// bytes in length.
bytes ip_address = 1;
// 0-64k, or -1 if not appropriate.
int32 port = 2;
}
// A Unix Domain Socket address.
message UdsAddress {
string filename = 1;
}
// An address type not included above.
message OtherAddress {
// The human readable version of the value.
string name = 1;
// The actual address message.
google.protobuf.Any value = 2;
}
oneof address {
TcpIpAddress tcpip_address = 1;
UdsAddress uds_address = 2;
OtherAddress other_address = 3;
}
}
message Security {
message Tls {
oneof cipher_suite {
// The cipher suite name in the RFC 4346 format:
// https://tools.ietf.org/html/rfc4346#appendix-C
string standard_name = 1;
// Some other way to describe the cipher suite if
// the RFC 4346 name is not available.
string other_name = 2;
}
// the certificate used by this endpoint.
bytes local_certificate = 3;
// the certificate used by the remote endpoint.
bytes remote_certificate = 4;
}
message OtherSecurity {
// The human readable version of the value.
string name = 1;
// The actual security details message.
google.protobuf.Any value = 2;
}
oneof model {
Tls tls = 1;
OtherSecurity other = 2;
}
}
message SocketOption {
string name = 1;
// The human readable value of this socket option. At least one of value or
// additional will be set.
string value = 2;
// Additional data associated with the socket option. At least one of value
// or additional will be set.
google.protobuf.Any additional = 3;
}
// For use with SocketOption's additional field. This is primarily used for
// SO_RCVTIMEO and SO_SNDTIMEO
message SocketOptionTimeout {
google.protobuf.Duration duration = 1;
}
message SocketOptionLinger {
bool active = 1;
google.protobuf.Duration duration = 2;
}
// Tcp info for SOL_TCP, TCP_INFO
message SocketOptionTcpInfo {
uint32 tcpi_state = 1;
uint32 tcpi_ca_state = 2;
uint32 tcpi_retransmits = 3;
uint32 tcpi_probes = 4;
uint32 tcpi_backoff = 5;
uint32 tcpi_options = 6;
uint32 tcpi_snd_wscale = 7;
uint32 tcpi_rcv_wscale = 8;
uint32 tcpi_rto = 9;
uint32 tcpi_ato = 10;
uint32 tcpi_snd_mss = 11;
uint32 tcpi_rcv_mss = 12;
uint32 tcpi_unacked = 13;
uint32 tcpi_sacked = 14;
uint32 tcpi_lost = 15;
uint32 tcpi_retrans = 16;
uint32 tcpi_fackets = 17;
uint32 tcpi_last_data_sent = 18;
uint32 tcpi_last_ack_sent = 19;
uint32 tcpi_last_data_recv = 20;
uint32 tcpi_last_ack_recv = 21;
uint32 tcpi_pmtu = 22;
uint32 tcpi_rcv_ssthresh = 23;
uint32 tcpi_rtt = 24;
uint32 tcpi_rttvar = 25;
uint32 tcpi_snd_ssthresh = 26;
uint32 tcpi_snd_cwnd = 27;
uint32 tcpi_advmss = 28;
uint32 tcpi_reordering = 29;
}
service Channelz {
// Gets all root channels (e.g. channels the application has directly
// created). This does not include subchannels nor non-top level channels.
rpc GetTopChannels(GetTopChannelsRequest) returns (GetTopChannelsResponse);
// Gets all servers that exist in the process.
rpc GetServers(GetServersRequest) returns (GetServersResponse);
// Gets all server sockets that exist in the process.
rpc GetServerSockets(GetServerSocketsRequest) returns (GetServerSocketsResponse);
// Returns a single Channel, or else a NOT_FOUND code.
rpc GetChannel(GetChannelRequest) returns (GetChannelResponse);
// Returns a single Subchannel, or else a NOT_FOUND code.
rpc GetSubchannel(GetSubchannelRequest) returns (GetSubchannelResponse);
// Returns a single Socket or else a NOT_FOUND code.
rpc GetSocket(GetSocketRequest) returns (GetSocketResponse);
}
message GetServersRequest {
// start_server_id indicates that only servers at or above this id should be
// included in the results.
int64 start_server_id = 1;
}
message GetServersResponse {
// list of servers that the connection detail service knows about. Sorted in
// ascending server_id order.
repeated Server server = 1;
// If set, indicates that the list of servers is the final list. Requesting
// more servers will only return more if they are created after this RPC
// completes.
bool end = 2;
}
message GetServerSocketsRequest {
int64 server_id = 1;
// start_socket_id indicates that only sockets at or above this id should be
// included in the results.
int64 start_socket_id = 2;
}
message GetServerSocketsResponse {
// list of socket refs that the connection detail service knows about. Sorted in
// ascending socket_id order.
repeated SocketRef socket_ref = 1;
// If set, indicates that the list of sockets is the final list. Requesting
// more sockets will only return more if they are created after this RPC
// completes.
bool end = 2;
}
message GetTopChannelsRequest {
// start_channel_id indicates that only channels at or above this id should be
// included in the results.
int64 start_channel_id = 1;
}
message GetTopChannelsResponse {
// list of channels that the connection detail service knows about. Sorted in
// ascending channel_id order.
repeated Channel channel = 1;
// If set, indicates that the list of channels is the final list. Requesting
// more channels can only return more if they are created after this RPC
// completes.
bool end = 2;
}
message GetChannelRequest {
int64 channel_id = 1;
}
message GetChannelResponse {
Channel channel = 1;
}
message GetSubchannelRequest {
int64 subchannel_id = 1;
}
message GetSubchannelResponse {
Subchannel subchannel = 1;
}
message GetSocketRequest {
int64 socket_id = 1;
}
message GetSocketResponse {
Socket socket = 1;
}

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,8 @@
// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Channelz</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,19 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
// this file differs with internal, must use instead:
// import { AppModuleNgFactory } from './app/app.module.ngfactory';
// platformBrowser().bootstrapModuleFactory(AppModuleNgFactory)
// .catch((err: any) => console.log(err));
platformBrowserDynamic().bootstrapModule(AppModule)
.catch((err: any) => console.log(err));

View File

@ -0,0 +1,79 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1,7 @@
/* You can add global styles to this file, and also import other style files */
body {
margin: 0;
font-family: Roboto, sans-serif;
}

View File

@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,14 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"module": "es2015",
"types": ["node"],
"typeRoots": [ "../node_modules/@types" ]
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,20 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"module": "commonjs",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,5 @@
/* SystemJS module definition */
declare var module: NodeModule;
interface NodeModule {
id: string;
}

View File

@ -0,0 +1,19 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
}
}

View File

@ -0,0 +1,142 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}