Repository: lensesio/schema-registry-ui Branch: master Commit: edb2342b567d Files: 32 Total size: 135.2 KB Directory structure: gitextract__mhr_92n/ ├── .gitignore ├── README.md ├── docker/ │ ├── Caddyfile │ ├── Dockerfile │ ├── README.md │ └── run.sh ├── env.js ├── package.json ├── src/ │ ├── app.js │ ├── assets/ │ │ └── css/ │ │ └── styles.css │ ├── factories/ │ │ ├── avro4s-factory.js │ │ ├── env-factory.js │ │ ├── index.js │ │ ├── schema-registry-factory.js │ │ ├── toast-factory.js │ │ └── utils-factory.js │ ├── index.html │ └── schema-registry/ │ ├── config/ │ │ ├── config.controller.js │ │ └── config.html │ ├── export/ │ │ ├── export.controller.js │ │ └── export.html │ ├── home/ │ │ ├── home.controller.js │ │ └── home.html │ ├── index.js │ ├── list/ │ │ ├── list.controller.js │ │ └── list.html │ ├── new/ │ │ ├── new.controller.js │ │ └── new.html │ ├── pagination/ │ │ └── dirPaginationControlsTemplate.html │ └── view/ │ ├── view.controller.js │ └── view.html └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ bower_components/ node_modules/ dist/ .idea/ .DS_Store TODO npm-debug.log *.iml ================================================ FILE: README.md ================================================ # schema-registry-ui [![release](http://github-release-version.herokuapp.com/github/landoop/schema-registry-ui/release.svg?style=flat)](https://github.com/landoop/schema-registry-ui/releases/latest) [![docker](https://img.shields.io/docker/pulls/landoop/schema-registry-ui.svg?style=flat)](https://hub.docker.com/r/landoop/schema-registry-ui/) [![Join the chat at https://gitter.im/Landoop/support](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](https://gitter.im/Landoop/support?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This is a web tool for the [confluentinc/schema-registry](https://github.com/confluentinc/schema-registry) in order to create / view / search / evolve / view history & configure **Avro** schemas of your Kafka cluster. ## Live Demo [schema-registry-ui.demo.lenses.io](http://schema-registry-ui.demo.lenses.io) ## Prerequisites You will need schema-registry installed with CORS enabled. In order to enable CORS, add in `/opt/confluent-3.x.x/etc/schema-registry/schema-registry.properties` ``` access.control.allow.methods=GET,POST,PUT,OPTIONS access.control.allow.origin=* ``` And then restart the [schema-registry] service ##### Get the set up locally We also provide the schema-registry and schema-registry-ui as part of the [fast-data-dev](https://github.com/Landoop/fast-data-dev) docker image for local development setup that also gives all the relevant backends. Just run: ``` docker run -d --name=fast-data-dev -p 8081:8081 landoop/fast-data-dev ``` Checkout more about fast-data-dev docker container [here](https://github.com/Landoop/fast-data-dev) ## Running it via Docker To run it via the provided docker image: ``` docker pull landoop/schema-registry-ui docker run --rm -p 8000:8000 \ -e "SCHEMAREGISTRY_URL=http://confluent-schema-registry-host:port" \ landoop/schema-registry-ui ``` Please see the [docker readme](https://github.com/Landoop/schema-registry-ui/tree/master/docker) for more information and how to enable various features or avoid CORS issues via the proxy flag. ## Build from source ``` git clone https://github.com/Landoop/schema-registry-ui.git cd schema-registry-ui npm install npm start ``` Web UI will be available at `http://localhost:8080` ### Nginx config If you use `nginx` to serve this ui, let angular manage routing with ``` location / { try_files $uri $uri/ /index.html =404; root /folder-with-schema-registry-ui/; } ``` ### Setup Schema Registry clusters Use multiple schema registry clusters in `env.js` : ``` var clusters = [ { NAME:"prod", // Schema Registry service URL (i.e. http://localhost:8081) SCHEMA_REGISTRY: "http://localhost:8081", // https://schema-registry.demo.landoop.com COLOR: "#141414", // optional readonlyMode: true // optional }, { NAME:"dev", SCHEMA_REGISTRY: "http://localhost:8383", COLOR: "red", // optional allowGlobalConfigChanges: true, // optional //allowTransitiveCompatibilities: true // if using a Confluent Platform release >= 3.1.1 uncomment this line } ]; ``` * Use `COLOR` to set different header colors for each set up cluster. * Use `allowGlobalConfigChanges` to enable configuring Global Compatibility Level from the UI. * Use `allowTransitiveCompatibilities` to enable transitive compatibility levels. This is supported in SR >= 3.1.1 * Use `allowSchemaDeletion` to enable schema deletion from the UI. This is supported in SR >= 3.3.0 * Use `readonlyMode` to prevent any configuration or schema changes from the UI. It overwrites the previous parameters (`allowGlobalConfigChanges`, `allowSchemaDeletion`). ## Changelog [Here](https://github.com/Landoop/schema-registry-ui/wiki/Changelog) ## License The project is licensed under the [BSL](http://www.landoop.com/bsl) license. ## Relevant Projects * [kafka-topics-ui](https://github.com/Landoop/kafka-topics-ui), UI to browse Kafka data and work with Kafka Topics * [kafka-connect-ui](https://github.com/Landoop/kafka-connect-ui), Set up and manage connectors for multiple connect clusters * [fast-data-dev](https://github.com/Landoop/fast-data-dev), Docker for Kafka developers (schema-registry,kafka-rest,zoo,brokers,landoop) * [Landoop-On-Cloudera](https://github.com/Landoop/Landoop-On-Cloudera), Install and manage your kafka streaming-platform on you Cloudera CDH cluster www.landoop.com ================================================ FILE: docker/Caddyfile ================================================ 0.0.0.0:8000 tls off root /schema-registry-ui log stdout ================================================ FILE: docker/Dockerfile ================================================ FROM alpine MAINTAINER Marios Andreopoulos WORKDIR / # Add needed tools RUN apk add --no-cache ca-certificates wget \ && echo "progress = dot:giga" | tee /etc/wgetrc # Add and Setup Caddy webserver RUN wget "https://github.com/mholt/caddy/releases/download/v0.10.11/caddy_v0.10.11_linux_amd64.tar.gz" -O /caddy.tgz \ && mkdir caddy \ && tar xzf caddy.tgz -C /caddy --no-same-owner \ && rm -f /caddy.tgz # Add and Setup Schema-Registry-Ui ENV SCHEMA_REGISTRY_UI_VERSION="0.9.5" RUN wget "https://github.com/Landoop/schema-registry-ui/releases/download/v.${SCHEMA_REGISTRY_UI_VERSION}/schema-registry-ui-${SCHEMA_REGISTRY_UI_VERSION}.tar.gz" \ -O /schema-registry-ui.tar.gz \ && mkdir /schema-registry-ui \ && tar xzf /schema-registry-ui.tar.gz -C /schema-registry-ui --no-same-owner \ && rm -f /schema-registry-ui.tar.gz \ && rm -f /schema-registry-ui/env.js \ && ln -s /tmp/env.js /schema-registry-ui/env.js # Add configuration and runtime files ADD Caddyfile /caddy/Caddyfile.template ADD run.sh / RUN chmod +x /run.sh EXPOSE 8000 # USER nobody:nogroup ENTRYPOINT ["/run.sh"] ================================================ FILE: docker/README.md ================================================ ## Schema Registry UI ## [![](https://images.microbadger.com/badges/image/landoop/schema-registry-ui.svg)](http://microbadger.com/images/landoop/schema-registry-ui) This is a small docker image for Landoop's schema-registry-ui. It serves the schema-registry-ui from port 8000 by default. A live version can be found at The software is stateless and the only necessary option is your Schema Registry URL. To run it: docker run --rm -p 8000:8000 \ -e "SCHEMAREGISTRY_URL=http://schema.registry.url" \ landoop/schema-registry-ui Visit http://localhost:8000 to see the UI. ### Advanced Settings Four of the Schema Registry UI settings need to be enabled explicitly. These are: 1. Support for global compatibility level configuration support —i.e change the default compatibility level of your schema registry. 2. Support for transitive compatibility levels (Schema Registry version 3.1.1 or better). 3. Support for Schema deletion (Schema Registry version 3.3.0 or better). 4. Support for readonly mode (overwrites settings for global compatibility configuration and schema deletion) They are handled by the `ALLOW_GLOBAL`, `ALLOW_TRANSITIVE`, `ALLOW_DELETION` and `READONLY_MODE` environment variables. E.g: docker run --rm -p 8000:8000 \ -e "SCHEMAREGISTRY_URL=http://schema.registry.url" \ -e ALLOW_GLOBAL=1 \ -e ALLOW_TRANSITIVE=1 \ -e ALLOW_DELETION=1 \ -e READONLY_MODE=1 \ landoop/schema-registry-ui docker run --rm -p 8000:8000 \ -e "SCHEMAREGISTRY_URL=http://schema.registry.url" \ -e READONLY_MODE=1 \ landoop/schema-registry-ui ### Proxying Schema Registry If you have CORS issues or want to pass through firewalls and maybe share your server, we added the `PROXY` option. Run the container with `-e PROXY=true` and Caddy server will proxy the traffic to Schema Registry: docker run --rm -p 8000:8000 \ -e "SCHEMAREGISTRY_URL=http://schema.registry.url" \ -e "PROXY=true" \ landoop/schema-registry-ui > **Important**: When proxying, for the `SCHEMAREGISTRY_URL` you have to use an > IP address or a domain that can be resolved to it. **You can't use** > `localhost` even if you serve Schema Registry from your localhost. The reason > for this is that a docker container has its own network, so your _localhost_ > is different from the container's _localhost_. As an example, if you are in > your home network and have an IP address of `192.168.5.65` and run Schema > Registry from your computer, instead of `http://127.0.0.1:8082` you must use > `http://192.168.5.65:8082`. If your Schema Registry uses self-signed SSL certificates, you can use the `PROXY_SKIP_VERIFY=true` environment variable to instruct the proxy to not verify the backend TLS certificate. ## Configuration options ### Schema Registry UI You can control most of Kafka Topics UI settings via environment variables: * `SCHEMAREGISTRY_URL` * `ALLOW_GLOBAL=[true|false]` (default false) * `ALLOW_TRANSITIVE=[true|false]` (default false) * `ALLOW_DELETION=[true|false]` (default false). * `READONLY_MODE=[true|false]` (default false). ## Docker Options - `PROXY=[true|false]` Whether to proxy Schema Registry endpoint via the internal webserver - `PROXY_SKIP_VERIFY=[true|false]` Whether to accept self-signed certificates when proxying Schema Registry via https - `PORT=[PORT]` The port number to use for schema-registry-ui. The default is `8000`. Usually the main reason for using this is when you run the container with `--net=host`, where you can't use docker's publish flag (`-p HOST_PORT:8000`). - `CADDY_OPTIONS=[OPTIONS]` The webserver that powers the image is Caddy. Via this variable you can add options that will be appended to its configuration (Caddyfile). Variables than span multiple lines are supported. As an example, you can set Caddy to not apply timeouts via: -e "CADDY_OPTIONS=timeouts none" Or you can set basic authentication via: -e "CADDY_OPTIONS=basicauth / [USER] [PASS]" - `RELATIVE_PROXY_URL=[true|false]` When proxying Schema Registry, enabling this option will set the Schema Registry endpoint in the UI as a relative URL. This can help when running Schema Registry UI under a subpath of your server (e.g `http://url:8000/sr-ui` instead of `http://url:8000/`). # Schema Registry Configuration If you don't wish to proxy Schema Registry's api, you should permit CORS via setting `access.control.allow.methods=GET,POST,PUT,DELETE,OPTIONS` and `access.control.allow.origin=*`. # Logging In the latest iterations, the container will print informational messages during startup at stderr and web server logs at stdout. This way you may sent the logs (stdout) to your favorite log management solution. ================================================ FILE: docker/run.sh ================================================ #!/bin/sh PROXY_SKIP_VERIFY="${PROXY_SKIP_VERIFY:-false}" INSECURE_PROXY="" ALLOW_GLOBAL="${ALLOW_GLOBAL:-false}" ALLOW_TRANSITIVE="${ALLOW_TRANSITIVE:-false}" ALLOW_DELETION="${ALLOW_DELETION:-false}" READONLY_MODE="${READONLY_MODE:-false}" CADDY_OPTIONS="${CADDY_OPTIONS:-}" RELATIVE_PROXY_URL="${RELATIVE_PROXY_URL:-false}" PORT="${PORT:-8000}" { echo "Landoop Schema Registry UI ${SCHEMA_REGISTRY_UI_VERSION}" echo "Visit " echo "to find more about how you can configure this container." echo if echo "$PROXY_SKIP_VERIFY" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then INSECURE_PROXY=insecure_skip_verify echo "Unsecure: won't verify proxy certicate chain." fi # fix for certain installations cat /caddy/Caddyfile.template \ | sed -e "s/8000/$PORT/" > /tmp/Caddyfile if echo $PROXY | egrep -sq "true|TRUE|y|Y|yes|YES|1" \ && [[ ! -z "$SCHEMAREGISTRY_URL" ]]; then echo "Enabling proxy." cat <>/tmp/Caddyfile proxy /api/schema-registry $SCHEMAREGISTRY_URL { without /api/schema-registry $INSECURE_PROXY } EOF if echo "$RELATIVE_PROXY_URL" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then SCHEMAREGISTRY_URL=api/schema-registry else SCHEMAREGISTRY_URL=/api/schema-registry fi fi if echo "$ALLOW_TRANSITIVE" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then TRANSITIVE_SETTING=",allowTransitiveCompatibilities: true" echo "Enabling transitive compatibility modes support." fi if echo "$ALLOW_GLOBAL" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then GLOBAL_SETTING=",allowGlobalConfigChanges: true" echo "Enabling global compatibility level change support." fi if echo "$ALLOW_DELETION" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then DELETION_SETTING=",allowSchemaDeletion: true" echo "Enabling schema deletion support." fi if echo "$READONLY_MODE" | egrep -sq "true|TRUE|y|Y|yes|YES|1"; then READONLY_SETTING=",readonlyMode: true" echo "Enabling readonly mode." fi if [[ -z "$SCHEMAREGISTRY_URL" ]]; then echo "Schema Registry URL was not set via SCHEMAREGISTRY_URL environment variable." else echo "Setting Schema Registry URL to $SCHEMAREGISTRY_URL." cat </tmp/env.js var clusters = [ { NAME: "default", SCHEMA_REGISTRY: "$SCHEMAREGISTRY_URL" $GLOBAL_SETTING $TRANSITIVE_SETTING $DELETION_SETTING $READONLY_SETTING } ] EOF fi if [[ -n "${CADDY_OPTIONS}" ]]; then echo "Applying custom options to Caddyfile" cat <>/tmp/Caddyfile $CADDY_OPTIONS EOF fi # Here we emulate the output by Caddy. Why? Because we can't # redirect caddy to stderr as the logging would also get redirected. cat <&2 exec /caddy/caddy -conf /tmp/Caddyfile -quiet ================================================ FILE: env.js ================================================ var clusters = [ { NAME: "prod", // Schema Registry service URL (i.e. http://localhost:8081) SCHEMA_REGISTRY: "http://localhost:8081", // https://schema-registry.demo.landoop.com COLOR: "#141414", // optional readonlyMode: true // optional }, { NAME: "dev", SCHEMA_REGISTRY: "http://localhost:8383", COLOR: "red", // optional allowGlobalConfigChanges: true, // optional allowSchemaDeletion: true // Supported for Schema Registry version >= 3.3.0 //allowTransitiveCompatibilities: true // if using a Schema Registry release >= 3.1.1 uncomment this line } ]; ================================================ FILE: package.json ================================================ { "name": "schema-registry-ui", "version": "0.9.5", "description": "A user interface for Confluent's Schema Registry", "readme": "README.md", "dependencies": { "angular": "1.5.9", "angular-animate": "1.5.9", "angular-aria": "1.5.9", "angular-diff-match-patch": "^0.1.14", "angular-json-tree": "^1.0.1", "angular-material": "^1.0.9", "angular-material-data-table": "^0.10.9", "angular-route": "1.5.9", "angular-sanitize": "1.5.9", "angular-ui-ace": "0.2.3", "angular-utils-pagination": "^0.11.1", "brace": "^0.10.0", "diff-match-patch": "^1.0.0", "file-saver": "^1.3.3", "font-awesome": "^4.6.3", "jszip": "3.1.3", "jszip-utils": "0.0.2" }, "devDependencies": { "babel-core": "^6.24.1", "babel-loader": "^7.0.0", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-1": "^6.24.1", "clean-webpack-plugin": "^0.1.16", "copy-webpack-plugin": "^4.0.1", "cross-env": "^5.0.1", "css-loader": "^0.28.1", "exports-loader": "^0.6.4", "expose-loader": "^0.7.3", "extract-text-webpack-plugin": "^2.1.0", "file-loader": "^0.11.1", "html-loader": "^0.4.5", "html-webpack-plugin": "^2.28.0", "imports-loader": "^0.7.1", "style-loader": "^0.17.0", "url-loader": "^0.5.8", "webpack": "^2.5.1", "webpack-dev-server": "^2.4.5" }, "scripts": { "start": "node ./node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.js --progress", "start-prod": "node ./node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.js --progress", "build-dev": "node ./node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node ./node_modules/webpack/bin/webpack --config webpack.config.js", "build-prod": "node ./node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node ./node_modules/webpack/bin/webpack --config webpack.config.js", "test": "echo \"Error: no test specified\" && exit 1", "postinstall": "" }, "repository": { "type": "git", "url": "git+https://github.com/Landoop/schema-registry-ui.git" }, "keywords": [ "schema", "registry", "kafka", "avro" ], "author": "Landoop team", "contributors": [ { "name": "Christina Daskalaki", "email": "christina@landoop.com" }, { "name": "Antonios Chalkiopoulos", "email": "antonios@landoop.com" }, { "name": "Marios Andreopoulos", "email": "marios@landoop.com" }, { "name": "John Glampedakis", "email": "marios@landoop.com" } ], "license": "BSL", "bugs": { "url": "https://github.com/Landoop/schema-registry-ui/issues" }, "homepage": "https://github.com/Landoop/schema-registry-ui#readme" } ================================================ FILE: src/app.js ================================================ 'use strict'; /** * Pulling in css libs */ require('font-awesome/css/font-awesome.min.css'); require('angular-material/angular-material.min.css'); require('angular-material-data-table/dist/md-data-table.min.css'); require('angular-json-tree/dist/angular-json-tree.css'); require('./assets/css/styles.css'); /** * Requiring in libs here for the time being * Going forward the require/import should be done in the file that needs it */ require('expose-loader?diff_match_patch!diff-match-patch'); window.DIFF_INSERT = require('exports-loader?DIFF_INSERT!diff-match-patch/index'); window.DIFF_DELETE = require('exports-loader?DIFF_DELETE!diff-match-patch/index'); window.DIFF_EQUAL = require('exports-loader?DIFF_EQUAL!diff-match-patch/index'); require('jszip'); require('jszip-utils'); require('angular'); require('angular-utils-pagination/dirPagination'); require('angular-ui-ace'); require('angular-route'); require('angular-sanitize'); require('angular-material'); require('angular-animate'); require('angular-aria'); require('angular-material-data-table'); require('angular-diff-match-patch'); require('angular-json-tree'); var angularAPP = angular.module('angularAPP', [ 'ui.ace', 'angularUtils.directives.dirPagination', 'ngRoute', 'ngMaterial', 'ngAnimate', 'ngAria', 'md.data.table', 'diff-match-patch', 'angular-json-tree', 'ngSanitize' ]); /** * */ require('./schema-registry'); require('./factories'); /** * Templates */ var homeTemplate = require('./schema-registry/home/home.html'); var newTemplate = require('./schema-registry/new/new.html'); var exportTemplate = require('./schema-registry/export/export.html'); var viewTemplate = require('./schema-registry/view/view.html'); var configTemplate = require('./schema-registry/config/config.html'); var listTemplate = require('./schema-registry/list/list.html'); var dirPaginationControlsTemplate = require('./schema-registry/pagination/dirPaginationControlsTemplate.html'); var HeaderCtrl = function ($rootScope, $scope, $location, $log, SchemaRegistryFactory, env) { $scope.$on('$routeChangeSuccess', function () { $rootScope.clusters = env.getClusters(); $scope.cluster = env.getSelectedCluster(); $scope.color = $scope.cluster.COLOR; }); $scope.updateEndPoint = function (cluster) { $rootScope.connectionFailure = false; $location.path("/cluster/" + cluster) } }; HeaderCtrl.$inject = ['$rootScope', '$scope', '$location', '$log', 'SchemaRegistryFactory', 'env']; angularAPP.controller('HeaderCtrl', HeaderCtrl); /* Custom directives */ angularAPP.directive('validJson', function () { return { require: 'ngModel', priority: 1000, link: function (scope, elem, attrs, ngModel) { // view to model ngModel.$parsers.unshift(function (value) { var valid = true, obj; try { obj = JSON.parse(value); } catch (ex) { valid = false; } ngModel.$setValidity('validJson', valid); return valid ? obj : undefined; }); // model to view ngModel.$formatters.push(function (value) { return value;//JSON.stringify(value, null, '\t'); }); } }; }); angularAPP.filter('reverse', function () { return function (items) { return items.slice().reverse(); }; }); angularAPP.config(['$compileProvider', '$mdThemingProvider', '$routeProvider', function ($compileProvider, $mdThemingProvider, $routeProvider) { $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|blob):/); $mdThemingProvider.theme('default') .primaryPalette('blue-grey') .accentPalette('blue') .warnPalette('grey'); $routeProvider .when('/', { template: homeTemplate, controller: 'HomeCtrl' }) .when('/cluster/:cluster', { template: homeTemplate, controller: 'HomeCtrl' }) .when('/cluster/:cluster/schema/new', { template: newTemplate, controller: 'NewSubjectCtrl as ctrl' }) .when('/cluster/:cluster/export', { template: exportTemplate, controller: 'ExportSchemasCtrl' }) .when('/cluster/:cluster/schema/:subject/version/:version', { template: viewTemplate, controller: 'SubjectsCtrl' }) .otherwise({ redirectTo: '/' }); } // $locationProvider.html5Mode(true); ]); angularAPP.run(['env', '$routeParams', '$rootScope', '$templateCache', function loadRoute(env, $routeParams, $rootScope, $templateCache) { $rootScope.$on('$routeChangeSuccess', function () { env.setSelectedCluster($routeParams.cluster); }); $templateCache.put('config.html', configTemplate); $templateCache.put('list.html', listTemplate); $templateCache.put('angularUtils.directives.dirPagination.template', dirPaginationControlsTemplate); } ]); ================================================ FILE: src/assets/css/styles.css ================================================ html, body { height: 100%; margin: 0; padding: 0; background-color: #F5F5F5; font-size: 14px; font-family: sans-serif; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } a { color: #337ab7; text-decoration: none; } .ace_editor { height: 460px; } .ace_gutter { background: #f9f9f9 !important; } .searchSchemas { border: 0; margin: 10px auto; display: block; width: 100%; height: 36px; border-bottom: 1px solid #ddd; outline: none; text-indent: 15px; } .searchSchemas:focus, .searchSchemas:focused { outline: none; } header.header { background-color: #141414; min-height: 20px; color: #ebebeb; margin-bottom: 0; } json-tree .branch-preview { overflow: hidden; font-style: italic; max-width: 90%; height: 1.5em; opacity: .7; } .header-section { background-color: #4F6375; color: #ebebeb; border-bottom: 0 solid #2b3e50; padding: 4px 0; margin-bottom: 0; } .selectCluster { margin: 7px 10px; text-indent: 15px; padding: 0; line-height: 1.2; } .selectCluster md-select { background: white; } .selectCluster md-select md-option, .selectCluster md-select md-select-value { color: #333 } .selectClusterLabel, .exportSchemas { color: rgb(204, 204, 204); font-size: 12px; line-height: 44px; padding-top: 0; margin: 0; } .exportSchemas { margin-right: 40px } md-input-container.md-default-theme label.md-required:after, md-input-container label.md-required:after { color: red; } .buttonGroup { top: 66px; text-align: right; right: 0; position: absolute; } .md-button.blue, .md-button.md-small.blue, .md-raised.md-primary.md-button.md-ink-ripple.blue { background-color: rgb(65, 191, 236); color: white; } .md-button.blue:hover, .md-button.md-small.blue:hover, .md-raised.md-primary.md-button.md-ink-ripple.blue:hover { background-color: rgb(60, 178, 216); } .md-button.md-small.green, .md-raised.md-primary.md-button.md-ink-ripple.green, .md-primary.md-button.green { background-color: rgb(139, 195, 74); color: white; } .md-button.md-small.green:hover, .md-raised.md-primary.md-button.md-ink-ripple.green:hover, .md-primary.md-button.green:hover { background-color: rgb(119, 175, 64); } .md-button.md-small.green[disabled], .md-raised.md-primary.md-button.md-ink-ripple.green[disabled], .md-primary.md-button.green[disabled], .md-button.blue[disabled], .md-button.md-small.blue[disabled], .md-raised.md-primary.md-button.md-ink-ripple.blue[disabled] { background-color: #ccc; } .md-button.md-small { width: 20px; height: 20px; line-height: 20px; min-height: 20px; vertical-align: top; font-size: 10px; padding: 0 0; margin: 0; } .exportSchemas:hover { color: rgb(230, 230, 230) } [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; } .container-fluid-centered { height: 100%; display: table; width: 100%; padding: 0; } a:link, header.a:visited, header.a:hover, header.a:active { text-decoration: none; } .row-fluid { height: 100%; display: table-cell; vertical-align: middle; } .textareacode { border-radius: 3px; outline: none; box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075); border: 1px solid #ccc; width: 95%; font: 12px Consolas, Menlo, Courier, monospace; } .example { position: relative; margin: 15px 0 0; padding: 39px 19px 14px; background-color: #fff; border-radius: 4px 4px 0 0; border: 1px solid #ddd; z-index: 2; } .example1:after { content: "Register a Complex Schema"; } .example2:after { content: "Test Schema Compatibility"; } .example:after { position: absolute; top: 0; left: 0; padding: 2px 8px; font-size: 12px; font-weight: bold; background-color: #f5f5f5; color: #9da0a4; border-radius: 4px 0 4px 0; } .snippet { position: relative; overflow: visible; } pre { margin-top: 0; margin-bottom: 0; font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace; } .centering { float: none; margin: 0 auto; } .bs-callout-warning { border-left-color: #228415 !important; } .bs-callout { padding: 10px; margin: 10px 0; border: 1px solid #eee; border-left-width: 5px; border-radius: 3px; } /* Style The Dropdown Button */ .dropbtn { background-color: #4f6375; color: white; font-size: 16px; padding: 4px; border: none; cursor: pointer; min-width: 250px; } /* The container
- needed to position the dropdown content */ .dropdown { position: relative; display: inline-block; z-index: 999; } /* Dropdown Content (Hidden by Default) */ .dropdown-content { display: none; position: absolute; background-color: green; min-width: 250px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); } /* Links inside the dropdown */ .dropdown-content a { color: black; padding: 12px 16px; text-decoration: none; display: block; } /* Change color of dropdown links on hover */ .dropdown-content a:hover { background-color: #6e8294 } /* Show the dropdown menu on hover */ .dropdown:hover .dropdown-content { display: block; background-color: #4f6375; } /* Change the background color of the dropdown button when the dropdown content is shown */ .dropdown:hover .dropbtn { background-color: #4f6375; } md-card { margin: 0; } md-toast.md-default-theme .md-toast-content, md-toast .md-toast-content { margin-top: 70px; margin-right: 25px; background-color: white; color: black; text-align: center; } md-chips.md-default-theme .md-chips, md-chips .md-chips { box-shadow: none; } .md-button.light-blue { background-color: rgba(74, 163, 223, 1); } /** Pagination **/ .label-primary { background-color: #286090; } /* Useful for the `New Subject` auto-complete field */ .md-whiteframe-1dp, .md-whiteframe-z1 { box-shadow: none; } /*.md-whiteframe-2dp {*/ /*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12);*/ /*}*/ /*.md-whiteframe-2dp:focus {*/ /*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.4), 0 2px 2px 0 rgba(0, 0, 0, 0.28), 0 3px 1px -2px rgba(0, 0, 0, 0.24);*/ /*}*/ .md-button.light-blue { background-color: rgba(74, 163, 223, 1); } .md-button.md-fab { width: 42px; height: 42px; background-color: white; } a.md-button.md-default-theme.md-warn.md-raised, a.md-button.md-warn.md-raised, a.md-button.md-default-theme.md-warn.md-fab, a.md-button.md-warn.md-fab, .md-button.md-default-theme.md-warn.md-raised, .md-button.md-warn.md-raised, .md-button.md-default-theme.md-warn.md-fab, .md-button.md-warn.md-fab { color: rgb(255, 255, 255); background-color: rgba(169, 169, 169, 0.1); } /* Custom topics-ui CSS */ md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style { min-height: 0; } .table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th { padding-top: 10px; padding-bottom: 10px; vertical-align: middle; } md-card md-card-title { padding-bottom: 10px; } /** Kafka-Connect only **/ .ats-switch span.switch-right { color: #000; background: #f59800; } .md-tooltip ._md-content { height: auto; } /*Animated cogs https://codepen.io/marclloyd77/pen/tAlmd*/ #development_icon { width: 100px; margin: 0; -webkit-animation: pop 0.4s ease-in; } /*Animate cogs*/ #large-cog, #small-cog { -webkit-animation: spin 4s linear infinite; -webkit-transform-origin: 50% 50%; -webkit-animation-delay: 0.6s; } #small-cog { -webkit-animation: spinback 2s linear infinite; -webkit-animation-delay: 0.6s; } @-webkit-keyframes pop { 0% { -webkit-transform: scale(0); } 90% { -webkit-transform: scale(1.1); } 100% { -webkit-transform: scale(1); } } @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } @-webkit-keyframes spinback { 100% { -webkit-transform: rotate(-360deg); } } .ace_diff_new_line { background: rgba(0, 128, 0, 0.04); } .noselect { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } /* diff css */ .match { display: none; color: blue; } .ins { background: rgba(55, 255, 55, 0.15); } .del { background: rgba(243, 59, 59, 0.15); } /* Essential for dirPagination material design */ .pagination .md-button { min-width: 30px; margin: 5px 8px; } /* Essential for dirPagination material design */ .md-button { min-width: 70px; margin: 5px 8px; color: #333; } .selectedListItem { background-color: #DDDDDD; } /*Funny color*/ .md-fab:hover, .md-fab.md-focused { background-color: rgba(230, 100, 0, .9) !important; } md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style { height: 48px; } md-content { background-color: white; } .md-button.md-default-theme.md-primary.md-raised.newschemabutton { margin-right: 4px; background-color: #448AFF; color: white; } md-list-item.md-2-line.shemaslistitem { width: 100%; } .md-2-line.shemaslistitem a .md-button.md-warn.md-raised.divlistitem { min-width: 100%; text-align: left; box-shadow: 0 0 0 0 rgba(0, 0, 0, .26) !important; } .md-raised.md-warn.md-button.versionbox { box-shadow: 0 0 0 0 rgba(0, 0, 0, .46); min-width: initial; font-size: 75%; line-height: 17px; min-height: 18px; text-transform: none; background-color: rgba(44, 152, 240, 0.3); color: black; text-align: left; float: right; padding: 3px 12px; margin-top: 10px; } .md-raised.md-warn.md-button.versionbox.moreversions { background: rgb(139, 195, 74); } .md-raised.md-warn.md-button.md-ink-ripple.editbutton, .md-raised.md-warn.md-button.md-ink-ripple.testbutton { padding-left: 20px; padding-right: 20px; background-color: #448AFF; color: white; } .md-default-theme md-input-container .md-errors-spacer { min-height: 0; } span.title { margin: 0; padding: 0; line-height: 44px; font-size: 14px; } md-tab-content md-content { overflow: auto } .flex-2 { width: 50%; float: left; } md-switch.md-default-theme.md-checked .md-thumb, md-switch.md-checked .md-thumb { background-color: rgb(65, 191, 236); } md-switch.md-default-theme.md-checked .md-bar, md-switch.md-checked .md-bar { background-color: rgba(65, 191, 236, 0.6); } .seperator:last-child { border-top: 2px dashed #aaa; margin: 40px 0 30px; } /* The Modal (background) */ .modal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ z-index: 999999; } /* Modal Content/Box */ .modal-content { background-color: #fefefe; margin: 5% auto; /* 15% from the top and centered */ padding: 20px; border: 1px solid #888; width: 1200px; /* Could be more or less, depending on screen size */ } /* The Close Button */ .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; } .close:hover, .close:focus { color: black; text-decoration: none; cursor: pointer; } .md-button.btn-danger { background:#d05653; color:white; } .md-button.btn-danger:hover { background:#a7110d; } ================================================ FILE: src/factories/avro4s-factory.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var Avro4ScalaFactory = function ($rootScope, $http, $location, $q, $log) { /* Public API */ return { getScalaFiles: function (apiData) { $log.warn(apiData); $http.defaults.useXDomain = true; var singleLineApiData = apiData.split("\n").join(" "); var req = { method: 'POST', data: singleLineApiData, crossDomain: true, url: AVRO4S, headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(req) .success(function (data) { $log.info("Received a response with: " + data); var results = data.split("###"); $log.info(results); if (results[0] === "scala") { $log.info("It's Scala !! "); $log.info("It's Scala :" + results[1]); //alg0 return results[1]; } }) .error(function (data, status) { $log.error("Bad data [" + data + "] status [" + status + "]"); }); } } }; Avro4ScalaFactory.$inject = ['$rootScope', '$http', '$location', '$q', '$log']; angularAPP.factory('Avro4ScalaFactory', Avro4ScalaFactory); // curl 'https://platform.landoop.com/avro4s/avro4s' -H 'Pragma: no-cache' -H 'Origin: https://avro4s-ui.landoop.com' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en-US,en;q=0.8,el;q=0.6' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Cache-Control: no-cache' -H 'Referer: https://avro4s-ui.landoop.com/' -H 'Connection: keep-alive' --data-binary '{ "type": "record", "name": "Evolution", "namespace": "com.landoop", "fields": [ { "name": "name", "type": "string" }, { "name": "number1", "type": "int" }, { "name": "number2", "type": "float" }, { "name": "text", "type": [ "string", "null" ], "default": "" } ] }' --compressed ================================================ FILE: src/factories/env-factory.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var envFactory = function ($rootScope) { var clusterArray = (typeof clusters !== "undefined") ? angular.copy(clusters) : []; var selectedCluster = null; setCluster(); return { setSelectedCluster : function(clusterName) { setCluster(clusterName)}, getSelectedCluster : function() { return selectedCluster; }, getClusters : function() { return clusters} , SCHEMA_REGISTRY : function () { return selectedCluster.SCHEMA_REGISTRY; }, AVRO4S : 'https://platform.landoop.com/avro4s/avro4s', // Not currently used, will be used for converting Avro -> Scala Case classes COLOR : function () { return selectedCluster.COLOR; }, allowGlobalConfigChanges : function () { return selectedCluster.allowGlobalConfigChanges; }, allowTransitiveCompatibilities: function () { return selectedCluster.allowTransitiveCompatibilities; }, allowSchemaDeletion: function () { return selectedCluster.allowSchemaDeletion; }, readonlyMode: function() { return selectedCluster.readonlyMode; } }; function setCluster(clusterName) { if(clusterArray.length === 0) { $rootScope.missingEnvJS = true; console.log("NOT EXISTS env.js") } if(angular.isUndefined(clusterName)) { selectedCluster = clusterArray[0]; } else { var filteredArray = clusterArray.filter(function(el) {return el.NAME === clusterName}); selectedCluster = filteredArray.length === 1 ? filteredArray[0] : clusterArray[0] } } }; envFactory.$inject = ['$rootScope']; angularAPP.factory('env', envFactory); ================================================ FILE: src/factories/index.js ================================================ require("./utils-factory"); require("./schema-registry-factory"); require("./avro4s-factory"); require("./env-factory"); require("./toast-factory"); ================================================ FILE: src/factories/schema-registry-factory.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); /** * Schema-Registry angularJS Factory * * Landoop - version 0.9.x (May.2017) */ var SchemaRegistryFactory = function ($rootScope, $http, $location, $q, $log, UtilsFactory, env) { /** * Get subjects * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects */ function getSubjects() { var url = env.SCHEMA_REGISTRY() + '/subjects/'; $log.debug(" curl -X GET " + url); var start = new Date().getTime(); var deferred = $q.defer(); $http.get(url) .then( function successCallback(response) { var allSubjectNames = response.data; $log.debug(" curl -X GET " + url + " => " + allSubjectNames.length + " registered subjects in [ " + ((new Date().getTime()) - start) + " ] msec"); deferred.resolve(allSubjectNames); }, function errorCallback(response) { deferred.reject("Failure with : " + response) }); return deferred.promise; } /** * Get subjects versions * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions */ function getSubjectsVersions(subjectName) { var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/'; $log.debug(" curl -X GET " + url); var start = new Date().getTime(); var deferred = $q.defer(); $http.get(url).then( function successCallback(response) { var allVersions = response.data; $log.debug(" curl -X GET " + url + " => " + JSON.stringify(allVersions) + " versions in [ " + (new Date().getTime() - start) + " ] msec"); deferred.resolve(allVersions); }, function errorCallback(response) { var msg = "Failure with : " + response + " " + JSON.stringify(response); $log.error("Error in getting subject versions : " + msg); deferred.reject(msg); }); return deferred.promise; } /** * Get a specific version of the schema registered under this subject * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions-(versionId- version) */ function getSubjectAtVersion(subjectName, version) { var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version; $log.debug(" curl -X GET " + url); var deferred = $q.defer(); var start = new Date().getTime(); $http.get(url).then( function successCallback(response) { var subjectInformation = response.data; $log.debug(" curl -X GET " + url + " => [" + subjectName + "] subject " + JSON.stringify(subjectInformation).length + " bytes in [ " + (new Date().getTime() - start) + " ] msec"); deferred.resolve(subjectInformation); }, function errorCallback(response) { var msg = "Failure getting subject at version : " + response + " " + JSON.stringify(response); $log.error(msg); deferred.reject(msg); }); return deferred.promise; } function getAllSchemas(cache) { var i; var allSchemasCache = []; angular.forEach(cache, function (schema) { for (i = 1; i <= schema.version; i++) { getSubjectAtVersion(schema.subjectName, i).then(function (selectedSubject) { allSchemasCache.push(selectedSubject) //$rootScope.downloadFile += '\n echo >>>' + selectedSubject.subject +'.'+ selectedSubject.version + '.json <<< \n' + schema.schema + ' \n \n EOF'; }) } }); $rootScope.allSchemasCache = allSchemasCache; return allSchemasCache } /** * Register a new schema under the specified subject. If successfully registered, this returns the unique identifier of this schema in the registry. * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject)-versions */ function postNewSubjectVersion(subjectName, newSchema) { var deferred = $q.defer(); $log.debug("Posting new version of subject [" + subjectName + "]"); var postSchemaRegistration = { method: 'POST', url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + "/versions", data: '{"schema":"' + newSchema.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'", dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(postSchemaRegistration) .success(function (data) { //$log.info("Success in registering new schema " + JSON.stringify(data)); var schemaId = data.id; deferred.resolve(schemaId); }) .error(function (data, status) { $log.info("Error on schema registration : " + JSON.stringify(data)); var errorMessage = data.message; if (status >= 400) { $log.debug("Schema registrations is not allowed " + status + " " + data); } else { $log.debug("Schema registration failure: " + JSON.stringify(data)); } deferred.reject(data); }); return deferred.promise; } /** * Check if a schema has already been registered under the specified subject. If so, this returns the schema string * along with its globally unique identifier, its version under this subject and the subject name. * * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject) */ function checkSchemaExists(subjectName, subjectInformation) { var deferred = $q.defer(); $log.debug("Checking if schema exists under this subject [" + subjectName + "]"); var postSchemaExists = { method: 'POST', url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName, data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'", dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(postSchemaExists) .success(function (data) { var response = { id: data.id, version: data.version }; $log.info("Response : " + JSON.stringify(response)); deferred.resolve(response); }) .error(function (data, status) { $log.info("Error while checking if schema exists under a subject : " + JSON.stringify(data)); var errorMessage = data.message; if (status === 407) { $log.debug("Subject not found or schema not found - 407 - " + status + " " + data); } else { $log.debug("Some other failure: " + JSON.stringify(data)); } $defered.reject("Something") }); return deferred.promise; } /** * Test input schema against a particular version of a subject’s schema for compatibility. * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--compatibility-subjects-(string- subject)-versions-(versionId- version) */ function testSchemaCompatibility(subjectName, subjectInformation) { var deferred = $q.defer(); $log.debug(" Testing schema compatibility for [" + subjectName + "]"); var postCompatibility = { method: 'POST', url: env.SCHEMA_REGISTRY() + '/compatibility/subjects/' + subjectName + "/versions/latest", data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'", dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(postCompatibility) .success(function (data) { $log.info("Success in testing schema compatibility " + JSON.stringify(data)); deferred.resolve(data.is_compatible) }) .error(function (data, status) { $log.warn("Error on check compatibility : " + JSON.stringify(data)); if (status === 404) { if (data.error_code === 40401) { $log.warn("40401 = Subject not found"); } $log.warn("[" + subjectName + "] is a non existing subject"); deferred.resolve("new"); // This will be a new subject (!) } else { $log.error("HTTP > 200 && < 400 (!) " + JSON.stringify(data)); } deferred.reject(data); }); return deferred.promise; } /** * Put global config (Test input schema against a particular version of a subject’s schema for compatibility. * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config */ function putConfig(compatibilityLevel) { var deferred = $q.defer(); if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(compatibilityLevel) !== -1) { var putConfig = { method: 'PUT', url: env.SCHEMA_REGISTRY() + '/config', data: '{"compatibility":"' + compatibilityLevel + '"}' + "'", dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(putConfig) .success(function (data) { $log.info("Success in changing global schema-registry compatibility " + JSON.stringify(data)); deferred.resolve(data.compatibility) }) .error(function (data, status) { $log.info("Error on changing global compatibility : " + JSON.stringify(data)); if (status === 422) { $log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data)); if (JSON.stringify(data).indexOf('50001') > -1) { $log.error(" Error in the backend data store - " + $scope.text); } else if (JSON.stringify(data).indexOf('50003') > -1) { $log.error("Error while forwarding the request to the master: " + JSON.stringify(data)); } } else { $log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data)); } deferred.reject(data); }); } else { $log.warn("Compatibility level:" + compatibilityLevel + " is not supported"); deferred.reject(); } return deferred.promise; } /** * Get global compatibility-level config * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--config */ function getGlobalConfig() { var deferred = $q.defer(); var url = env.SCHEMA_REGISTRY() + '/config'; $log.debug(" curl -X GET " + url); var start = new Date().getTime(); $http.get(url) .success(function (data) { $log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec"); deferred.resolve(data) }) .error(function (data, status) { deferred.reject("Get global config rejection : " + data + " " + status) }); return deferred.promise; } function getSubjectConfig(subjectName) { var deferred = $q.defer(); var url = env.SCHEMA_REGISTRY() + '/config/' + subjectName; $log.debug(" curl -X GET " + url); var start = new Date().getTime(); $http.get(url) .success(function (data) { $log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec"); deferred.resolve(data) }) .error(function (data, status) { if (status === 404) { $log.warn('No compatibility level is set for ' + subjectName + '. Global compatibility level is applied'); } else deferred.reject("Get global config rejection : " + data + " " + status) }); return deferred.promise; } /** * Update compatibility level for the specified subject * @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config-(string- subject) */ function updateSubjectCompatibility(subjectName, newCompatibilityLevel) { var deferred = $q.defer(); if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(newCompatibilityLevel) !== -1) { var putConfig = { method: 'PUT', url: env.SCHEMA_REGISTRY() + '/config/' + subjectName, data: '{"compatibility":"' + newCompatibilityLevel + '"}' + "'", dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json'} }; $http(putConfig) .success(function (data) { $log.info("Success in changing subject [ " + subjectName + " ] compatibility " + JSON.stringify(data)); deferred.resolve(data.compatibility) }) .error(function (data, status) { $log.info("Error on changing compatibility : " + JSON.stringify(data)); if (status === 422) { $log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data)); if (JSON.stringify(data).indexOf('50001') > -1) { $log.error(" Error in the backend data store - " + $scope.text); } else if (JSON.stringify(data).indexOf('50003') > -1) { $log.error("Error while forwarding the request to the master: " + JSON.stringify(data)); } } else { $log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data)); } deferred.reject(data); }); } else { $log.warn("Compatibility level:" + newCompatibilityLevel + " is not supported"); deferred.reject(); } return deferred.promise; } /** * Custom logic of Factory is implemented here. * * In a nut-shell `CACHE` is holding a cache of known subjects * Methods here are utilizing the cache - picking from it or updating * * Subjects are immutable in the schema-registry, thus downloading them * just once is enough ! */ var CACHE = []; // A cache of the latest subject /** * Gets from CACHE if exists - undefined otherwise */ function getFromCache(subjectName, subjectVersion) { var start = new Date().getTime(); var response = undefined; angular.forEach(CACHE, function (subject) { if (subject.subjectName === subjectName && subject.version === subjectVersion) { $log.debug(" [ " + subjectName + "/" + subjectVersion + " ] found in cache " + JSON.stringify(subject).length + " bytes in [ " + ((new Date().getTime()) - start) + " ] msec"); response = subject; } }); return response; } /** * GETs latest from CACHE or 'undefined' */ function getLatestFromCache(subjectName) { var subjectFromCache = undefined; for (var i = 1; i < 10000; i++) { var x = getFromCache(subjectName, i); if (x !== undefined) subjectFromCache = x; } return subjectFromCache; } /** * * Composite & Public Methods of this factory * */ return { // Proxy in function getGlobalConfig: function () { return getGlobalConfig(); }, getSubjectConfig: function (subjectName) { return getSubjectConfig(subjectName); }, putConfig: function (config) { return putConfig(config); }, updateSubjectCompatibility: function (subjectName, newCompatibilityLevel) { return updateSubjectCompatibility(subjectName, newCompatibilityLevel); }, // Proxy in function testSchemaCompatibility: function (subjectName, subjectInformation) { return testSchemaCompatibility(subjectName, subjectInformation); }, // Proxy in function registerNewSchema: function (subjectName, subjectInformation) { return postNewSubjectVersion(subjectName, subjectInformation); }, // Proxy in function getSubjectsVersions: function (subjectName) { return getSubjectsVersions(subjectName); }, // Proxy in function getLatestSubjectFromCache: function (subjectName) { return getLatestFromCache(subjectName); }, // Proxy in function getAllSchemas: function (schemas) { return getAllSchemas(schemas); }, deleteVersionOfSubject: function (subjectName, version) { var deferred = $q.defer(); var request = { method: 'DELETE', url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version, dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'} }; $http(request) .success(function (data) { $log.info("Success in deleting subject version" + subjectName + ", version" + version); var schemaId = data.id; deferred.resolve(schemaId); }) .error(function (data, status) { $log.info("Error on subject version deletion : ", data); deferred.reject(data); }); return deferred.promise; }, deleteSubject: function (subjectName) { var deferred = $q.defer(); var request = { method: 'DELETE', url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName, dataType: 'json', headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'} }; $http(request) .success(function (data) { $log.info("Success in deleting schema" + subjectName); deferred.resolve(); }) .error(function (data, status) { $log.info("Error on schema deletion : ", data); deferred.reject(); }); return deferred.promise; }, /** * GETs all subject-names and then GETs the /versions/latest of each one * * Refreshes the CACHE object with latest subjects */ refreshLatestSubjectsCACHE: function () { var deferred = $q.defer(); var start = new Date().getTime(); // 1. Get all subject names getSubjects().then( function success(allSubjectNames) { // 2. Get full details of subject's final versions var urlFetchLatestCalls = []; angular.forEach(allSubjectNames, function (subject) { urlFetchLatestCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subject + '/versions/latest')); }); $q.all(urlFetchLatestCalls).then(function (latestSchemas) { CACHE = []; // Clean up existing cache - to replace with new one angular.forEach(latestSchemas, function (result) { var data = result.data; var cacheData = { version: data.version, // version id: data.id, // id schema: data.schema, // schema - in String - schema i.e. {\"type\":\"record\",\"name\":\"User\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"}]} Schema: JSON.parse(data.schema), // js type | name | doc | fields ... subjectName: data.subject }; CACHE.push(cacheData); }); $log.debug(" pipeline : get-latest-subjects-refresh-cache in [ " + (new Date().getTime() - start) + " ] msec"); $rootScope.showSpinner = false; $rootScope.Cache = CACHE; deferred.resolve(CACHE); }); }); return deferred.promise; }, /** * Get one subject at a particular version */ getSubjectAtVersion: function (subjectName, subjectVersion) { var deferred = $q.defer(); // If it's easier to fetch it from cache var subjectFromCache = getFromCache(subjectName, subjectVersion); if (subjectFromCache !== undefined) { deferred.resolve(subjectFromCache); } else { var start = new Date().getTime(); getSubjectAtVersion(subjectName, subjectVersion).then( function success(subjectInformation) { //cache it var subjectInformationWithMetadata = { version: subjectInformation.version, id: subjectInformation.id, schema: subjectInformation.schema, // this is text Schema: JSON.parse(subjectInformation.schema), // this is json subjectName: subjectInformation.subject }; $log.debug(" pipeline: " + subjectName + "/" + subjectVersion + " in [ " + (new Date().getTime() - start) + " ] msec"); deferred.resolve(subjectInformationWithMetadata); }, function errorCallback(response) { $log.error("Failure with : " + JSON.stringify(response)); }); } return deferred.promise; }, /** * GETs the entire subject's history, by * * i. Getting all version * ii. Fetching each version either from cache or from HTTP GET */ getSubjectHistory: function (subjectName) { var deferred = $q.defer(); $log.info("Getting subject [ " + subjectName + "] history"); var completeSubjectHistory = []; getSubjectsVersions(subjectName).then( function success(allVersions) { var urlCalls = []; angular.forEach(allVersions, function (version) { // If in cache var subjectFromCache = getFromCache(subjectName, version); if (subjectFromCache !== undefined) { completeSubjectHistory.push(subjectFromCache); } else { urlCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version)); } }); // Get all missing versions and add them to cache $q.all(urlCalls).then(function (results) { angular.forEach(results, function (result) { completeSubjectHistory.push(result.data); }); deferred.resolve(completeSubjectHistory); }); }, function failure(data) { deferred.reject("pdata=>" + data); }); return deferred.promise; }, /** * Get the history in a diff format convenient for rendering a ui */ getSubjectHistoryDiff: function (subjectHistory) { var changelog = []; $log.info("Sorting by version.."); var sortedHistory = UtilsFactory.sortByVersion(subjectHistory); for (var i = 0; i < sortedHistory.length; i++) { var previous = ''; if (i > 0) previous = JSON.parse(sortedHistory[i - 1].schema); var changeDetected = { version: sortedHistory[i].version, id: sortedHistory[i].id, current: JSON.parse(sortedHistory[i].schema), previous: previous }; changelog.push(changeDetected); } return changelog; } } }; SchemaRegistryFactory.$inject = ['$rootScope', '$http', '$location', '$q', '$log', 'UtilsFactory', 'env']; angularAPP.factory('SchemaRegistryFactory', SchemaRegistryFactory); ================================================ FILE: src/factories/toast-factory.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var toastFactory = function ($rootScope, $mdToast, $window) { var last = { bottom: false, top: true, left: false, right: true }; var toastPosition = angular.extend({}, last); /* Public API of this factory*/ this.getToastPosition = function () { this.sanitizePosition(); return Object.keys(toastPosition) .filter(function (pos) { return toastPosition[pos]; }) .join(' '); }; this.sanitizePosition = function () { var current = toastPosition; if (current.bottom && last.top) current.top = false; if (current.top && last.bottom) current.bottom = false; if (current.right && last.left) current.left = false; if (current.left && last.right) current.right = false; last = angular.extend({}, current); }; this.showSimpleToast = function (message) { $mdToast.show( $mdToast.simple() .textContent(message) .position(this.getToastPosition()) .hideDelay(2000) ); }; this.showSimpleToastToTop = function (message) { this.showSimpleToast(message); $window.scrollTo(0, 0); }; this.showLongToast = function (message) { var last = this.getToastPosition(); $mdToast.show( $mdToast.simple() .textContent(message) .position(last) .hideDelay(5000) ); $window.scrollTo(0, 0); }; this.showActionToast = function (message) { var toast = $mdToast.simple() .textContent(message) .action('DELETE') .highlightAction(true) //.highlightClass('md-accent')// Accent is used by default, this just demonstrates the usage. .position(this.getToastPosition()) .hideDelay(2000); $mdToast.show(toast).then(function (response) { if (response === 'ok') { //alert('You clicked the \'UNDO\' action.'); } }); }; this.hideToast = function () { $mdToast.hide(); }; }; toastFactory.$inject = ['$rootScope', '$mdToast', '$window']; angularAPP.service('toastFactory', toastFactory); ================================================ FILE: src/factories/utils-factory.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); /** * Utils angularJS Factory */ var UtilsFactory = function ($log) { // Sort arrays by key function sortByKey(array, key, reverse) { return array.sort(function (a, b) { var x = a[key]; var y = b[key]; return ((x < y) ? -1 * reverse : ((x > y) ? 1 * reverse : 0)); }); } /* Public API */ return { sortByKey: function (array, key, reverse) { return sortByKey(array, key, reverse); }, sortByVersion: function (array) { var sorted = array.sort(function (a, b) { return a.version - b.version; }); return sorted; }, IsJsonString: function (str) { try { JSON.parse(str); } catch (e) { return false; } return true; } } }; UtilsFactory.$inject = ['$log']; angularAPP.factory('UtilsFactory', UtilsFactory); ================================================ FILE: src/index.html ================================================ Schema Registry UI
EXPORT SCHEMAS {{connectEndPoint.NAME}}

Missing Cluster Configuration


In order to configure schema-registry-ui you need to add env.js file in the root directory of the app.
Example env.js structure:
        
var clusters = [
   {
       NAME:"prod",
       // Schema Registry service URL (i.e. http://localhost:8081)
       SCHEMA_REGISTRY: "http://localhost:8081", // https://schema-registry.demo.landoop.com
       COLOR: "#141414" // optional
     },
     {
       NAME:"dev",
       SCHEMA_REGISTRY: "http://localhost:8383",
       COLOR: "red", // optional
       allowGlobalConfigChanges: true, // optional
       //allowTransitiveCompatibilities: true        // if using a Confluent Platform release >= 3.1.1 uncomment this line
     }
  ];
        
      

================================================ FILE: src/schema-registry/config/config.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var SchemaRegistryConfigCtrl = function ($scope, $http, $log, $mdDialog, SchemaRegistryFactory, env) { $log.info("Starting schema-registry controller"); $scope.config = {}; $scope.connectionFailure = false; $scope.showButton = false; //Get the top level config $scope.$watch(function () { return env.getSelectedCluster().NAME; }, function () { $scope.schemaRegistryURL = env.SCHEMA_REGISTRY(); $scope.globalConfigOpts = ["NONE", "FULL", "FORWARD", "BACKWARD"]; if (env.allowTransitiveCompatibilities()) { $scope.globalConfigOpts.push("FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"); } SchemaRegistryFactory.getGlobalConfig().then( function success(config) { $scope.allowChanges = !env.readonlyMode() && env.allowGlobalConfigChanges(); $scope.config = config; $scope.connectionFailure = false; $scope.form = $scope.config.compatibilityLevel; }, function failure(response) { $log.error("Failure with : " + JSON.stringify(response)); $scope.connectionFailure = true; }); }, true); $scope.updateGlobalConfig = function (config, event) { $mdDialog.show(dialog(config, event)).then(function () { SchemaRegistryFactory.putConfig(config).then(function () { $scope.form = $scope.config.compatibilityLevel = config; $scope.form = config; }); }); }; var backwardText = 'Backward compatibility (default):
A new schema is backward compatible if it can be used to read the data written in all previous schemas.
Backward compatibility is useful for loading data into systems like Hadoop since one can always query data of all versions using the latest schema.'; var forwardText = 'Forward compatibility:
A new schema is forward compatible if all previous schemas can read data written in this schema.
Forward compatibility is useful for consumer applications that can only deal with data in a particular version that may not always be the latest version.'; var fullText = "Full compatibility: A new schema is fully compatible if it's both backward and forward compatible."; var noneText = "No compatibility: A new schema can be any schema as long as it's a valid Avro."; var backward_transitive = "Backward transitive: Only available for schema registry 3.1.0 and above.
New schema is backward and forward compatible with all previously registered schemas."; var forward_transitive = "Forward transitive: Only available for schema registry 3.1.0 and above.
All previously registered schemas can read data produced by the new schema."; var full_transitive = "Full transitive: Only available for schema registry 3.1.0 and above.
New schema can read data produced by all previously registered schemas."; var text = ''; function dialog(config, event) { switch (config) { case "BACKWARD": text = backwardText; break; case "FORWARD": text = forwardText; break; case "FULL": text = fullText; break; case "NONE": text = noneText; break; case "BACKWARD_TRANSITIVE": text = backward_transitive; break; case "FORWARD_TRANSITIVE": text = forward_transitive; break; case "FULL_TRANSITIVE": text = full_transitive; break; default: text = '' } return $mdDialog.confirm() .title('Warning. You are about to change the \'Global Compatibility Level\'.') .htmlContent('This will affect the default behaviour and all subjects/schemas that do not have a compatibility level explicitly defined.

' + text) .targetEvent(event) .ok('UPDATE') .cancel('CANCEL'); } }; SchemaRegistryConfigCtrl.$inject = ['$scope', '$http', '$log', '$mdDialog', 'SchemaRegistryFactory', 'env']; angularAPP.controller('SchemaRegistryConfigCtrl', SchemaRegistryConfigCtrl); ================================================ FILE: src/schema-registry/config/config.html ================================================
Url : {{schemaRegistryURL}} CONNECTIVITY ERROR
Global Compatibility level : {{config.compatibilityLevel}} change
schema-registry-ui: 0.9.5

Powered by Landoop

================================================ FILE: src/schema-registry/export/export.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var JSZip = require('jszip'); var FileSaver = require('file-saver'); var ExportSchemasCtrl = function ($rootScope, $scope, env, SchemaRegistryFactory) { $scope.$on('$routeChangeSuccess', function () { $scope.cluster = env.getSelectedCluster().NAME;//$routeParams.cluster; }); var d = new Date(); $scope.date = '-' + d.getDate() + '' + (d.getMonth() + 1) + '' + d.getFullYear() + '' + d.getHours() + '' + d.getMinutes(); var script = '\n\n # To restore the schema - edit & run the following \n # cat "$schema" | sed -e \'s/"/\\"/g\' -e \'s/\\n//g\' -e \'1s/^/{ "schema": "/\' -e \'$s/$/"}/\' | curl -XPOST -i -H "Content-Type: application/vnd.schemaregistry.v1+json" --data @- "SCHEMA_REGISTRY_URL/subjects/$SUBJECT/versions" \n # done'; var latestZip = new JSZip(); var allZip = new JSZip(); if ($rootScope.Cache && $rootScope.Cache.length > 0) { angular.forEach($scope.allSchemas, function (schema, key) { latestZip.file(schema.subjectName + '.' + schema.version + '.json', schema.schema); }) } else { SchemaRegistryFactory.refreshLatestSubjectsCACHE().then(function (latestSchemas) { angular.forEach(latestSchemas, function (schema) { latestZip.file(schema.subjectName + '.' + schema.version + '.json', schema.schema); }) }) } $scope.$watch(function () { return $rootScope.showSpinner; }, function () { $scope.allSchemas = SchemaRegistryFactory.getAllSchemas($rootScope.Cache) }, true); $scope.$watch(function () { return $rootScope.allSchemasCache; }, function () { angular.forEach($rootScope.allSchemasCache, function (schema) { allZip.file(schema.subject + '.' + schema.version + '.json', schema.schema); }) }, true); function bindEvent(el, eventName, eventHandler) { if (el.addEventListener) { // standard way el.addEventListener(eventName, eventHandler, false); } else if (el.attachEvent) { // old IE el.attachEvent('on' + eventName, eventHandler); } } function downloadLatestSchemasWithBlob() { latestZip.generateAsync({type: "blob"}).then(function (blob) { FileSaver.saveAs(blob, "latestSchemas" + $scope.date + ".zip"); }, function (err) { latestLink.innerHTML += " " + err; }); return false; } var latestLink = document.getElementById('latestSchemas'); if (JSZip.support.blob) { bindEvent(latestLink, 'click', downloadLatestSchemasWithBlob); } else { latestLink.innerHTML += " (not supported on this browser)"; } function downloadAllSchemasWithBlob() { allZip.generateAsync({type: "blob"}).then(function (blob) { FileSaver.saveAs(blob, "allSchemas" + $scope.date + ".zip"); }, function (err) { allLink.innerHTML += " " + err; }); return false; } var allLink = document.getElementById('allSchemas'); if (JSZip.support.blob) { bindEvent(allLink, 'click', downloadAllSchemasWithBlob); } else { allLink.innerHTML += " (not supported on this browser)"; } }; ExportSchemasCtrl.$inject = ['$rootScope', '$scope', 'env', 'SchemaRegistryFactory', '$location']; angularAPP.controller('ExportSchemasCtrl', ExportSchemasCtrl); ================================================ FILE: src/schema-registry/export/export.html ================================================

Export Schemas

Number of schemas: {{Cache.length}}
Total number of schemas (including all versions): {{allSchemasCache.length}}
Export latest only version of each schema
download latest download all
================================================ FILE: src/schema-registry/home/home.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var HomeCtrl = function ($log, SchemaRegistryFactory, toastFactory, $scope, env) { $log.info("Starting schema-registry controller - home"); $scope.readonlyMode = env.readonlyMode(); toastFactory.hideToast(); $scope.$watch(function () { return env.getSelectedCluster().NAME; }, function () { $scope.cluster = env.getSelectedCluster().NAME; }, true); }; HomeCtrl.$inject = ['$log', 'SchemaRegistryFactory', 'toastFactory', '$scope', 'env']; angularAPP.controller('HomeCtrl', HomeCtrl); ================================================ FILE: src/schema-registry/home/home.html ================================================

Welcome to the schema registry ui


select a schema or create a new one


================================================ FILE: src/schema-registry/index.js ================================================ require("./home/home.controller"); require("./config/config.controller"); require("./view/view.controller"); require("./export/export.controller"); require("./list/list.controller"); require("./new/new.controller"); ================================================ FILE: src/schema-registry/list/list.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var SubjectListCtrl = function ($scope, $rootScope, $log, $mdMedia, SchemaRegistryFactory, env) { $log.info("Starting schema-registry controller : list ( initializing subject cache )"); $scope.readonlyMode = env.readonlyMode(); function addCompatibilityValue() { angular.forEach($rootScope.allSchemas, function (schema) { SchemaRegistryFactory.getSubjectConfig(schema.subjectName).then( function success(config) { schema.compatibilityLevel = config.compatibilityLevel; }, function errorCallback(response) { $log.error(response); }); }) } /* * Watch the 'newCreated' and update the subject-cache accordingly */ $scope.$watch(function () { return $rootScope.listChanges; }, function (a) { if (a !== undefined && a === true) { loadCache(); //When new is created refresh the list $rootScope.listChanges = false; } }, true); // listen for the event in the relevant $scope $scope.$on('newEvolve', function (event, args) { loadCache(); }); $scope.$watch(function () { return env.getSelectedCluster().NAME; }, function (a) { $scope.cluster = env.getSelectedCluster().NAME; $scope.readonlyMode = env.readonlyMode(); loadCache(); //When cluster change, reload the list }, true); /** * Load cache by fetching all latest subjects */ function loadCache() { $rootScope.allSchemas = []; var promise = SchemaRegistryFactory.refreshLatestSubjectsCACHE(); promise.then(function (cachedData) { $rootScope.allSchemas = cachedData; addCompatibilityValue(); }, function (reason) { $log.error('Failed at loadCache : ' + reason); }, function (update) { $log.debug('Got notification: ' + update); }); } var itemsPerPage = (window.innerHeight - 355) / 48; Math.floor(itemsPerPage) < 3 ? $scope.itemsPerPage = 3 : $scope.itemsPerPage = Math.floor(itemsPerPage); }; SubjectListCtrl.$inject = ['$scope', '$rootScope', '$log', '$mdMedia', 'SchemaRegistryFactory', 'env']; angularAPP.controller('SubjectListCtrl', SubjectListCtrl); //In small devices the list is hidden // $scope.$mdMedia = $mdMedia; // $scope.$watch(function () { // return $mdMedia('gt-sm'); // }, function (display) { // $rootScope.showList = display; // }); ================================================ FILE: src/schema-registry/list/list.html ================================================

{{allSchemas.length}} Schemas

New

{{schema.subjectName}}

Compatibility level is set to {{schema.compatibilityLevel}}

v.{{schema.version}}
================================================ FILE: src/schema-registry/new/new.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var NewSubjectCtrl = function ($scope, $route, $rootScope, $http, $log, $q, $location, UtilsFactory, SchemaRegistryFactory, toastFactory, env) { $log.debug("NewSubjectCtrl - initiating"); $scope.$on('$routeChangeSuccess', function () { $scope.cluster = env.getSelectedCluster().NAME;//$routeParams.cluster; }); $scope.noSubjectName = true; $rootScope.listChanges = false; toastFactory.hideToast(); $scope.showSimpleToast = function (message) { toastFactory.showSimpleToast(message) }; $scope.showSimpleToastToTop = function (message) { toastFactory.showSimpleToastToTop(message); }; $scope.hideToast = function () { toastFactory.hide(); }; $scope.$watch(function () { return $scope.text; }, function (a) { $scope.allowCreateOrEvolution = false; updateCurl(); }, true); $scope.$watch(function () { return $scope.newAvroString; }, function (a) { $scope.allowCreateOrEvolution = false; updateCurl(); }, true); /** * Create filter function for a query string */ function createFilterFor(query) { var lowercaseQuery = angular.lowercase(query); return function filterFn(state) { return (state.value.indexOf(lowercaseQuery) === 0); }; } /** * Possibilities * 1. no-subject-name -> User has not filled-in the subjectName * 2. not-json -> Schema is invalid Json * 3. new-schema -> Schema is Json + subject does not exist */ $scope.allowCreateOrEvolution = false; var validTypes = ["null", "double", "string", "record", "int", "float", "long", "array", "boolean", "enum", "map", "fixed", "bytes", "type"]; var primitiveTypes = ["null", "boolean", "int", "long", "float", "double", "bytes", "string"]; function testCompatibility(subject, newAvroString) { if (env.readonlyMode()) { var deferred = $q.defer(); $scope.showSimpleToastToTop("Creation is not allowed in readonly mode"); deferred.resolve("readonly"); return deferred.promise; } $scope.notValidType = false; if (newAvroString === "null") { if (primitiveTypes.indexOf(newAvroString) === -1) { $scope.wrongType = newAvroString; $scope.notValidType = true; } } else { var a; try { a = JSON.parse(newAvroString); console.log("It's probably object, so checking types", a) } catch (e) { if (typeof(newAvroString) === "string") { if (primitiveTypes.indexOf(newAvroString) === -1) { $scope.wrongType = newAvroString; $scope.notValidType = true; } } } } var flattenObject = function (ob) { var toReturn = {}; for (var i in ob) { if (!ob.hasOwnProperty(i)) continue; if ((typeof ob[i]) === 'object') { var flatObject = flattenObject(ob[i]); for (var x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + '.' + x] = flatObject[x]; } } else { toReturn[i] = ob[i]; } } return toReturn; }; var obj = flattenObject(newAvroString); var typeKeysToCheck = Object.keys(obj) .reduce(function (typeKeys, key, idx) { // Check that this string has a type substring, if not, we don't have to validate if (key.indexOf('type') !== -1 && isPrimitiveTypeKey(key)) { typeKeys.push(key); } return typeKeys; }, []); var typeKeysToCheckLength = typeKeysToCheck.length; // Create for loop vars var i; var keyToCheck; /* * By iterating in a for loop, we can break out of an invalid key type found immediately. * That way the UI shows each wrong type one by one(if there are many) instead of just the last * one. */ for (i = 0; i < typeKeysToCheckLength; i++) { keyToCheck = typeKeysToCheck[i]; if (validTypes.indexOf(obj[keyToCheck]) < 0) { $scope.wrongType = obj[keyToCheck]; $scope.notValidType = true; break; } } function isKeyType(key) { return key === 'type'; } function checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart) { var isLastKeyPartNotANumber = isNaN(lastKeyPart); // If it is not a number, then make sure it's a type key if (isLastKeyPartNotANumber) { return isKeyType(lastKeyPart); } // If the last part was a number, is the next to last a type part? return isKeyType(nextToLastKeyPart); }; // Check if they key is actually a key that defines the primitive type function isPrimitiveTypeKey(key) { var keyToArray = key.split('.'); var keyToArrayLength = keyToArray.length; var lastKeyPart = keyToArray[keyToArrayLength - 1]; var nextToLastKeyPart = keyToArray[keyToArrayLength - 2]; return keyToArrayLength === 1 || checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart); } newAvroString = JSON.stringify(newAvroString); var deferred = $q.defer(); if ((subject === undefined) || subject.length === 0) { $scope.showSimpleToastToTop("Please fill in the subject name"); // (1.) $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; deferred.resolve("no-subject-name"); } else { if ($scope.notValidType) { $scope.showSimpleToastToTop($scope.wrongType + " is not valid"); // (2.) $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; deferred.resolve("not-valid-type") } else if (!UtilsFactory.IsJsonString(newAvroString)) { $scope.showSimpleToastToTop("This schema is not valid"); // (2.) $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; deferred.resolve("not-json") } else { var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject); if (latestKnownSubject === undefined) { // (3.) $scope.createOrEvolve = "Create new schema"; $scope.showSimpleToast("This will be a new Subject"); $scope.allowCreateOrEvolution = true; $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; $log.info('Valid schema'); deferred.resolve("new-schema") } else { SchemaRegistryFactory.testSchemaCompatibility($scope.text, $scope.newAvroString).then( function success(data) { $log.info("Success in testing schema compatibility " + data); // (4.) $scope.allowCreateOrEvolution = false; $scope.showSimpleToastToTop("Schema exists, please select a unique subject name"); $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; deferred.resolve("non-compatible") }, function failure(data) { $scope.showSimpleToastToTop("Failure with - " + data); deferred.resolve("failure"); } ); } } } return deferred.promise; } /** * Update curl to reflect selected subject + schema */ function updateCurl() { //$log.debug("Updating curl commands accordingly"); var remoteSubject = "FILL_IN_SUBJECT"; if (($scope.text !== undefined) && $scope.text.length > 0) { remoteSubject = $scope.text; } if (JSON.stringify($scope.newAvroString)) { var curlPrefix = 'curl -vs --stderr - -XPOST -i -H "Content-Type: application/vnd.schemaregistry.v1+json" --data '; $scope.curlCommand = "\n" + "// Register new schema\n" + curlPrefix + "'" + '{"schema":"' + JSON.stringify($scope.newAvroString).replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "' " + env.SCHEMA_REGISTRY() + "/subjects/" + remoteSubject + "/versions"; } } /** * Private method to register-new-schema */ function registerNewSchemaPrivate(newSubject, newAvro) { var deferred = $q.defer(); SchemaRegistryFactory.registerNewSchema(newSubject, newAvro).then( function success(id) { $log.info("Success in registering new schema " + id); var schemaId = id; $scope.showSimpleToastToTop("Schema ID : " + id); $rootScope.listChanges = true; // trigger a cache re-load $location.path('/cluster/' + $scope.cluster + '/schema/' + newSubject + '/version/latest'); deferred.resolve(schemaId); }, function error(data, status) { $log.info("Error on schema registration : " + JSON.stringify(data)); var errorMessage = data.message; $scope.showSimpleToastToTop(errorMessage); if (status >= 400) { $log.debug("Schema registrations is not allowed " + status + " " + data); } else { $log.debug("Schema registration failure: " + JSON.stringify(data)); } deferred.reject(errorMessage); }); return deferred.promise; } $scope.testCompatibility = function () { return testCompatibility($scope.text, $scope.newAvroString); }; /** * How to responde to register new schema clicks */ $scope.registerNewSchema = function () { var subject = $scope.text; testCompatibility(subject, $scope.newAvroString).then( function success(response) { // no-subject-name | not-json | new-schema | compatible | non-compatible | failure | readonly switch (response) { case "no-subject-name": case "not-json": case "not-valid-type": case "failure": case "non-compatible": case "readonly": $log.debug("registerNewSchema - cannot do anything more with [ " + response + " ]"); break; case 'new-schema': var schemaString = ''; if (typeof $scope.newAvroString !== 'string') schemaString = JSON.stringify($scope.newAvroString); else schemaString = $scope.newAvroString; registerNewSchemaPrivate(subject, schemaString).then( function success(newSchemaId) { $log.info("New subject id after posting => " + newSchemaId); }, function failure(data) { $log.error("peiler2=>" + data); $scope.allowCreateOrEvolution = false; $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; }); break; case 'compatible': $log.info("Compatibility [compatible]"); // TODO var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject); if (latestKnownSubject === undefined) { $log.error("This should never happen.") } else { $log.info("Existing schema id = " + latestKnownSubject.version); registerNewSchemaPrivate(subject, $scope.newAvroString).then( function success(newSchemaId) { $log.info("New subject id after posting => " + newSchemaId); if (latestKnownSubject.version === newSchemaId) { toastFactory.showSimpleToastToTop("The schema you posted was same to the existing one") } }, function failure(data) { $log.error("peiler=>" + data); $scope.allowCreateOrEvolution = false; $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; }); break; } default: $log.warn("Should never come here " + response); } }, function failure(data) { if (data.error_code === 500) { $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; toastFactory.showSimpleToastToTop("Not a valid avro"); } else { $log.error("Could not test compatibilitydasdas", data); } }); }; // $scope.createOrEvolve = "Create new schema"; // $scope.allowCreateOrEvolution = true; // $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; // $http(postSchemaRegistration) // $http.get(env.SCHEMA_REGISTRY() + '/subjects/' + $scope.text + '/versions/latest') // .success(function (data) { // $log.info("Schema succesfully registered: " + JSON.stringify(data)); // $location.path('/subjects/' + data.subject + '/version/' + data.version); // }); // } // When the 'Ace' of the schema/new is loaded $scope.newSchemaAceLoaded = function (_editor) { $scope.editor = _editor; $scope.editor.$blockScrolling = Infinity; $scope.aceSchemaSession = _editor.getSession(); // we can get data on changes now var lines = $scope.newAvroString.split("\n").length; // TODO : getScalaFiles($scope.aceString); // Add one extra line for each command > 110 characters angular.forEach($scope.newAvroString.split("\n"), function (line) { lines = lines + Math.floor(line.length / 110); }); if (lines <= 1) { lines = 10; } _editor.setOptions({ minLines: lines + 1, maxLines: lines + 1, highlightActiveLine: false }); updateCurl(); }; // When the 'Ace' of the schema/new is CHANGED (!) $scope.newSchemaAceChanged = function (_editor) { $scope.editor = _editor; updateCurl(); }; // When the 'Ace' of the curl command is loaded $scope.curlCommandAceLoaded = function (_editor) { $scope.editor = _editor; $scope.editor.$blockScrolling = Infinity; }; $scope.newAvroString = angular.toJson( { "type": "record", "name": "evolution", "doc": "This is a sample Avro schema to get you started. Please edit", "namespace": "com.landoop", "fields": [{"name": "name", "type": "string"}, {"name": "number1", "type": "int"}, { "name": "number2", "type": "float" }] }, true); }; NewSubjectCtrl.$inject = ['$scope', '$route', '$rootScope', '$http', '$log', '$q', '$location', 'UtilsFactory', 'SchemaRegistryFactory', 'toastFactory', 'env'] angularAPP.controller('NewSubjectCtrl', NewSubjectCtrl); ================================================ FILE: src/schema-registry/new/new.html ================================================

New Subject

{{item.display}}
This field is required
Schema: CURL command:
schema / curl schema / curl
** This is a sample schema. Please edit! ** VALIDATE {{createOrEvolve}}
================================================ FILE: src/schema-registry/pagination/dirPaginationControlsTemplate.html ================================================
{{pageNumber}}
================================================ FILE: src/schema-registry/view/view.controller.js ================================================ var angular = require('angular'); var angularAPP = angular.module('angularAPP'); var ace = require('brace'); require('brace/mode/json'); require('brace/mode/batchfile'); require('brace/theme/chrome'); require('brace/worker/json'); require.context("brace/ext/", false); //var Range = ace.acequire('ace/range').Range; var SubjectsCtrl = function ($rootScope, $scope, $route, $routeParams, $log, $location, $mdDialog, SchemaRegistryFactory, UtilsFactory, toastFactory, Avro4ScalaFactory, env) { $log.info("Starting schema-registry controller: view ( " + $routeParams.subject + "/" + $routeParams.version + " )"); $rootScope.listChanges = false; toastFactory.hideToast(); /** * At start-up - get the entire subject `History` */ SchemaRegistryFactory.getSubjectHistory($routeParams.subject).then( function success(data) { $scope.completeSubjectHistory = SchemaRegistryFactory.getSubjectHistoryDiff(data); //$log.warn("Diff is:"); //$log.warn(JSON.stringify($scope.completeSubjectHistory)); } ); $scope.allowSchemaDeletion = env.allowSchemaDeletion(); $scope.readonlyMode = env.readonlyMode(); $scope.allowTransitiveCompatibilities = env.allowTransitiveCompatibilities(); $scope.$watch(function () { return $scope.aceString; }, function (a) { $scope.isAvroUpdatedAndCompatible = false; }, true); SchemaRegistryFactory.getSubjectConfig($routeParams.subject).then( function success(config) { $scope.compatibilitySelect = config.compatibilityLevel; $scope.existingValue = config.compatibilityLevel; }, function errorCallback(response) { $log.error(response); }); SchemaRegistryFactory.getGlobalConfig().then( function success(config) { $scope.globalConfig = config.compatibilityLevel; }, function failure(response) { $log.error("Failure with : " + JSON.stringify(response)); $scope.connectionFailure = true; }); /** * At start-up do something more ... */ function getSchema(){ var promise = SchemaRegistryFactory.getSubjectAtVersion($routeParams.subject, $routeParams.version); promise.then(function (selectedSubject) { $log.info('Success fetching [' + $routeParams.subject + '/' + $routeParams.version + '] with MetaData'); $rootScope.subjectObject = selectedSubject; $scope.arraySchema = typeof $rootScope.subjectObject.Schema[0] !== 'undefined' ? true : false; $scope.tableWidth = 100 / $scope.subjectObject.Schema.length; $rootScope.schema = selectedSubject.Schema.fields; $scope.aceString = angular.toJson(selectedSubject.Schema, true); $scope.aceStringOriginal = $scope.aceString; $scope.aceReady = true; SchemaRegistryFactory.getSubjectsVersions($routeParams.subject).then( function success(allVersions) { var otherVersions = []; angular.forEach(allVersions, function (version) { if (version !== $rootScope.subjectObject.version) { otherVersions.push(version); } }); $scope.otherVersions = otherVersions; $scope.multipleVersionsOn = $scope.otherVersions.length > 0; // TODO remove }, function failure(response) { // TODO } ) }, function (reason) { $log.error('Failed: ' + reason); }, function (update) { $log.info('Got notification: ' + update); }); } if ($routeParams.subject && $routeParams.version) { getSchema(); } $scope.$on('$routeChangeSuccess', function () { $scope.cluster = env.getSelectedCluster().NAME;//$routeParams.cluster; $scope.maxHeight = window.innerHeight - 215; if ($scope.maxHeight < 310) { $scope.maxHeight = 310 } }); $scope.updateCompatibility = function (compatibilitySelect) { SchemaRegistryFactory.updateSubjectCompatibility($routeParams.subject, compatibilitySelect).then( function success() { $scope.existingValue = compatibilitySelect; $rootScope.listChanges = true; // trigger a cache re-load $scope.success = true; }); }; $scope.aceString = ""; $scope.aceStringOriginal = ""; $scope.multipleVersionsOn = false; $scope.isAvroUpdatedAndCompatible = false; $scope.testAvroCompatibility = function () { $log.debug("Testing Avro compatibility"); if ($scope.aceString === $scope.aceStringOriginal) { toastFactory.showSimpleToastToTop("You have not changed the schema"); } else { if (UtilsFactory.IsJsonString($scope.aceString)) { $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; $log.debug("Edited schema is a valid json and is a augmented"); SchemaRegistryFactory.testSchemaCompatibility($routeParams.subject, $scope.aceString).then( function success(result) { if (result) { $log.info("Schema is compatible"); $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; toastFactory.showSimpleToast("You can now evolve the schema"); $scope.isAvroUpdatedAndCompatible = true; } else { $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; toastFactory.showLongToast("This schema is incompatible with the latest version"); } }, function failure(data) { if (data.error_code === 500) { $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; toastFactory.showSimpleToastToTop("Not a valid avro"); } else { $log.error("Could not test compatibilitydasdas", data); } }); } else { $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; toastFactory.showLongToast("Invalid Avro"); } } }; $scope.evolveAvroSchema = function () { if ($scope.aceString !== $scope.aceStringOriginal && UtilsFactory.IsJsonString($scope.aceString)) { SchemaRegistryFactory.testSchemaCompatibility($routeParams.subject, $scope.aceString).then( function success(result) { var latestSchema = SchemaRegistryFactory.getLatestSubjectFromCache($routeParams.subject); $log.warn("peiler"); $log.warn(latestSchema); var latestID = latestSchema.id; SchemaRegistryFactory.registerNewSchema($routeParams.subject, $scope.aceString).then( function success(schemaId) { $log.info("Latest schema ID was : " + latestID); $log.info("New schema ID is : " + schemaId); if (latestID === schemaId) { toastFactory.showSimpleToastToTop(" Schema is the same as latest ") } else { toastFactory.showSimpleToastToTop(" Schema evolved to ID: " + schemaId); $rootScope.$broadcast('newEvolve'); $location.path('/cluster/' + $scope.cluster + '/schema/' + $routeParams.subject + '/version/latest'); $route.reload(); } }, function failure(data) { } ); }, function failure(data) { } ); } else { $scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)"; toastFactory.showLongToast("Invalid Avro"); } }; $scope.isAvroAceEditable = false; $scope.aceBackgroundColor = "white"; $scope.cancelEditor = function () { $scope.selectedIndex = 0; $log.info("Canceling editor"); $scope.maxHeight = $scope.maxHeight + 64; $scope.form.json.$error.validJson = false; $scope.aceBackgroundColor = "white"; toastFactory.hideToast(); $log.info("Setting " + $scope.aceStringOriginal); $scope.isAvroAceEditable = false; $scope.isAvroUpdatedAndCompatible = false; $scope.aceString = $scope.aceStringOriginal; $scope.aceSchemaSession.setValue($scope.aceString); }; $scope.toggleEditor = function () { $scope.isAvroAceEditable = !$scope.isAvroAceEditable; if ($scope.isAvroAceEditable) { $scope.maxHeight = $scope.maxHeight - 64; toastFactory.showLongToast("You can now edit the schema"); $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)"; } else { $scope.aceBackgroundColor = "white"; toastFactory.hideToast(); } }; /************************* md-table ***********************/ $scope.tableOptions = { rowSelection: false, multiSelect: false, autoSelect: false, decapitate: false, largeEditDialog: false, boundaryLinks: false, limitSelect: true, pageSelect: true }; $scope.query = { order: 'name', limit: 100, page: 1 }; // This one is called each time - the user clicks on an md-table header (applies sorting) $scope.logOrder = function (a) { // $log.info("Ordering event " + a); sortSchema(a); }; function sortSchema(type) { var reverse = 1; if (type.indexOf('-') === 0) { // remove the - symbol type = type.substring(1, type.length); reverse = -1; } // $log.info(type + " " + reverse); $scope.schema = UtilsFactory.sortByKey($scope.schema, type, reverse); } function getScalaFiles(xx) { var scala = Avro4ScalaFactory.getScalaFiles(xx); $log.error("SCALA-> " + scala); } $scope.otherTabSelected = function () { $scope.hideEdit = true; }; /************************* md-table ***********************/ $scope.editor; // When the 'Ace' schema/view is loaded $scope.viewSchemaAceLoaded = function (_editor) { // $log.info("me"); $scope.editor = _editor; $scope.editor.$blockScrolling = Infinity; $scope.aceSchemaSession = _editor.getSession(); // we can get data on changes now $scope.editor.getSession().setUseWrapMode(true); $scope.editor.getSession().setBehavioursEnabled(false); var lines = $scope.aceString.split("\n").length; // TODO : getScalaFiles($scope.aceString); // Add one extra line for each command > 110 characters angular.forEach($scope.aceString.split("\n"), function (line) { lines = lines + Math.floor(line.length / 110); }); if (lines <= 1) { lines = 10; } // $log.warn("Lines loaded for curl create connector -> " + lines + "\n" + $scope.curlCommand); _editor.setOptions({ minLines: lines, maxLines: lines, highlightActiveLine: false }); // var _renderer = _editor.renderer; // _renderer.animatedScroll = false; }; // When the 'Ace' schema/view is CHANGED $scope.viewSchemaAceChanged = function (_editor) { $scope.editor = _editor; var aceString = $scope.aceSchemaSession.getDocument().getValue(); // $log.warn("LOADED ...."); // Highlight differences //TODO //$scope.aceSchemaSession.addMarker(new Range(2, 5, 4, 16), "ace_diff_new_line", "fullLine"); $scope.aceString = aceString; }; $scope.showTree = function (keyOrValue) { return !(angular.isNumber(keyOrValue) || angular.isString(keyOrValue) || (keyOrValue === null)); } $scope.askForConfirmToDelete = function (version){ $scope.versionToBeDeleted = version; $scope.showDeleteConfirmation = true; } $scope.deleteSchema = function (versionToBeDeleted) { $scope.showDeleteConfirmation =false; var subjectName = $routeParams.subject if(versionToBeDeleted) { SchemaRegistryFactory.deleteVersionOfSubject(subjectName, versionToBeDeleted).then(function(){ $rootScope.listChanges = true; toastFactory.showLongToast(subjectName + " version " + versionToBeDeleted + " deleted successfully"); getSchema() $location.path('/cluster/'+ $scope.cluster + '/schema/' + subjectName + '/version/latest') }) } else { SchemaRegistryFactory.deleteSubject(subjectName).then(function(){ $rootScope.listChanges = true; toastFactory.showLongToast(subjectName + "deleted successfully"); $location.path('/cluster/'+ $scope.cluster) }) } } }; SubjectsCtrl.$inject = ['$rootScope', '$scope', '$route', '$routeParams', '$log', '$location', '$mdDialog', 'SchemaRegistryFactory', 'UtilsFactory', 'toastFactory', 'Avro4ScalaFactory', 'env'] angularAPP.controller('SubjectsCtrl', SubjectsCtrl); //end of controller // Useful for browsing through different versions of a schema angularAPP.directive('clickLink', ['$location', function ($location) { return { link: function (scope, element, attrs) { element.on('click', function () { scope.$apply(function () { $location.path(attrs.clickLink); }); }); } } }]); ================================================ FILE: src/schema-registry/view/view.html ================================================

{{subjectObject.subjectName}}

SCHEMA ID: {{subjectObject.id}}

{{subjectObject.subjectName}}

SCHEMA ID: {{subjectObject.id}}

version {{subjectObject.version}} version {{subjectObject.version}} version {{version}}
EDIT CANCEL DELETE Delete Latest Version Delete Selected Version ({{subjectObject.version}}) Delete Subject
Schema {{subjectObject.subjectName}} version: {{versionToBeDeleted}} will be deleted.
DELETE CANCEL
Schema
- Syntax Error
VALIDATE EVOLVE SCHEMA
Info type: {{subjectObject.Schema.type}} name: {{subjectObject.Schema.name}} namespace: {{subjectObject.Schema.namespace}}

{{subjectObject.Schema.doc}}

Name Type Default Documentation
{{s.name}}
{{s.type}}
{{s.default}} {{s.doc}}
type: {{schemas.type}} name: {{schemas.name}} namespace: {{schemas.namespace}}

{{subjectObject.Schema.doc}}

Name Type Default Documentation
{{s.name}}
{{s.type}}
{{s.default}} {{s.doc}}
Config

Current compatibility for {{subjectObject.subjectName}} : {{ existingValue }}
Schema {{subjectObject.subjectName}} uses the global compatibility level [{{globalConfig}}]
Change compatibility level to:

NONE FULL FORWARD BACKWARD FULL TRANSITIVE FORWARD TRANSITIVE BACKWARD TRANSITIVE Update
Successfully changed compatibility level to {{compatibilitySelect}}
History
Version {{x.version}} (Schema ID: {{x.id}})

            
Version 1 (Schema ID: {{completeSubjectHistory[0].id}})

          
================================================ FILE: webpack.config.js ================================================ const path = require("path"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const CleanWebpackPlugin = require('clean-webpack-plugin'); const ENV = process.env.NODE_ENV || 'development'; const isProd = ENV === 'production'; console.log('Building for ' + ENV); const config = { watch: !isProd, devtool: isProd ? "cheap-source-map" : "cheap-module-source-map", entry: { app: "./src/app.js" }, output: { filename: "js/[name].[hash].bundle.js", path: path.resolve(__dirname, "dist"), publicPath: "" }, module: { rules: [ { test: /\.(js|jsx)$/, loader: "babel-loader", exclude: /node_modules/, query: { plugins: ['transform-decorators-legacy', 'transform-runtime', 'transform-object-rest-spread', 'transform-class-properties'], presets: [['es2015', { modules: false }], 'stage-1'] } }, { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }, { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff&outputPath=&publicPath=../../" }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" }, { test: /\.html$/, loader: "html-loader" } ] }, plugins: [ new ExtractTextPlugin({ filename: "assets/css/[name].[contenthash].css" }), new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function (module) { return module.context && module.context.indexOf("node_modules") !== -1; } }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest" }), new HtmlWebpackPlugin({ template: "./src/index.html" }), new CopyWebpackPlugin([{ from: __dirname + '/src/assets', to: path.resolve(__dirname, "dist/src/assets") }]), new CopyWebpackPlugin([{ from: __dirname + '/env.js', to: path.resolve(__dirname, "dist/") }]), new webpack.HotModuleReplacementPlugin() ], resolve: { alias: { } }, devServer: { host: "localhost", port: "8080", contentBase: path.resolve(__dirname, "dist"), //compress: true, historyApiFallback: true, hot: true, inline: true, https: false, noInfo: true } }; if (isProd) { config.plugins.push( new CleanWebpackPlugin(['dist']), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, screw_ie8: true, conditionals: true, unused: true, comparisons: true, sequences: true, dead_code: true, evaluate: true, join_vars: true, if_return: true }, output: { comments: false } })) } module.exports = config;