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
[](https://github.com/landoop/schema-registry-ui/releases/latest)
[](https://hub.docker.com/r/landoop/schema-registry-ui/)
[](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
<img src="http://www.landoop.com/images/landoop-dark.svg" width="13" /> 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 <marios@landoop.com>
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 ##
[](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 <https://schema-registry-ui.demo.lenses.io>
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 <https://github.com/Landoop/schema-registry-ui/tree/master/docker>"
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 <<EOF >>/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 <<EOF >/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 <<EOF >>/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 <<EOF
Note: if you use a PORT lower than 1024, please note that schema-registry-ui can
now run under any user. In the future a non-root user may become the default.
In this case you will have to explicitly allow binding to such ports, either by
setting the root user or something like '--sysctl net.ipv4.ip_unprivileged_port_start=0'.
Activating privacy features... done.
http://0.0.0.0:$PORT
EOF
} 1>&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 <div> - 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
================================================
<!DOCTYPE html>
<html>
<head>
<title>Schema Registry UI</title>
<link rel="shortcut icon" href="./src/assets/icons/favicon.png">
<meta charset=utf-8/>
<meta name="viewport" content="width=device-width">
<meta name="keywords" content="kafka,schema registry,avro,landoop">
<meta name="description" content="Kafka Schema Registry UI">
<meta name="author" content="http://landoop.com">
<script src="env.js"></script>
</head>
<body ng-app="angularAPP" ng-cloak>
<!--Header Starts here-->
<header class="header" ng-controller="HeaderCtrl" style="background-color:{{color}}">
<div layout="row" layout-xs="column">
<div flex>
<span class="title"><a style="color:#fff;" class="md-padding" href="{{cluster ? '#/cluster/'+cluster.NAME : '#/'}}">SCHEMA REGISTRY</a></span>
</div>
<a style="" class="exportSchemas" ng-href="#/cluster/{{cluster.NAME}}/export">
<i class="fa fa-download" aria-hidden="true"></i> <b>EXPORT SCHEMAS</b>
</a>
<label ng-show="!missingEnvJS && clusters.length > 1" class="selectClusterLabel"><b>SELECT CLUSTER :</b></label>
<md-input-container ng-show="!missingEnvJS && clusters.length > 1" class="selectCluster">
<md-select ng-model="connectEndPoint">
<md-option ng-repeat="connectEndPoint in clusters track by $index" ng-click="updateEndPoint(connectEndPoint.NAME)" value="{{connectEndPoint.NAME}}"
ng-selected="{{cluster.NAME == connectEndPoint.NAME}}">{{connectEndPoint.NAME}}</md-option>
</md-select>
</md-input-container>
</div>
</header>
<div layout="row" flex="100" ng-show="missingEnvJS" ng-cloak>
<md-card>
<md-card-content>
<h3 style="font-weight:300; color:red"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> Missing Cluster Configuration</h3>
<br> In order to configure <code>schema-registry-ui</code> you need to add <code>env.js</code> file in the
root directory of the app.
<br> Example <b>env.js</b> structure:
<pre>
<code>
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
}
];
</code>
</pre>
</md-card-content>
</md-card>
</div>
<!--Main Content Starts here-->
<div layout="row" layout-margin layout-sm="column" layout-xs="column" ng-controller="SchemaRegistryConfigCtrl" ng-hide="missingEnvJS"
style="margin-bottom:0;">
<div flex-gt-sm="33">
<div ng-include="'list.html'"></div>
<br>
<div ng-include="'config.html'" hide-sm hide-xs></div>
</div>
<div flex-gt-sm="66" flex style="margin-bottom:0;">
<ng-view></ng-view>
<div ng-include="'config.html'" show-sm hide-gt-sm></div>
</div>
</div>
<!--CUSTOM-FOOTER-->
<!-- LENSES -->
<!-- analytics -->
<!--
<script src="bower_components/ace-builds/src-min-noconflict/ace.js"></script>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/angular-ui-ace/ui-ace.min.js"></script>
<script src="bower_components/spin.js/spin.min.js"></script>
<script src="bower_components/angular-spinner/angular-spinner.min.js"></script>
<script src="bower_components/angular-route/angular-route.min.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.min.js"></script>
<script src="bower_components/angular-material/angular-material.min.js"></script>
<script src="bower_components/angular-animate/angular-animate.min.js"></script>
<script src="bower_components/angular-aria/angular-aria.min.js"></script>
<script src="bower_components/angular-material-data-table/dist/md-data-table.min.js"></script>
<script src="bower_components/angular-diff-match-patch/angular-diff-match-patch.js"></script>
<script src="bower_components/ace-diff/libs/diff_match_patch.js"></script>
<script src="bower_components/angular-json-tree/dist/angular-json-tree.min.js"></script>
<script src="bower_components/jszip/dist/jszip.min.js"></script>
<script src="bower_components/jszip/vendor/FileSaver.js"></script>
<script src="bower_components/jszip-utils/dist/jszip-utils.min.js"></script>-->
<!-- build:js combined.js -->
<!--<script src="src/app.js"></script>
<script src="src/schema-registry/home/home.controller.js"></script>
<script src="src/schema-registry/config/config.controller.js"></script>
<script src="src/schema-registry/view/view.controller.js"></script>
<script src="src/schema-registry/export/export.controller.js"></script>
<script src="src/schema-registry/list/list.controller.js"></script>
<script src="src/schema-registry/new/new.controller.js"></script>
<script src="src/factories/dirPagination.js"></script>
<script src="src/factories/utils-factory.js"></script>
<script src="src/factories/schema-registry-factory.js"></script>
<script src="src/factories/avro4s-factory.js"></script>
<script src="src/factories/env.factory.js"></script>
<script src="src/factories/toast-factory.js"></script>-->
<!-- endbuild -->
</body>
</html>
================================================
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 = '<b>Backward compatibility (default)</b>:<br /> A new schema is backward compatible if it can be used to read the data written in all previous schemas. <br />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 = '<b>Forward compatibility</b>:<br /> A new schema is forward compatible if all previous schemas can read data written in this schema. <br />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 = "<b>Full compatibility</b>: A new schema is fully compatible if it's both backward and forward compatible.";
var noneText = "<b>No compatibility</b>: A new schema can be any schema as long as it's a valid Avro.";
var backward_transitive = "<b>Backward transitive</b>: Only available for schema registry 3.1.0 and above.<br />New schema is backward and forward compatible with all previously registered schemas.";
var forward_transitive = "<b>Forward transitive</b>: Only available for schema registry 3.1.0 and above.<br />All previously registered schemas can read data produced by the new schema.";
var full_transitive = "<b>Full transitive</b>: Only available for schema registry 3.1.0 and above.<br />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('<b>This will affect the default behaviour and all subjects/schemas that do not have a compatibility level explicitly defined.</b> <br /><br />' + 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
================================================
<md-card>
<md-content flex layout-padding style="padding:6px;padding-bottom:0">
<div>
<b>Url :</b> {{schemaRegistryURL}}
<span ng-show="connectionFailure" style="color:red">CONNECTIVITY ERROR</span><br>
<b>Global Compatibility level :</b>
<select ng-show="allowChanges && config.compatibilityLevel" ng-model='form' required ng-options='option as option for option in globalConfigOpts'></select>
<span ng-hide="allowChanges ">{{config.compatibilityLevel}}</span>
<md-button class="green md-small" style="padding: 0px 6px; line-height: 1.2;" ng-hide="form == config.compatibilityLevel" ng-click="updateGlobalConfig(form, $event);">change</md-button>
<br>
<b>schema-registry-ui:</b> 0.9.5
</div>
</md-content>
</md-card>
<div layout-margin flex>
<div flex="nogrow">
<img ng-src="src/assets/icons/landoop-dark.svg" style="width:20px;float: left;padding-right:5px;"></div>
<div flex>
<p class="md-caption">Powered by <a href="https://www.landoop.com/" target="_blank">Landoop</a></p>
</div>
</div>
================================================
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
================================================
<md-card>
<md-toolbar class="md-hue-2">
<!--<div class="md-toolbar-tools">-->
<div layout="row" layout-xs="column" layout-align="center center" layout-wrap>
<div flex>
<h3 class="md-toolbar-tools md-padding" hide-xs><i class="fa fa-download" aria-hidden="true" style="margin-right:7px"></i>Export Schemas
</h3>
</div>
</div>
</md-toolbar>
<md-content flex class="md-padding">
<div>
<div ng-show="isChecked" >Number of schemas: {{Cache.length}}</div>
<div ng-hide="isChecked" >Total number of schemas (including all versions): {{allSchemasCache.length}}</div>
<md-checkbox style="margin:10px" ng-model="isChecked" aria-label="only latest">
Export latest only version of each schema
</md-checkbox><br />
<md-button ng-show="isChecked" id="latestSchemas" class="btn btn-primary blue">download latest</md-button>
<md-button ng-hide="isChecked" ng-disabled="allSchemasCache.length < 1" id="allSchemas" class="btn btn-primary blue">download all</md-button>
</div>
<!--<div flex="2">-->
<!--<div class="md-padding">Total number of schemas (including all versions): {{allSchemas.length}}</div>-->
<!--<md-button ng-click="downloadAllSchemas(allSchemas)" download="allSchemas-{{date}}.txt" ng-href="{{curlsURL}}">Export all schemas</md-button>-->
<!--</div>-->
</md-content>
</md-card>
================================================
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
================================================
<div class="container-fluid-centered" style="height: 500px;text-align:center;">
<div class="row-fluid">
<div >
<p> Welcome to the <b>schema registry ui</b></p><br/>
<p> select a schema<span ng-hide="readonlyMode"> or <a ng-href="#/cluster/{{cluster}}/schema/new">create a new one</a></span></p><br/>
</div>
</div>
</div>
================================================
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
================================================
<md-card ng-controller="SubjectListCtrl" md-theme="{{ showDarkTheme ? 'dark-grey' : 'default' }}">
<md-toolbar class="md-hue-2">
<div class="md-toolbar-tools">
<h3>
<span>{{allSchemas.length}} Schemas</span>
</h3>
<span flex></span>
<a md-ink-ripple ng-hide="readonlyMode" ng-href="#/cluster/{{cluster}}/schema/new"
class="md-raised md-primary md-button md-ink-ripple blue"
type="button"
aria-label="new connector">New</a>
</div>
</md-toolbar>
<md-content style="background-color:white;overflow:hidden;">
<input ng-model="search" class="searchSchemas" placeholder="Search schemas">
<i class="fa fa-search" aria-hidden="true" style="right: 26px;font-size: 16px;position: absolute;top: 20px;color:#ddd"></i>
<md-list flex>
<md-list-item class="md-2-line shemaslistitem"
dir-paginate="schema in allSchemas | orderBy:'-schema.length' | filter : search | itemsPerPage: itemsPerPage"
href="#/cluster/{{cluster}}/schema/{{schema.subjectName}}/version/{{schema.version}}"
aria-label="{{schema.subjectName}}"
style="border-bottom: 1px solid rgba(0, 0, 0, .1)"
ng-class="{ 'selectedListItem': schema.subjectName == subjectObject.subjectName }">
<!-- cute / sexy schema bar -->
<div class="md-list-item-text" layout="column">
<h4>{{schema.subjectName}}</h4>
<p style="font-size:12px;" ng-if="schema.compatibilityLevel">
Compatibility level is set to {{schema.compatibilityLevel}}
</p>
</div>
<div ng-class="{ 'moreversions': schema.version > 1}"
class="md-raised md-warn md-button md-ink-ripple versionbox"
type="button" aria-label="Primary">
<span>v.{{schema.version}}</span>
</div>
</md-list-item>
</md-list>
<dir-pagination-controls max-size="6" style="margin:0;"></dir-pagination-controls>
</md-content>
</md-card>
================================================
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
================================================
<form name="postSchema" class="form-horizontal" style="width: 100%">
<!-- Main Card for new subject -->
<md-card md-theme-watch="">
<md-toolbar class="md-hue-2">
<div class="md-toolbar-tools">
<h3>
<span>New Subject</span>
</h3>
</div>
</md-toolbar>
<!--Content: Form-->
<md-card-content style="padding-bottom: 0px;">
<div layout="row">
<md-input-container flex style="margin:20px 0 0;">
<label style="color:#43687a;font-weight:bold;font-size:19px;">Subject Name</label>
<input ng-model="text" autofocus placeholder="my-new-topic-value" required>
<md-item-template>
<span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.display}}</span>
</md-item-template>
<div ng-messages="postSchema.autocomplete.$error">
<div ng-message="required">This field is required</div>
</div>
</input>
</md-input-container>
<div class="md-errors-spacer"></div>
</div>
<div layout="row" layout-align="center center" >
<div flex="80" >
<b style="color:#43687a" ng-hide="curlme">Schema:</b>
<b style="color:#43687a" ng-show="curlme">CURL command:</b>
</div>
<div flex layout="row" layout-align="end center">
<md-switch ng-model="curlme" ng-show="curlme" style="margin:0;color:#43687a">schema / <b>curl</b></md-switch>
<md-switch ng-model="curlme" ng-hide="curlme" style="margin:0;color:#43687a"><b>schema</b> / curl</md-switch>
</div>
</div>
<!-- data raw -->
<div
ng-style="{'background-color':aceBackgroundColor}"
ng-hide="curlme" id="newavro"
style="height:313px;background-color:rgba(0, 128, 0, 0.04)"
name="json"
ng-model="newAvroString"
ng-readonly="false"
ui-ace="{
mode: 'json',
firstLineNumber: 1,
onLoad: newSchemaAceLoaded,
onChange: newSchemaAceChanged,
showPrintMargin:false,
}"
valid-json
>
</div>
<div ng-show="curlme" id="curlcommand"
style="height:313px;"
ng-model="curlCommand" ng-readonly="true"
ui-ace="{
mode: 'batchfile',
useWrapMode: true,
firstLineNumber: 1,
onLoad: curlCommandAceLoaded,
showPrintMargin:false,
onChange: curlCommandAceChanged
}"
>
</div>
</md-card-content>
<!--Actions-->
<md-card-actions style="margin-left:16px;">
<i>** This is a sample schema. Please edit! **</i>
<md-button style="float:right;"
ng-disabled="postSchema.json.$error.validJson"
ng-hide="allowCreateOrEvolution"
ng-click="testCompatibility();"
ng-validate="noSubjectName"
class=" md-raised"
type="submit"
aria-label="Test schema compatibility">
VALIDATE
</md-button>
<md-button style="float:right;"
ng-disabled="postSchema.json.$error.validJson"
ng-show="allowCreateOrEvolution"
ng-click="registerNewSchema();"
ng-validate="noSubjectName"
class=" md-raised"
type="submit"
aria-label="Register new schema">
<i class="fa fa-floppy-o ng-scope" aria-hidden="true"></i>
{{createOrEvolve}}
</md-button><br />
</md-card-actions>
</md-card>
</form>
================================================
FILE: src/schema-registry/pagination/dirPaginationControlsTemplate.html
================================================
<section style="margin: 5px 10px 0;" layout= "row" layout-align= "center" ng-if= "1 < pages.length || !autoHide"
class= "pagination">
<md-button aria-label= "Previous page" ng-if= "boundaryLinks" ng-disabled= "pagination.current===1" ng-click=
"setCurrent(1)">
<ng-md-icon icon= "first_page">
</ng-md-icon>
</md-button>
<md-button aria-label= "First page" ng-if= "directionLinks" ng-disabled= "pagination.current===1" ng-click=
"setCurrent(pagination.current - 1)">
<ng-md-icon class= "fa fa-chevron-left">
</ng-md-icon>
</md-button>
<md-button ng-repeat= "pageNumber in pages track by tracker(pageNumber, $index)" ng-class=
"{'md-primary' : pagination.current==pageNumber}" ng-disabled= "pageNumber==='...'" ng-click= "setCurrent(pageNumber)">{{pageNumber}}
</md-button>
<md-button aria-label= "Last page" ng-if= "directionLinks" ng-disabled= "pagination.current===pagination.last" ng-click=
"setCurrent(pagination.current + 1)">
<ng-md-icon class= "fa fa-chevron-right">
</ng-md-icon>
</md-button>
<md-button ng-if= "boundaryLinks" ng-disabled= "pagination.current===pagination.last" ng-click=
"setCurrent(pagination.last)">
<ng-md-icon icon= "last_page">
</ng-md-icon>
</md-button>
</section>
================================================
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
================================================
<md-card ng-show="subjectObject">
<md-toolbar class="md-hue-2">
<!--<div class="md-toolbar-tools">-->
<div layout="row" layout-xs="column" layout-align="center center" layout-wrap>
<div flex>
<h3 class="md-toolbar-tools" style="padding:0px;" hide-xs>
<div layout="row" style="height: 64px;">
<md-icon class="md-avatar-icon" md-svg-icon="src/assets/icons/avro.svg"
style="height: 40px;width: 40px;padding: 5px;background-color: rgba(255,255,255,0.87);border-radius: 60px;margin-left: 10px;margin-right: 10px;">
</md-icon>
<!--<md-tooltip md-direction="top" style="margin-top:15px;margin-left:-20px">-->
<!--Schema [{{subjectObject.subjectName}}]-->
<!--</md-tooltip>-->
<div layout="column" style="padding-top: 12px;padding-bottom: 12px;">
<div flex>{{subjectObject.subjectName}}</div>
<div flex>
<span>
<p class="md-caption"
style="margin-top:10px;margin-bottom:0px;">
SCHEMA ID: {{subjectObject.id}}
</p>
</span>
</div>
</div>
</div>
</h3>
<!--Mobile version only-->
<h3 class="md-toolbar-tools" style="margin-top:10px;text-align:center;" hide-gt-xs>
<div layout="column">
<div flex><span style="color: black;">{{subjectObject.subjectName}}</span></div>
<div flex>
<span>
<p class="md-caption"
style="margin-top:10px;margin-bottom:0px; text-align:center;">
SCHEMA ID: {{subjectObject.id}}
</p>
</span>
</div>
</div>
</h3>
<span flex></span>
</div>
<div flex-gt-sm="nogrow" flex>
<md-button ng-hide="multipleVersionsOn"
md-no-ink
style="color: white; background-color: rgb(55, 70, 79);box-shadow:0px;cursor:text;"
disabled>
version {{subjectObject.version}}
</md-button>
<md-menu d-position-mode="target-right target" ng-show="multipleVersionsOn">
<md-button style="color: white; background-color: rgba(139, 195, 74,0.9)" class="md-raised" ng-click="$mdOpenMenu($event)">
version {{subjectObject.version}}
<i class="fa fa-caret-down" aria-hidden="true"></i>
</md-button>
<md-menu-content width="3">
<md-menu-item ng-repeat="version in otherVersions | orderBy:'version'">
<md-button click-link="cluster/{{cluster}}/schema/{{subjectObject.subjectName}}/version/{{version}}">
<span md-menu-align-target="">version</span> {{version}}
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>
</div>
<!--</div>-->
<div class="buttonGroup" ng-hide="hideEdit" ng-if="aceReady && !showDeleteConfirmation" >
<!-- When in non-editable state, display the EDIT button -->
<md-button ng-show="!isAvroAceEditable" ng-click="toggleEditor();" ng-if="!readonlyMode"
aria-label="EDIT"
aria-hidden="false">
<i class="fa fa-pencil-square-o ng-scope" aria-hidden="true"></i>
EDIT
</md-button>
<!-- When in edit-state, display the CANCEL button -->
<md-button ng-show="isAvroAceEditable && !readonlyMode" ng-click="cancelEditor();"
type="button" aria-label="CANCEL"
aria-hidden="false">
<i class="fa fa-ban ng-scope" aria-hidden="true"></i>
CANCEL
</md-button>
<md-menu d-position-mode="target-right target" ng-if="!isAvroAceEditable && allowSchemaDeletion && !readonlyMode">
<md-button ng-click="$mdOpenMenu($event)">
<i class="fa fa-trash" aria-hidden="true"></i>
DELETE
</md-button>
<md-menu-content width="3">
<md-menu-item ng-if="multipleVersionsOn">
<md-button ng-click="askForConfirmToDelete('latest')">
<span md-menu-align-target="">Delete Latest Version</span>
</md-button>
</md-menu-item>
<md-menu-item ng-if="multipleVersionsOn">
<md-button ng-click="askForConfirmToDelete(subjectObject.version)">
<span md-menu-align-target=""> Delete Selected Version ({{subjectObject.version}})</span>
</md-button>
</md-menu-item>
<md-menu-item>
<md-button ng-click="askForConfirmToDelete()">
<span md-menu-align-target="">Delete Subject</span>
</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
</div>
</md-toolbar>
<md-content flex layout-padding style="padding: 2px;">
<div flex-gt-sm="nogrow" style="background-color: rgba(0, 128, 0, 0.04);padding:20px;" flex ng-show="showDeleteConfirmation">
Schema <b>{{subjectObject.subjectName}}</b> <span ng-if="versionToBeDeleted">version: <b>{{versionToBeDeleted}}</b></span> will be <span class="text-danger">deleted</span>.
<div style="position:absolute;top:5px;right:0;">
<md-button class="btn-danger" type="button" ng-click="deleteSchema(versionToBeDeleted)">
<i class="fa fa-trash" aria-hidden="true"></i>
DELETE
</md-button>
<md-button type="button" ng-click="showDeleteConfirmation=false;">
<i class="fa fa fa-ban ng-scope" aria-hidden="true"></i>
CANCEL</md-button>
</div>
</div>
<md-tabs md-dynamic-height md-border-bottom style="padding:0">
<md-tab md-on-select="hideEdit = false;">
<md-tab-label>
Schema
<!--<i class="fa fa-file-text-o" style="padding-right:10px;" aria-hidden="true"></i> Schema-->
</md-tab-label>
<md-tab-body>
<md-content class="md-padding" style="max-height:{{maxHeight}}px">
<!--put avro here-->
<form name="form" class="form">
<div ng-if="aceReady">
<div style="color:red;" ng-show="form.json.$error.validJson">- Syntax Error</div>
<div id="left"
ng-class="{'has-error':form.json.$invalid, 'has-success':!form.json.$invalid}"
ng-model="aceString"
ng-readonly="!isAvroAceEditable"
ng-style="{'background-color':aceBackgroundColor}"
name="json"
ui-ace="{ mode: 'json', firstLineNumber: 1, onLoad: viewSchemaAceLoaded, onChange: viewSchemaAceChanged, blockScrolling: Infinity , showPrintMargin:false}"
valid-json
></div>
</div>
</form>
</md-content><!--<div ng-show="form.json.$invalid && isAvroAceEditable" style="color:red;">- Invalid syntax</div>-->
<!-- When in edit-state, display the TEST button -->
<md-button style="float: right; margin-right: 32px;"
class="md-raised"
ng-disabled="form.json.$error.validJson"
ng-show="isAvroAceEditable && !isAvroUpdatedAndCompatible && !hideEdit" ng-click="testAvroCompatibility();"
aria-label="TEST"
aria-hidden="false">
VALIDATE
</md-button>
<!-- When in edit-state, and Avro is Updated and Compatible display the UPDATE button -->
<md-button ng-show="isAvroAceEditable && isAvroUpdatedAndCompatible && !hideEdit" ng-click="evolveAvroSchema();"
style="float: right; margin-right: 32px;"
class="md-raised"
type="button" aria-label="EVOLVE SCHEMA"
aria-hidden="false">
<i class="fa fa-floppy-o ng-scope" aria-hidden="true"></i>
EVOLVE SCHEMA
</md-button>
</md-tab-body>
</md-tab>
<md-tab md-on-select="otherTabSelected()" ng-if="aceReady && aceString.length > 10">
<md-tab-label>
Info
<!--<i class="fa fa-table" style="padding-right:10px;" aria-hidden="true"></i> Info-->
</md-tab-label>
<md-tab-body>
<md-content class="md-padding">
<!-- Nice header -->
<md-card style="box-shadow:none;" ng-hide="arraySchema">
<md-card-header style="padding:10px">
<md-card-avatar>
<md-icon class="md-avatar-icon" md-svg-icon="src/assets/icons/avro.svg"
style="height: 32px;width: 32px;margin-top: 10px;margin-left: 5px;padding:0;background-color:white"></md-icon>
</md-card-avatar>
<md-card-header-text>
<span class="md-title"><b>type:</b> {{subjectObject.Schema.type}}</span>
<span class="md-title"><b>name:</b> {{subjectObject.Schema.name}}</span>
<span class="md-title"><b>namespace:</b> {{subjectObject.Schema.namespace}}</span>
</md-card-header-text>
</md-card-header>
<md-card-content ng-show="subjectObject.Schema.doc != undefined" style="padding-bottom:5px">
<p class="md-card-image md-caption ng-binding">{{subjectObject.Schema.doc}}</p>
</md-card-content>
</md-card>
<!-- 3a. data table -->
<md-table-container ng-hide="arraySchema">
<table md-table md-row-select="optionsTable.rowSelection" multiple="{{optionsTable.multiSelect}}"
ng-model="selected" md-progress="promise">
<thead ng-if="!optionsTable.decapitate" md-head md-order="query.order" md-on-reorder="logOrder">
<tr md-row>
<th md-column md-order-by="name"><span>Name</span></th>
<th md-column md-order-by="type"><span>Type</span></th>
<th md-column md-order-by="default"><span>Default</span></th>
<th md-column md-order-by="doc"><span>Documentation</span></th>
</tr>
</thead>
<tbody md-body>
<tr md-row md-select="selected" md-select-id="name" ng-repeat="s in schema">
<td md-cell>{{s.name}}</td>
<td md-cell>
<json-tree ng-show="showTree(s.type)" object="s.type" root-name="'Type'"></json-tree>
<div ng-hide="showTree(s.type)">{{s.type}}</div>
</td>
<td md-cell>{{s.default}}</td>
<td md-cell class="md-card-image md-caption">{{s.doc}}</td>
</tr>
</tbody>
</table>
</md-table-container>
<div ng-repeat="schemas in subjectObject.Schema" ng-show="arraySchema">
<md-card style="box-shadow:none;">
<md-card-header style="padding:10px">
<md-card-avatar>
<md-icon class="md-avatar-icon" md-svg-icon="src/assets/icons/avro.svg"
style="height: 32px;width: 32px;margin-top: 10px;margin-left: 5px;padding:0;background-color:white"></md-icon>
</md-card-avatar>
<md-card-header-text>
<span class="md-title"><b>type:</b> {{schemas.type}}</span>
<span class="md-title"><b>name:</b> {{schemas.name}}</span>
<span class="md-title"><b>namespace:</b> {{schemas.namespace}}</span>
</md-card-header-text>
</md-card-header>
<md-card-content ng-show="subjectObject.Schema.doc != undefined" style="padding-bottom:5px">
<p class="md-card-image md-caption ng-binding">{{subjectObject.Schema.doc}}</p>
</md-card-content>
</md-card>
<md-table-container>
<table md-table md-row-select="optionsTable.rowSelection" multiple="{{optionsTable.multiSelect}}"
ng-model="selected" md-progress="promise"
>
<thead ng-if="!optionsTable.decapitate" md-head md-order="query.order" md-on-reorder="logOrder">
<tr md-row>
<th md-column md-order-by="name"><span>Name</span></th>
<th md-column md-order-by="type"><span>Type</span></th>
<th md-column md-order-by="default"><span>Default</span></th>
<th md-column md-order-by="doc"><span>Documentation</span></th>
</tr>
</thead>
<tbody md-body>
<tr md-row md-select="selected" md-select-id="name" ng-repeat="s in schemas.fields">
<td md-cell>{{s.name}}</td>
<td md-cell>
<json-tree ng-show="showTree(s.type)" object="s.type" root-name="'Type'"></json-tree>
<div ng-hide="showTree(s.type)">{{s.type}}</div>
</td>
<td md-cell>{{s.default}}</td>
<td md-cell class="md-card-image md-caption">{{s.doc}}</td>
</tr>
</tbody>
</table>
</md-table-container>
<div class="seperator" ng-hide="$index == subjectObject.Schema.length -1"></div>
</div>
</md-content>
</md-tab-body>
</md-tab>
<md-tab md-on-select="hideEdit = true;" ng-if="!readonlyMode">
<md-tab-label>
Config
<!--<i class="fa fa-file-text-o" style="padding-right:10px;" aria-hidden="true"></i> Schema-->
</md-tab-label>
<md-tab-body>
<md-content class="md-padding">
<!--put avro here-->
<form ng-cloak>
<p><span ng-if="existingValue">Current compatibility for <b>{{subjectObject.subjectName}}</b> : <span class="radioValue">{{ existingValue }}</span><br /></span>
<span ng-if="!existingValue">Schema <b>{{subjectObject.subjectName}}</b> uses the global compatibility level [{{globalConfig}}]<br /></span>
Change compatibility level to:
</p>
<md-radio-group ng-model="compatibilitySelect" ng-change='success=false'>
<md-radio-button value="NONE">NONE</md-radio-button>
<md-radio-button value="FULL"> FULL </md-radio-button>
<md-radio-button value="FORWARD">FORWARD</md-radio-button>
<md-radio-button value="BACKWARD">BACKWARD</md-radio-button>
<md-radio-button ng-if="allowTransitiveCompatibilities" value="FULL_TRANSITIVE">FULL TRANSITIVE</md-radio-button>
<md-radio-button ng-if="allowTransitiveCompatibilities" value="FORWARD_TRANSITIVE">FORWARD TRANSITIVE</md-radio-button>
<md-radio-button ng-if="allowTransitiveCompatibilities" value="BACKWARD_TRANSITIVE">BACKWARD TRANSITIVE</md-radio-button>
</md-radio-group>
<md-button class="md-raised" ng-disabled="!compatibilitySelect" ng-click="updateCompatibility(compatibilitySelect)" type="button">Update</md-button>
</form>
<div ng-show = "success">
<span style="color:green">Successfully changed compatibility level to {{compatibilitySelect}} </span>
</div>
</md-content>
</md-tab-body>
</md-tab>
<md-tab md-on-select="otherTabSelected()" ng-if="completeSubjectHistory.length > 1">
<md-tab-label>
<i class="" style="padding-right:10px;" aria-hidden="true"></i> History
</md-tab-label>
<md-tab-body>
<md-content class="md-padding">
<div ng-show="x.version != 1" ng-repeat="x in completeSubjectHistory | reverse">
<!--<pre semantic-diff left-obj="x.left.text | json" right-obj="x.right.text | json"></pre>-->
<h5>Version {{x.version}} <span style="font-weight: 200;">(Schema ID: {{x.id}})</span></h5>
<pre
style="background-color:white;font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;"
line-diff left-obj="x.previous | json" right-obj="x.current | json"></pre>
</div>
<h5>Version 1 <span
style="font-weight: 200;">(Schema ID: {{completeSubjectHistory[0].id}})</span></h5>
<pre
style="background-color:white;font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;"
line-diff
left-obj="''"
right-obj="completeSubjectHistory[0].current | json"></pre>
</md-content>
</md-tab-body>
</md-tab>
</md-tabs>
</md-content>
<!--old code ends here-->
</md-card>
================================================
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;
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
SYMBOL INDEX (29 symbols across 9 files)
FILE: src/factories/env-factory.js
function setCluster (line 24) | function setCluster(clusterName) {
FILE: src/factories/schema-registry-factory.js
function getSubjects (line 15) | function getSubjects() {
function getSubjectsVersions (line 40) | function getSubjectsVersions(subjectName) {
function getSubjectAtVersion (line 67) | function getSubjectAtVersion(subjectName, version) {
function getAllSchemas (line 90) | function getAllSchemas(cache) {
function postNewSubjectVersion (line 109) | function postNewSubjectVersion(subjectName, newSchema) {
function checkSchemaExists (line 149) | function checkSchemaExists(subjectName, subjectInformation) {
function testSchemaCompatibility (line 190) | function testSchemaCompatibility(subjectName, subjectInformation) {
function putConfig (line 230) | function putConfig(compatibilityLevel) {
function getGlobalConfig (line 277) | function getGlobalConfig() {
function getSubjectConfig (line 296) | function getSubjectConfig(subjectName) {
function updateSubjectCompatibility (line 320) | function updateSubjectCompatibility(subjectName, newCompatibilityLevel) {
function getFromCache (line 379) | function getFromCache(subjectName, subjectVersion) {
function getLatestFromCache (line 394) | function getLatestFromCache(subjectName) {
FILE: src/factories/utils-factory.js
function sortByKey (line 11) | function sortByKey(array, key, reverse) {
FILE: src/schema-registry/config/config.controller.js
function dialog (line 54) | function dialog(config, event) {
FILE: src/schema-registry/export/export.controller.js
function bindEvent (line 47) | function bindEvent(el, eventName, eventHandler) {
function downloadLatestSchemasWithBlob (line 57) | function downloadLatestSchemasWithBlob() {
function downloadAllSchemasWithBlob (line 73) | function downloadAllSchemasWithBlob() {
FILE: src/schema-registry/list/list.controller.js
function addCompatibilityValue (line 9) | function addCompatibilityValue() {
function loadCache (line 48) | function loadCache() {
FILE: src/schema-registry/new/new.controller.js
function createFilterFor (line 44) | function createFilterFor(query) {
function testCompatibility (line 61) | function testCompatibility(subject, newAvroString) {
function updateCurl (line 223) | function updateCurl() {
function registerNewSchemaPrivate (line 242) | function registerNewSchemaPrivate(newSubject, newAvro) {
FILE: src/schema-registry/view/view.controller.js
function getSchema (line 60) | function getSchema(){
function sortSchema (line 246) | function sortSchema(type) {
function getScalaFiles (line 257) | function getScalaFiles(xx) {
FILE: webpack.config.js
constant ENV (line 9) | const ENV = process.env.NODE_ENV || 'development';
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (147K chars).
[
{
"path": ".gitignore",
"chars": 79,
"preview": "bower_components/\nnode_modules/\ndist/\n.idea/\n.DS_Store\nTODO\nnpm-debug.log\n*.iml"
},
{
"path": "README.md",
"chars": 4559,
"preview": "# schema-registry-ui\n\n[](http://micro"
},
{
"path": "docker/run.sh",
"chars": 3365,
"preview": "#!/bin/sh\n\nPROXY_SKIP_VERIFY=\"${PROXY_SKIP_VERIFY:-false}\"\nINSECURE_PROXY=\"\"\nALLOW_GLOBAL=\"${ALLOW_GLOBAL:-false}\"\nALLOW"
},
{
"path": "env.js",
"chars": 618,
"preview": "var clusters = [\n {\n NAME: \"prod\",\n // Schema Registry service URL (i.e. http://localhost:8081)\n SCHEMA_REGIST"
},
{
"path": "package.json",
"chars": 3159,
"preview": "{\n \"name\": \"schema-registry-ui\",\n \"version\": \"0.9.5\",\n \"description\": \"A user interface for Confluent's Schema Regist"
},
{
"path": "src/app.js",
"chars": 4893,
"preview": "'use strict';\n\n/**\n * Pulling in css libs\n */\nrequire('font-awesome/css/font-awesome.min.css');\nrequire('angular-materia"
},
{
"path": "src/assets/css/styles.css",
"chars": 11885,
"preview": "html, body {\n height: 100%;\n margin: 0;\n padding: 0;\n background-color: #F5F5F5;\n font-size: 14px;\n fo"
},
{
"path": "src/factories/avro4s-factory.js",
"chars": 2196,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar Avro4ScalaFactory = function ($roo"
},
{
"path": "src/factories/env-factory.js",
"chars": 1655,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar envFactory = function ($rootScope)"
},
{
"path": "src/factories/index.js",
"chars": 148,
"preview": "require(\"./utils-factory\");\nrequire(\"./schema-registry-factory\");\nrequire(\"./avro4s-factory\");\nrequire(\"./env-factory\");"
},
{
"path": "src/factories/schema-registry-factory.js",
"chars": 22754,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\n/**\n * Schema-Registry angularJS Facto"
},
{
"path": "src/factories/toast-factory.js",
"chars": 2105,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar toastFactory = function ($rootScop"
},
{
"path": "src/factories/utils-factory.js",
"chars": 923,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\n/**\n * Utils angularJS Factory\n */\n\nva"
},
{
"path": "src/index.html",
"chars": 5700,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <title>Schema Registry UI</title>\n <link rel=\"shortcut icon\" href=\"./src/assets/ic"
},
{
"path": "src/schema-registry/config/config.controller.js",
"chars": 4116,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar SchemaRegistryConfigCtrl = functio"
},
{
"path": "src/schema-registry/config/config.html",
"chars": 1141,
"preview": "<md-card>\n <md-content flex layout-padding style=\"padding:6px;padding-bottom:0\">\n <div>\n <b>Url :</"
},
{
"path": "src/schema-registry/export/export.controller.js",
"chars": 3265,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\nvar JSZip = require('jszip');\nvar FileS"
},
{
"path": "src/schema-registry/export/export.html",
"chars": 1408,
"preview": "<md-card>\n\n <md-toolbar class=\"md-hue-2\">\n\n <!--<div class=\"md-toolbar-tools\">-->\n <div layout=\"row\" layout-xs=\"c"
},
{
"path": "src/schema-registry/home/home.controller.js",
"chars": 588,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar HomeCtrl = function ($log, SchemaR"
},
{
"path": "src/schema-registry/home/home.html",
"chars": 346,
"preview": "<div class=\"container-fluid-centered\" style=\"height: 500px;text-align:center;\">\n <div class=\"row-fluid\">\n <div >\n "
},
{
"path": "src/schema-registry/index.js",
"chars": 215,
"preview": "require(\"./home/home.controller\");\nrequire(\"./config/config.controller\");\nrequire(\"./view/view.controller\");\nrequire(\"./"
},
{
"path": "src/schema-registry/list/list.controller.js",
"chars": 2401,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar SubjectListCtrl = function ($scope"
},
{
"path": "src/schema-registry/list/list.html",
"chars": 2038,
"preview": "<md-card ng-controller=\"SubjectListCtrl\" md-theme=\"{{ showDarkTheme ? 'dark-grey' : 'default' }}\">\n <md-toolbar class=\""
},
{
"path": "src/schema-registry/new/new.controller.js",
"chars": 14206,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\n\nvar NewSubjectCtrl = function ($scope,"
},
{
"path": "src/schema-registry/new/new.html",
"chars": 3637,
"preview": "<form name=\"postSchema\" class=\"form-horizontal\" style=\"width: 100%\">\n\n <!-- Main Card for new subject -->\n <md-card md"
},
{
"path": "src/schema-registry/pagination/dirPaginationControlsTemplate.html",
"chars": 1343,
"preview": "<section style=\"margin: 5px 10px 0;\" layout= \"row\" layout-align= \"center\" ng-if= \"1 < pages.length || !autoHide\"\n cla"
},
{
"path": "src/schema-registry/view/view.controller.js",
"chars": 12619,
"preview": "var angular = require('angular');\nvar angularAPP = angular.module('angularAPP');\nvar ace = require('brace');\nrequire('br"
},
{
"path": "src/schema-registry/view/view.html",
"chars": 17246,
"preview": "<md-card ng-show=\"subjectObject\">\n\n <md-toolbar class=\"md-hue-2\">\n\n <!--<div class=\"md-toolbar-tools\">-->\n <div l"
},
{
"path": "webpack.config.js",
"chars": 3622,
"preview": "const path = require(\"path\");\nconst webpack = require(\"webpack\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin"
}
]
About this extraction
This page contains the full source code of the lensesio/schema-registry-ui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (135.2 KB), approximately 35.4k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.