- needed to position the dropdown content */
.dropdown {
position: relative;
display: inline-block;
z-index: 999;
}
/* Dropdown Content (Hidden by Default) */
.dropdown-content {
display: none;
position: absolute;
background-color: green;
min-width: 250px;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
}
/* Links inside the dropdown */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
/* Change color of dropdown links on hover */
.dropdown-content a:hover {
background-color: #6e8294
}
/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {
display: block;
background-color: #4f6375;
}
/* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {
background-color: #4f6375;
}
md-card {
margin: 0;
}
md-toast.md-default-theme .md-toast-content, md-toast .md-toast-content {
margin-top: 70px;
margin-right: 25px;
background-color: white;
color: black;
text-align: center;
}
md-chips.md-default-theme .md-chips, md-chips .md-chips {
box-shadow: none;
}
.md-button.light-blue {
background-color: rgba(74, 163, 223, 1);
}
/** Pagination **/
.label-primary {
background-color: #286090;
}
/* Useful for the `New Subject` auto-complete field */
.md-whiteframe-1dp, .md-whiteframe-z1 {
box-shadow: none;
}
/*.md-whiteframe-2dp {*/
/*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12);*/
/*}*/
/*.md-whiteframe-2dp:focus {*/
/*box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.4), 0 2px 2px 0 rgba(0, 0, 0, 0.28), 0 3px 1px -2px rgba(0, 0, 0, 0.24);*/
/*}*/
.md-button.light-blue {
background-color: rgba(74, 163, 223, 1);
}
.md-button.md-fab {
width: 42px;
height: 42px;
background-color: white;
}
a.md-button.md-default-theme.md-warn.md-raised, a.md-button.md-warn.md-raised, a.md-button.md-default-theme.md-warn.md-fab, a.md-button.md-warn.md-fab, .md-button.md-default-theme.md-warn.md-raised, .md-button.md-warn.md-raised, .md-button.md-default-theme.md-warn.md-fab, .md-button.md-warn.md-fab {
color: rgb(255, 255, 255);
background-color: rgba(169, 169, 169, 0.1);
}
/* Custom topics-ui CSS */
md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style {
min-height: 0;
}
.table > tbody > tr > td, .table > tbody > tr > th, .table > tfoot > tr > td, .table > tfoot > tr > th, .table > thead > tr > td, .table > thead > tr > th {
padding-top: 10px;
padding-bottom: 10px;
vertical-align: middle;
}
md-card md-card-title {
padding-bottom: 10px;
}
/** Kafka-Connect only **/
.ats-switch span.switch-right {
color: #000;
background: #f59800;
}
.md-tooltip ._md-content {
height: auto;
}
/*Animated cogs https://codepen.io/marclloyd77/pen/tAlmd*/
#development_icon {
width: 100px;
margin: 0;
-webkit-animation: pop 0.4s ease-in;
}
/*Animate cogs*/
#large-cog, #small-cog {
-webkit-animation: spin 4s linear infinite;
-webkit-transform-origin: 50% 50%;
-webkit-animation-delay: 0.6s;
}
#small-cog {
-webkit-animation: spinback 2s linear infinite;
-webkit-animation-delay: 0.6s;
}
@-webkit-keyframes pop {
0% {
-webkit-transform: scale(0);
}
90% {
-webkit-transform: scale(1.1);
}
100% {
-webkit-transform: scale(1);
}
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
@-webkit-keyframes spinback {
100% {
-webkit-transform: rotate(-360deg);
}
}
.ace_diff_new_line {
background: rgba(0, 128, 0, 0.04);
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* diff css */
.match {
display: none;
color: blue;
}
.ins {
background: rgba(55, 255, 55, 0.15);
}
.del {
background: rgba(243, 59, 59, 0.15);
}
/* Essential for dirPagination material design */
.pagination .md-button {
min-width: 30px;
margin: 5px 8px;
}
/* Essential for dirPagination material design */
.md-button {
min-width: 70px;
margin: 5px 8px;
color: #333;
}
.selectedListItem {
background-color: #DDDDDD;
}
/*Funny color*/
.md-fab:hover, .md-fab.md-focused {
background-color: rgba(230, 100, 0, .9) !important;
}
md-list-item.md-2-line, md-list-item.md-2-line > .md-no-style {
height: 48px;
}
md-content {
background-color: white;
}
.md-button.md-default-theme.md-primary.md-raised.newschemabutton {
margin-right: 4px;
background-color: #448AFF;
color: white;
}
md-list-item.md-2-line.shemaslistitem {
width: 100%;
}
.md-2-line.shemaslistitem a .md-button.md-warn.md-raised.divlistitem {
min-width: 100%;
text-align: left;
box-shadow: 0 0 0 0 rgba(0, 0, 0, .26) !important;
}
.md-raised.md-warn.md-button.versionbox {
box-shadow: 0 0 0 0 rgba(0, 0, 0, .46);
min-width: initial;
font-size: 75%;
line-height: 17px;
min-height: 18px;
text-transform: none;
background-color: rgba(44, 152, 240, 0.3);
color: black;
text-align: left;
float: right;
padding: 3px 12px;
margin-top: 10px;
}
.md-raised.md-warn.md-button.versionbox.moreversions {
background: rgb(139, 195, 74);
}
.md-raised.md-warn.md-button.md-ink-ripple.editbutton,
.md-raised.md-warn.md-button.md-ink-ripple.testbutton {
padding-left: 20px;
padding-right: 20px;
background-color: #448AFF;
color: white;
}
.md-default-theme md-input-container .md-errors-spacer {
min-height: 0;
}
span.title {
margin: 0;
padding: 0;
line-height: 44px;
font-size: 14px;
}
md-tab-content md-content {
overflow: auto
}
.flex-2 {
width: 50%;
float: left;
}
md-switch.md-default-theme.md-checked .md-thumb, md-switch.md-checked .md-thumb {
background-color: rgb(65, 191, 236);
}
md-switch.md-default-theme.md-checked .md-bar, md-switch.md-checked .md-bar {
background-color: rgba(65, 191, 236, 0.6);
}
.seperator:last-child {
border-top: 2px dashed #aaa;
margin: 40px 0 30px;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
z-index: 999999;
}
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 5% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
width: 1200px; /* Could be more or less, depending on screen size */
}
/* The Close Button */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.md-button.btn-danger {
background:#d05653;
color:white;
}
.md-button.btn-danger:hover {
background:#a7110d;
}
================================================
FILE: src/factories/avro4s-factory.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var Avro4ScalaFactory = function ($rootScope, $http, $location, $q, $log) {
/* Public API */
return {
getScalaFiles: function (apiData) {
$log.warn(apiData);
$http.defaults.useXDomain = true;
var singleLineApiData = apiData.split("\n").join(" ");
var req = {
method: 'POST',
data: singleLineApiData,
crossDomain: true,
url: AVRO4S,
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(req)
.success(function (data) {
$log.info("Received a response with: " + data);
var results = data.split("###");
$log.info(results);
if (results[0] === "scala") {
$log.info("It's Scala !! ");
$log.info("It's Scala :" + results[1]);
//alg0
return results[1];
}
})
.error(function (data, status) {
$log.error("Bad data [" + data + "] status [" + status + "]");
});
}
}
};
Avro4ScalaFactory.$inject = ['$rootScope', '$http', '$location', '$q', '$log'];
angularAPP.factory('Avro4ScalaFactory', Avro4ScalaFactory);
// curl 'https://platform.landoop.com/avro4s/avro4s' -H 'Pragma: no-cache' -H 'Origin: https://avro4s-ui.landoop.com' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en-US,en;q=0.8,el;q=0.6' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Cache-Control: no-cache' -H 'Referer: https://avro4s-ui.landoop.com/' -H 'Connection: keep-alive' --data-binary '{ "type": "record", "name": "Evolution", "namespace": "com.landoop", "fields": [ { "name": "name", "type": "string" }, { "name": "number1", "type": "int" }, { "name": "number2", "type": "float" }, { "name": "text", "type": [ "string", "null" ], "default": "" } ] }' --compressed
================================================
FILE: src/factories/env-factory.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var envFactory = function ($rootScope) {
var clusterArray = (typeof clusters !== "undefined") ? angular.copy(clusters) : [];
var selectedCluster = null;
setCluster();
return {
setSelectedCluster : function(clusterName) { setCluster(clusterName)},
getSelectedCluster : function() { return selectedCluster; },
getClusters : function() { return clusters} ,
SCHEMA_REGISTRY : function () { return selectedCluster.SCHEMA_REGISTRY; },
AVRO4S : 'https://platform.landoop.com/avro4s/avro4s', // Not currently used, will be used for converting Avro -> Scala Case classes
COLOR : function () { return selectedCluster.COLOR; },
allowGlobalConfigChanges : function () { return selectedCluster.allowGlobalConfigChanges; },
allowTransitiveCompatibilities: function () { return selectedCluster.allowTransitiveCompatibilities; },
allowSchemaDeletion: function () { return selectedCluster.allowSchemaDeletion; },
readonlyMode: function() { return selectedCluster.readonlyMode; }
};
function setCluster(clusterName) {
if(clusterArray.length === 0) {
$rootScope.missingEnvJS = true;
console.log("NOT EXISTS env.js")
}
if(angular.isUndefined(clusterName)) {
selectedCluster = clusterArray[0];
} else {
var filteredArray = clusterArray.filter(function(el) {return el.NAME === clusterName});
selectedCluster = filteredArray.length === 1 ? filteredArray[0] : clusterArray[0]
}
}
};
envFactory.$inject = ['$rootScope'];
angularAPP.factory('env', envFactory);
================================================
FILE: src/factories/index.js
================================================
require("./utils-factory");
require("./schema-registry-factory");
require("./avro4s-factory");
require("./env-factory");
require("./toast-factory");
================================================
FILE: src/factories/schema-registry-factory.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
/**
* Schema-Registry angularJS Factory
*
* Landoop - version 0.9.x (May.2017)
*/
var SchemaRegistryFactory = function ($rootScope, $http, $location, $q, $log, UtilsFactory, env) {
/**
* Get subjects
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects
*/
function getSubjects() {
var url = env.SCHEMA_REGISTRY() + '/subjects/';
$log.debug(" curl -X GET " + url);
var start = new Date().getTime();
var deferred = $q.defer();
$http.get(url)
.then(
function successCallback(response) {
var allSubjectNames = response.data;
$log.debug(" curl -X GET " + url + " => " + allSubjectNames.length + " registered subjects in [ " + ((new Date().getTime()) - start) + " ] msec");
deferred.resolve(allSubjectNames);
},
function errorCallback(response) {
deferred.reject("Failure with : " + response)
});
return deferred.promise;
}
/**
* Get subjects versions
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions
*/
function getSubjectsVersions(subjectName) {
var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/';
$log.debug(" curl -X GET " + url);
var start = new Date().getTime();
var deferred = $q.defer();
$http.get(url).then(
function successCallback(response) {
var allVersions = response.data;
$log.debug(" curl -X GET " + url + " => " + JSON.stringify(allVersions) + " versions in [ " + (new Date().getTime() - start) + " ] msec");
deferred.resolve(allVersions);
},
function errorCallback(response) {
var msg = "Failure with : " + response + " " + JSON.stringify(response);
$log.error("Error in getting subject versions : " + msg);
deferred.reject(msg);
});
return deferred.promise;
}
/**
* Get a specific version of the schema registered under this subject
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--subjects-(string- subject)-versions-(versionId- version)
*/
function getSubjectAtVersion(subjectName, version) {
var url = env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version;
$log.debug(" curl -X GET " + url);
var deferred = $q.defer();
var start = new Date().getTime();
$http.get(url).then(
function successCallback(response) {
var subjectInformation = response.data;
$log.debug(" curl -X GET " + url + " => [" + subjectName + "] subject " + JSON.stringify(subjectInformation).length + " bytes in [ " + (new Date().getTime() - start) + " ] msec");
deferred.resolve(subjectInformation);
},
function errorCallback(response) {
var msg = "Failure getting subject at version : " + response + " " + JSON.stringify(response);
$log.error(msg);
deferred.reject(msg);
});
return deferred.promise;
}
function getAllSchemas(cache) {
var i;
var allSchemasCache = [];
angular.forEach(cache, function (schema) {
for (i = 1; i <= schema.version; i++) {
getSubjectAtVersion(schema.subjectName, i).then(function (selectedSubject) {
allSchemasCache.push(selectedSubject)
//$rootScope.downloadFile += '\n echo >>>' + selectedSubject.subject +'.'+ selectedSubject.version + '.json <<< \n' + schema.schema + ' \n \n EOF';
})
}
});
$rootScope.allSchemasCache = allSchemasCache;
return allSchemasCache
}
/**
* Register a new schema under the specified subject. If successfully registered, this returns the unique identifier of this schema in the registry.
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject)-versions
*/
function postNewSubjectVersion(subjectName, newSchema) {
var deferred = $q.defer();
$log.debug("Posting new version of subject [" + subjectName + "]");
var postSchemaRegistration = {
method: 'POST',
url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + "/versions",
data: '{"schema":"' + newSchema.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(postSchemaRegistration)
.success(function (data) {
//$log.info("Success in registering new schema " + JSON.stringify(data));
var schemaId = data.id;
deferred.resolve(schemaId);
})
.error(function (data, status) {
$log.info("Error on schema registration : " + JSON.stringify(data));
var errorMessage = data.message;
if (status >= 400) {
$log.debug("Schema registrations is not allowed " + status + " " + data);
} else {
$log.debug("Schema registration failure: " + JSON.stringify(data));
}
deferred.reject(data);
});
return deferred.promise;
}
/**
* Check if a schema has already been registered under the specified subject. If so, this returns the schema string
* along with its globally unique identifier, its version under this subject and the subject name.
*
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--subjects-(string- subject)
*/
function checkSchemaExists(subjectName, subjectInformation) {
var deferred = $q.defer();
$log.debug("Checking if schema exists under this subject [" + subjectName + "]");
var postSchemaExists = {
method: 'POST',
url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName,
data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(postSchemaExists)
.success(function (data) {
var response = {
id: data.id,
version: data.version
};
$log.info("Response : " + JSON.stringify(response));
deferred.resolve(response);
})
.error(function (data, status) {
$log.info("Error while checking if schema exists under a subject : " + JSON.stringify(data));
var errorMessage = data.message;
if (status === 407) {
$log.debug("Subject not found or schema not found - 407 - " + status + " " + data);
} else {
$log.debug("Some other failure: " + JSON.stringify(data));
}
$defered.reject("Something")
});
return deferred.promise;
}
/**
* Test input schema against a particular version of a subject’s schema for compatibility.
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#post--compatibility-subjects-(string- subject)-versions-(versionId- version)
*/
function testSchemaCompatibility(subjectName, subjectInformation) {
var deferred = $q.defer();
$log.debug(" Testing schema compatibility for [" + subjectName + "]");
var postCompatibility = {
method: 'POST',
url: env.SCHEMA_REGISTRY() + '/compatibility/subjects/' + subjectName + "/versions/latest",
data: '{"schema":"' + subjectInformation.replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") + '"}' + "'",
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(postCompatibility)
.success(function (data) {
$log.info("Success in testing schema compatibility " + JSON.stringify(data));
deferred.resolve(data.is_compatible)
})
.error(function (data, status) {
$log.warn("Error on check compatibility : " + JSON.stringify(data));
if (status === 404) {
if (data.error_code === 40401) {
$log.warn("40401 = Subject not found");
}
$log.warn("[" + subjectName + "] is a non existing subject");
deferred.resolve("new"); // This will be a new subject (!)
} else {
$log.error("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
}
deferred.reject(data);
});
return deferred.promise;
}
/**
* Put global config (Test input schema against a particular version of a subject’s schema for compatibility.
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config
*/
function putConfig(compatibilityLevel) {
var deferred = $q.defer();
if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(compatibilityLevel) !== -1) {
var putConfig = {
method: 'PUT',
url: env.SCHEMA_REGISTRY() + '/config',
data: '{"compatibility":"' + compatibilityLevel + '"}' + "'",
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(putConfig)
.success(function (data) {
$log.info("Success in changing global schema-registry compatibility " + JSON.stringify(data));
deferred.resolve(data.compatibility)
})
.error(function (data, status) {
$log.info("Error on changing global compatibility : " + JSON.stringify(data));
if (status === 422) {
$log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data));
if (JSON.stringify(data).indexOf('50001') > -1) {
$log.error(" Error in the backend data store - " + $scope.text);
} else if (JSON.stringify(data).indexOf('50003') > -1) {
$log.error("Error while forwarding the request to the master: " + JSON.stringify(data));
}
} else {
$log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
}
deferred.reject(data);
});
} else {
$log.warn("Compatibility level:" + compatibilityLevel + " is not supported");
deferred.reject();
}
return deferred.promise;
}
/**
* Get global compatibility-level config
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#get--config
*/
function getGlobalConfig() {
var deferred = $q.defer();
var url = env.SCHEMA_REGISTRY() + '/config';
$log.debug(" curl -X GET " + url);
var start = new Date().getTime();
$http.get(url)
.success(function (data) {
$log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec");
deferred.resolve(data)
})
.error(function (data, status) {
deferred.reject("Get global config rejection : " + data + " " + status)
});
return deferred.promise;
}
function getSubjectConfig(subjectName) {
var deferred = $q.defer();
var url = env.SCHEMA_REGISTRY() + '/config/' + subjectName;
$log.debug(" curl -X GET " + url);
var start = new Date().getTime();
$http.get(url)
.success(function (data) {
$log.debug(" curl -X GET " + url + " => in [ " + ((new Date().getTime()) - start) + "] msec");
deferred.resolve(data)
})
.error(function (data, status) {
if (status === 404) {
$log.warn('No compatibility level is set for ' + subjectName + '. Global compatibility level is applied');
} else
deferred.reject("Get global config rejection : " + data + " " + status)
});
return deferred.promise;
}
/**
* Update compatibility level for the specified subject
* @see http://docs.confluent.io/3.0.0/schema-registry/docs/api.html#put--config-(string- subject)
*/
function updateSubjectCompatibility(subjectName, newCompatibilityLevel) {
var deferred = $q.defer();
if (["NONE", "FULL", "FORWARD", "BACKWARD", "FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE"].indexOf(newCompatibilityLevel) !== -1) {
var putConfig = {
method: 'PUT',
url: env.SCHEMA_REGISTRY() + '/config/' + subjectName,
data: '{"compatibility":"' + newCompatibilityLevel + '"}' + "'",
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}
};
$http(putConfig)
.success(function (data) {
$log.info("Success in changing subject [ " + subjectName + " ] compatibility " + JSON.stringify(data));
deferred.resolve(data.compatibility)
})
.error(function (data, status) {
$log.info("Error on changing compatibility : " + JSON.stringify(data));
if (status === 422) {
$log.warn("Invalid compatibility level " + JSON.stringify(status) + " " + JSON.stringify(data));
if (JSON.stringify(data).indexOf('50001') > -1) {
$log.error(" Error in the backend data store - " + $scope.text);
} else if (JSON.stringify(data).indexOf('50003') > -1) {
$log.error("Error while forwarding the request to the master: " + JSON.stringify(data));
}
} else {
$log.debug("HTTP > 200 && < 400 (!) " + JSON.stringify(data));
}
deferred.reject(data);
});
} else {
$log.warn("Compatibility level:" + newCompatibilityLevel + " is not supported");
deferred.reject();
}
return deferred.promise;
}
/**
* Custom logic of Factory is implemented here.
*
* In a nut-shell `CACHE` is holding a cache of known subjects
* Methods here are utilizing the cache - picking from it or updating
*
* Subjects are immutable in the schema-registry, thus downloading them
* just once is enough !
*/
var CACHE = []; // A cache of the latest subject
/**
* Gets from CACHE if exists - undefined otherwise
*/
function getFromCache(subjectName, subjectVersion) {
var start = new Date().getTime();
var response = undefined;
angular.forEach(CACHE, function (subject) {
if (subject.subjectName === subjectName && subject.version === subjectVersion) {
$log.debug(" [ " + subjectName + "/" + subjectVersion + " ] found in cache " + JSON.stringify(subject).length + " bytes in [ " + ((new Date().getTime()) - start) + " ] msec");
response = subject;
}
});
return response;
}
/**
* GETs latest from CACHE or 'undefined'
*/
function getLatestFromCache(subjectName) {
var subjectFromCache = undefined;
for (var i = 1; i < 10000; i++) {
var x = getFromCache(subjectName, i);
if (x !== undefined)
subjectFromCache = x;
}
return subjectFromCache;
}
/**
*
* Composite & Public Methods of this factory
*
*/
return {
// Proxy in function
getGlobalConfig: function () {
return getGlobalConfig();
},
getSubjectConfig: function (subjectName) {
return getSubjectConfig(subjectName);
},
putConfig: function (config) {
return putConfig(config);
},
updateSubjectCompatibility: function (subjectName, newCompatibilityLevel) {
return updateSubjectCompatibility(subjectName, newCompatibilityLevel);
},
// Proxy in function
testSchemaCompatibility: function (subjectName, subjectInformation) {
return testSchemaCompatibility(subjectName, subjectInformation);
},
// Proxy in function
registerNewSchema: function (subjectName, subjectInformation) {
return postNewSubjectVersion(subjectName, subjectInformation);
},
// Proxy in function
getSubjectsVersions: function (subjectName) {
return getSubjectsVersions(subjectName);
},
// Proxy in function
getLatestSubjectFromCache: function (subjectName) {
return getLatestFromCache(subjectName);
},
// Proxy in function
getAllSchemas: function (schemas) {
return getAllSchemas(schemas);
},
deleteVersionOfSubject: function (subjectName, version) {
var deferred = $q.defer();
var request = {
method: 'DELETE',
url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version,
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'}
};
$http(request)
.success(function (data) {
$log.info("Success in deleting subject version" + subjectName + ", version" + version);
var schemaId = data.id;
deferred.resolve(schemaId);
})
.error(function (data, status) {
$log.info("Error on subject version deletion : ", data);
deferred.reject(data);
});
return deferred.promise;
},
deleteSubject: function (subjectName) {
var deferred = $q.defer();
var request = {
method: 'DELETE',
url: env.SCHEMA_REGISTRY() + '/subjects/' + subjectName,
dataType: 'json',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json, text/plain'}
};
$http(request)
.success(function (data) {
$log.info("Success in deleting schema" + subjectName);
deferred.resolve();
})
.error(function (data, status) {
$log.info("Error on schema deletion : ", data);
deferred.reject();
});
return deferred.promise;
},
/**
* GETs all subject-names and then GETs the /versions/latest of each one
*
* Refreshes the CACHE object with latest subjects
*/
refreshLatestSubjectsCACHE: function () {
var deferred = $q.defer();
var start = new Date().getTime();
// 1. Get all subject names
getSubjects().then(
function success(allSubjectNames) {
// 2. Get full details of subject's final versions
var urlFetchLatestCalls = [];
angular.forEach(allSubjectNames, function (subject) {
urlFetchLatestCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subject + '/versions/latest'));
});
$q.all(urlFetchLatestCalls).then(function (latestSchemas) {
CACHE = []; // Clean up existing cache - to replace with new one
angular.forEach(latestSchemas, function (result) {
var data = result.data;
var cacheData = {
version: data.version, // version
id: data.id, // id
schema: data.schema, // schema - in String - schema i.e. {\"type\":\"record\",\"name\":\"User\",\"fields\":[{\"name\":\"name\",\"type\":\"string\"}]}
Schema: JSON.parse(data.schema), // js type | name | doc | fields ...
subjectName: data.subject
};
CACHE.push(cacheData);
});
$log.debug(" pipeline : get-latest-subjects-refresh-cache in [ " + (new Date().getTime() - start) + " ] msec");
$rootScope.showSpinner = false;
$rootScope.Cache = CACHE;
deferred.resolve(CACHE);
});
});
return deferred.promise;
},
/**
* Get one subject at a particular version
*/
getSubjectAtVersion: function (subjectName, subjectVersion) {
var deferred = $q.defer();
// If it's easier to fetch it from cache
var subjectFromCache = getFromCache(subjectName, subjectVersion);
if (subjectFromCache !== undefined) {
deferred.resolve(subjectFromCache);
} else {
var start = new Date().getTime();
getSubjectAtVersion(subjectName, subjectVersion).then(
function success(subjectInformation) {
//cache it
var subjectInformationWithMetadata = {
version: subjectInformation.version,
id: subjectInformation.id,
schema: subjectInformation.schema, // this is text
Schema: JSON.parse(subjectInformation.schema), // this is json
subjectName: subjectInformation.subject
};
$log.debug(" pipeline: " + subjectName + "/" + subjectVersion + " in [ " + (new Date().getTime() - start) + " ] msec");
deferred.resolve(subjectInformationWithMetadata);
},
function errorCallback(response) {
$log.error("Failure with : " + JSON.stringify(response));
});
}
return deferred.promise;
},
/**
* GETs the entire subject's history, by
*
* i. Getting all version
* ii. Fetching each version either from cache or from HTTP GET
*/
getSubjectHistory: function (subjectName) {
var deferred = $q.defer();
$log.info("Getting subject [ " + subjectName + "] history");
var completeSubjectHistory = [];
getSubjectsVersions(subjectName).then(
function success(allVersions) {
var urlCalls = [];
angular.forEach(allVersions, function (version) {
// If in cache
var subjectFromCache = getFromCache(subjectName, version);
if (subjectFromCache !== undefined) {
completeSubjectHistory.push(subjectFromCache);
} else {
urlCalls.push($http.get(env.SCHEMA_REGISTRY() + '/subjects/' + subjectName + '/versions/' + version));
}
});
// Get all missing versions and add them to cache
$q.all(urlCalls).then(function (results) {
angular.forEach(results, function (result) {
completeSubjectHistory.push(result.data);
});
deferred.resolve(completeSubjectHistory);
});
},
function failure(data) {
deferred.reject("pdata=>" + data);
});
return deferred.promise;
},
/**
* Get the history in a diff format convenient for rendering a ui
*/
getSubjectHistoryDiff: function (subjectHistory) {
var changelog = [];
$log.info("Sorting by version..");
var sortedHistory = UtilsFactory.sortByVersion(subjectHistory);
for (var i = 0; i < sortedHistory.length; i++) {
var previous = '';
if (i > 0)
previous = JSON.parse(sortedHistory[i - 1].schema);
var changeDetected = {
version: sortedHistory[i].version,
id: sortedHistory[i].id,
current: JSON.parse(sortedHistory[i].schema),
previous: previous
};
changelog.push(changeDetected);
}
return changelog;
}
}
};
SchemaRegistryFactory.$inject = ['$rootScope', '$http', '$location', '$q', '$log', 'UtilsFactory', 'env'];
angularAPP.factory('SchemaRegistryFactory', SchemaRegistryFactory);
================================================
FILE: src/factories/toast-factory.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var toastFactory = function ($rootScope, $mdToast, $window) {
var last = {
bottom: false,
top: true,
left: false,
right: true
};
var toastPosition = angular.extend({}, last);
/* Public API of this factory*/
this.getToastPosition = function () {
this.sanitizePosition();
return Object.keys(toastPosition)
.filter(function (pos) {
return toastPosition[pos];
})
.join(' ');
};
this.sanitizePosition = function () {
var current = toastPosition;
if (current.bottom && last.top) current.top = false;
if (current.top && last.bottom) current.bottom = false;
if (current.right && last.left) current.left = false;
if (current.left && last.right) current.right = false;
last = angular.extend({}, current);
};
this.showSimpleToast = function (message) {
$mdToast.show(
$mdToast.simple()
.textContent(message)
.position(this.getToastPosition())
.hideDelay(2000)
);
};
this.showSimpleToastToTop = function (message) {
this.showSimpleToast(message);
$window.scrollTo(0, 0);
};
this.showLongToast = function (message) {
var last = this.getToastPosition();
$mdToast.show(
$mdToast.simple()
.textContent(message)
.position(last)
.hideDelay(5000)
);
$window.scrollTo(0, 0);
};
this.showActionToast = function (message) {
var toast = $mdToast.simple()
.textContent(message)
.action('DELETE')
.highlightAction(true)
//.highlightClass('md-accent')// Accent is used by default, this just demonstrates the usage.
.position(this.getToastPosition())
.hideDelay(2000);
$mdToast.show(toast).then(function (response) {
if (response === 'ok') {
//alert('You clicked the \'UNDO\' action.');
}
});
};
this.hideToast = function () {
$mdToast.hide();
};
};
toastFactory.$inject = ['$rootScope', '$mdToast', '$window'];
angularAPP.service('toastFactory', toastFactory);
================================================
FILE: src/factories/utils-factory.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
/**
* Utils angularJS Factory
*/
var UtilsFactory = function ($log) {
// Sort arrays by key
function sortByKey(array, key, reverse) {
return array.sort(function (a, b) {
var x = a[key];
var y = b[key];
return ((x < y) ? -1 * reverse : ((x > y) ? 1 * reverse : 0));
});
}
/* Public API */
return {
sortByKey: function (array, key, reverse) {
return sortByKey(array, key, reverse);
},
sortByVersion: function (array) {
var sorted = array.sort(function (a, b) {
return a.version - b.version;
});
return sorted;
},
IsJsonString: function (str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
}
};
UtilsFactory.$inject = ['$log'];
angularAPP.factory('UtilsFactory', UtilsFactory);
================================================
FILE: src/index.html
================================================
Schema Registry UI
Missing Cluster Configuration
In order to configure schema-registry-ui you need to add env.js file in the
root directory of the app.
Example env.js structure:
var clusters = [
{
NAME:"prod",
// Schema Registry service URL (i.e. http://localhost:8081)
SCHEMA_REGISTRY: "http://localhost:8081", // https://schema-registry.demo.landoop.com
COLOR: "#141414" // optional
},
{
NAME:"dev",
SCHEMA_REGISTRY: "http://localhost:8383",
COLOR: "red", // optional
allowGlobalConfigChanges: true, // optional
//allowTransitiveCompatibilities: true // if using a Confluent Platform release >= 3.1.1 uncomment this line
}
];
================================================
FILE: src/schema-registry/config/config.controller.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var SchemaRegistryConfigCtrl = function ($scope, $http, $log, $mdDialog, SchemaRegistryFactory, env) {
$log.info("Starting schema-registry controller");
$scope.config = {};
$scope.connectionFailure = false;
$scope.showButton = false;
//Get the top level config
$scope.$watch(function () {
return env.getSelectedCluster().NAME;
}, function () {
$scope.schemaRegistryURL = env.SCHEMA_REGISTRY();
$scope.globalConfigOpts = ["NONE", "FULL", "FORWARD", "BACKWARD"];
if (env.allowTransitiveCompatibilities()) {
$scope.globalConfigOpts.push("FULL_TRANSITIVE", "FORWARD_TRANSITIVE", "BACKWARD_TRANSITIVE");
}
SchemaRegistryFactory.getGlobalConfig().then(
function success(config) {
$scope.allowChanges = !env.readonlyMode() && env.allowGlobalConfigChanges();
$scope.config = config;
$scope.connectionFailure = false;
$scope.form = $scope.config.compatibilityLevel;
},
function failure(response) {
$log.error("Failure with : " + JSON.stringify(response));
$scope.connectionFailure = true;
});
}, true);
$scope.updateGlobalConfig = function (config, event) {
$mdDialog.show(dialog(config, event)).then(function () {
SchemaRegistryFactory.putConfig(config).then(function () {
$scope.form = $scope.config.compatibilityLevel = config;
$scope.form = config;
});
});
};
var backwardText = '
Backward compatibility (default) :
A new schema is backward compatible if it can be used to read the data written in all previous schemas.
Backward compatibility is useful for loading data into systems like Hadoop since one can always query data of all versions using the latest schema.';
var forwardText = '
Forward compatibility :
A new schema is forward compatible if all previous schemas can read data written in this schema.
Forward compatibility is useful for consumer applications that can only deal with data in a particular version that may not always be the latest version.';
var fullText = "
Full compatibility : A new schema is fully compatible if it's both backward and forward compatible.";
var noneText = "
No compatibility : A new schema can be any schema as long as it's a valid Avro.";
var backward_transitive = "
Backward transitive : Only available for schema registry 3.1.0 and above.
New schema is backward and forward compatible with all previously registered schemas.";
var forward_transitive = "
Forward transitive : Only available for schema registry 3.1.0 and above.
All previously registered schemas can read data produced by the new schema.";
var full_transitive = "
Full transitive : Only available for schema registry 3.1.0 and above.
New schema can read data produced by all previously registered schemas.";
var text = '';
function dialog(config, event) {
switch (config) {
case "BACKWARD":
text = backwardText;
break;
case "FORWARD":
text = forwardText;
break;
case "FULL":
text = fullText;
break;
case "NONE":
text = noneText;
break;
case "BACKWARD_TRANSITIVE":
text = backward_transitive;
break;
case "FORWARD_TRANSITIVE":
text = forward_transitive;
break;
case "FULL_TRANSITIVE":
text = full_transitive;
break;
default:
text = ''
}
return $mdDialog.confirm()
.title('Warning. You are about to change the \'Global Compatibility Level\'.')
.htmlContent('
This will affect the default behaviour and all subjects/schemas that do not have a compatibility level explicitly defined. ' + text)
.targetEvent(event)
.ok('UPDATE')
.cancel('CANCEL');
}
};
SchemaRegistryConfigCtrl.$inject = ['$scope', '$http', '$log', '$mdDialog', 'SchemaRegistryFactory', 'env'];
angularAPP.controller('SchemaRegistryConfigCtrl', SchemaRegistryConfigCtrl);
================================================
FILE: src/schema-registry/config/config.html
================================================
Url : {{schemaRegistryURL}}
CONNECTIVITY ERROR
Global Compatibility level :
{{config.compatibilityLevel}}
change
schema-registry-ui: 0.9.5
================================================
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
================================================
Number of schemas: {{Cache.length}}
Total number of schemas (including all versions): {{allSchemasCache.length}}
Export latest only version of each schema
download latest
download all
================================================
FILE: src/schema-registry/home/home.controller.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var HomeCtrl = function ($log, SchemaRegistryFactory, toastFactory, $scope, env) {
$log.info("Starting schema-registry controller - home");
$scope.readonlyMode = env.readonlyMode();
toastFactory.hideToast();
$scope.$watch(function () {
return env.getSelectedCluster().NAME;
}, function () {
$scope.cluster = env.getSelectedCluster().NAME;
}, true);
};
HomeCtrl.$inject = ['$log', 'SchemaRegistryFactory', 'toastFactory', '$scope', 'env'];
angularAPP.controller('HomeCtrl', HomeCtrl);
================================================
FILE: src/schema-registry/home/home.html
================================================
================================================
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
================================================
{{schema.subjectName}}
Compatibility level is set to {{schema.compatibilityLevel}}
v.{{schema.version}}
================================================
FILE: src/schema-registry/new/new.controller.js
================================================
var angular = require('angular');
var angularAPP = angular.module('angularAPP');
var NewSubjectCtrl = function ($scope, $route, $rootScope, $http, $log, $q, $location, UtilsFactory, SchemaRegistryFactory, toastFactory, env) {
$log.debug("NewSubjectCtrl - initiating");
$scope.$on('$routeChangeSuccess', function () {
$scope.cluster = env.getSelectedCluster().NAME;//$routeParams.cluster;
});
$scope.noSubjectName = true;
$rootScope.listChanges = false;
toastFactory.hideToast();
$scope.showSimpleToast = function (message) {
toastFactory.showSimpleToast(message)
};
$scope.showSimpleToastToTop = function (message) {
toastFactory.showSimpleToastToTop(message);
};
$scope.hideToast = function () {
toastFactory.hide();
};
$scope.$watch(function () {
return $scope.text;
}, function (a) {
$scope.allowCreateOrEvolution = false;
updateCurl();
}, true);
$scope.$watch(function () {
return $scope.newAvroString;
}, function (a) {
$scope.allowCreateOrEvolution = false;
updateCurl();
}, true);
/**
* Create filter function for a query string
*/
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(state) {
return (state.value.indexOf(lowercaseQuery) === 0);
};
}
/**
* Possibilities
* 1. no-subject-name -> User has not filled-in the subjectName
* 2. not-json -> Schema is invalid Json
* 3. new-schema -> Schema is Json + subject does not exist
*/
$scope.allowCreateOrEvolution = false;
var validTypes = ["null", "double", "string", "record", "int", "float", "long", "array", "boolean", "enum", "map", "fixed", "bytes", "type"];
var primitiveTypes = ["null", "boolean", "int", "long", "float", "double", "bytes", "string"];
function testCompatibility(subject, newAvroString) {
if (env.readonlyMode()) {
var deferred = $q.defer();
$scope.showSimpleToastToTop("Creation is not allowed in readonly mode");
deferred.resolve("readonly");
return deferred.promise;
}
$scope.notValidType = false;
if (newAvroString === "null") {
if (primitiveTypes.indexOf(newAvroString) === -1) {
$scope.wrongType = newAvroString;
$scope.notValidType = true;
}
} else {
var a;
try {
a = JSON.parse(newAvroString);
console.log("It's probably object, so checking types", a)
} catch (e) {
if (typeof(newAvroString) === "string") {
if (primitiveTypes.indexOf(newAvroString) === -1) {
$scope.wrongType = newAvroString;
$scope.notValidType = true;
}
}
}
}
var flattenObject = function (ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) === 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var obj = flattenObject(newAvroString);
var typeKeysToCheck = Object.keys(obj)
.reduce(function (typeKeys, key, idx) {
// Check that this string has a type substring, if not, we don't have to validate
if (key.indexOf('type') !== -1 && isPrimitiveTypeKey(key)) {
typeKeys.push(key);
}
return typeKeys;
}, []);
var typeKeysToCheckLength = typeKeysToCheck.length;
// Create for loop vars
var i;
var keyToCheck;
/*
* By iterating in a for loop, we can break out of an invalid key type found immediately.
* That way the UI shows each wrong type one by one(if there are many) instead of just the last
* one.
*/
for (i = 0; i < typeKeysToCheckLength; i++) {
keyToCheck = typeKeysToCheck[i];
if (validTypes.indexOf(obj[keyToCheck]) < 0) {
$scope.wrongType = obj[keyToCheck];
$scope.notValidType = true;
break;
}
}
function isKeyType(key) {
return key === 'type';
}
function checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart) {
var isLastKeyPartNotANumber = isNaN(lastKeyPart);
// If it is not a number, then make sure it's a type key
if (isLastKeyPartNotANumber) {
return isKeyType(lastKeyPart);
}
// If the last part was a number, is the next to last a type part?
return isKeyType(nextToLastKeyPart);
};
// Check if they key is actually a key that defines the primitive type
function isPrimitiveTypeKey(key) {
var keyToArray = key.split('.');
var keyToArrayLength = keyToArray.length;
var lastKeyPart = keyToArray[keyToArrayLength - 1];
var nextToLastKeyPart = keyToArray[keyToArrayLength - 2];
return keyToArrayLength === 1 || checkLastTwoKeyParts(lastKeyPart, nextToLastKeyPart);
}
newAvroString = JSON.stringify(newAvroString);
var deferred = $q.defer();
if ((subject === undefined) || subject.length === 0) {
$scope.showSimpleToastToTop("Please fill in the subject name"); // (1.)
$scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
deferred.resolve("no-subject-name");
} else {
if ($scope.notValidType) {
$scope.showSimpleToastToTop($scope.wrongType + " is not valid"); // (2.)
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
deferred.resolve("not-valid-type")
} else if (!UtilsFactory.IsJsonString(newAvroString)) {
$scope.showSimpleToastToTop("This schema is not valid"); // (2.)
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
deferred.resolve("not-json")
} else {
var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject);
if (latestKnownSubject === undefined) {
// (3.)
$scope.createOrEvolve = "Create new schema";
$scope.showSimpleToast("This will be a new Subject");
$scope.allowCreateOrEvolution = true;
$scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
$log.info('Valid schema');
deferred.resolve("new-schema")
} else {
SchemaRegistryFactory.testSchemaCompatibility($scope.text, $scope.newAvroString).then(
function success(data) {
$log.info("Success in testing schema compatibility " + data);
// (4.)
$scope.allowCreateOrEvolution = false;
$scope.showSimpleToastToTop("Schema exists, please select a unique subject name");
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
deferred.resolve("non-compatible")
},
function failure(data) {
$scope.showSimpleToastToTop("Failure with - " + data);
deferred.resolve("failure");
}
);
}
}
}
return deferred.promise;
}
/**
* Update curl to reflect selected subject + schema
*/
function updateCurl() {
//$log.debug("Updating curl commands accordingly");
var remoteSubject = "FILL_IN_SUBJECT";
if (($scope.text !== undefined) && $scope.text.length > 0) {
remoteSubject = $scope.text;
}
if (JSON.stringify($scope.newAvroString)) {
var curlPrefix = 'curl -vs --stderr - -XPOST -i -H "Content-Type: application/vnd.schemaregistry.v1+json" --data ';
$scope.curlCommand =
"\n" +
"// Register new schema\n" + curlPrefix +
"'" + '{"schema":"' + JSON.stringify($scope.newAvroString).replace(/\n/g, " ").replace(/\s\s+/g, ' ').replace(/"/g, "\\\"") +
'"}' + "' " + env.SCHEMA_REGISTRY() + "/subjects/" + remoteSubject + "/versions";
}
}
/**
* Private method to register-new-schema
*/
function registerNewSchemaPrivate(newSubject, newAvro) {
var deferred = $q.defer();
SchemaRegistryFactory.registerNewSchema(newSubject, newAvro).then(
function success(id) {
$log.info("Success in registering new schema " + id);
var schemaId = id;
$scope.showSimpleToastToTop("Schema ID : " + id);
$rootScope.listChanges = true; // trigger a cache re-load
$location.path('/cluster/' + $scope.cluster + '/schema/' + newSubject + '/version/latest');
deferred.resolve(schemaId);
},
function error(data, status) {
$log.info("Error on schema registration : " + JSON.stringify(data));
var errorMessage = data.message;
$scope.showSimpleToastToTop(errorMessage);
if (status >= 400) {
$log.debug("Schema registrations is not allowed " + status + " " + data);
} else {
$log.debug("Schema registration failure: " + JSON.stringify(data));
}
deferred.reject(errorMessage);
});
return deferred.promise;
}
$scope.testCompatibility = function () {
return testCompatibility($scope.text, $scope.newAvroString);
};
/**
* How to responde to register new schema clicks
*/
$scope.registerNewSchema = function () {
var subject = $scope.text;
testCompatibility(subject, $scope.newAvroString).then(
function success(response) {
// no-subject-name | not-json | new-schema | compatible | non-compatible | failure | readonly
switch (response) {
case "no-subject-name":
case "not-json":
case "not-valid-type":
case "failure":
case "non-compatible":
case "readonly":
$log.debug("registerNewSchema - cannot do anything more with [ " + response + " ]");
break;
case 'new-schema':
var schemaString = '';
if (typeof $scope.newAvroString !== 'string')
schemaString = JSON.stringify($scope.newAvroString);
else
schemaString = $scope.newAvroString;
registerNewSchemaPrivate(subject, schemaString).then(
function success(newSchemaId) {
$log.info("New subject id after posting => " + newSchemaId);
},
function failure(data) {
$log.error("peiler2=>" + data);
$scope.allowCreateOrEvolution = false;
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
});
break;
case 'compatible':
$log.info("Compatibility [compatible]");
// TODO
var latestKnownSubject = SchemaRegistryFactory.getLatestSubjectFromCache(subject);
if (latestKnownSubject === undefined) {
$log.error("This should never happen.")
} else {
$log.info("Existing schema id = " + latestKnownSubject.version);
registerNewSchemaPrivate(subject, $scope.newAvroString).then(
function success(newSchemaId) {
$log.info("New subject id after posting => " + newSchemaId);
if (latestKnownSubject.version === newSchemaId) {
toastFactory.showSimpleToastToTop("The schema you posted was same to the existing one")
}
},
function failure(data) {
$log.error("peiler=>" + data);
$scope.allowCreateOrEvolution = false;
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
});
break;
}
default:
$log.warn("Should never come here " + response);
}
},
function failure(data) {
if (data.error_code === 500) {
$scope.aceBackgroundColor = "rgba(255, 255, 0, 0.10)";
toastFactory.showSimpleToastToTop("Not a valid avro");
}
else {
$log.error("Could not test compatibilitydasdas", data);
}
});
};
// $scope.createOrEvolve = "Create new schema";
// $scope.allowCreateOrEvolution = true;
// $scope.aceBackgroundColor = "rgba(0, 128, 0, 0.04)";
// $http(postSchemaRegistration)
// $http.get(env.SCHEMA_REGISTRY() + '/subjects/' + $scope.text + '/versions/latest')
// .success(function (data) {
// $log.info("Schema succesfully registered: " + JSON.stringify(data));
// $location.path('/subjects/' + data.subject + '/version/' + data.version);
// });
// }
// When the 'Ace' of the schema/new is loaded
$scope.newSchemaAceLoaded = function (_editor) {
$scope.editor = _editor;
$scope.editor.$blockScrolling = Infinity;
$scope.aceSchemaSession = _editor.getSession(); // we can get data on changes now
var lines = $scope.newAvroString.split("\n").length;
// TODO : getScalaFiles($scope.aceString);
// Add one extra line for each command > 110 characters
angular.forEach($scope.newAvroString.split("\n"), function (line) {
lines = lines + Math.floor(line.length / 110);
});
if (lines <= 1) {
lines = 10;
}
_editor.setOptions({
minLines: lines + 1,
maxLines: lines + 1,
highlightActiveLine: false
});
updateCurl();
};
// When the 'Ace' of the schema/new is CHANGED (!)
$scope.newSchemaAceChanged = function (_editor) {
$scope.editor = _editor;
updateCurl();
};
// When the 'Ace' of the curl command is loaded
$scope.curlCommandAceLoaded = function (_editor) {
$scope.editor = _editor;
$scope.editor.$blockScrolling = Infinity;
};
$scope.newAvroString =
angular.toJson(
{
"type": "record",
"name": "evolution",
"doc": "This is a sample Avro schema to get you started. Please edit",
"namespace": "com.landoop",
"fields": [{"name": "name", "type": "string"}, {"name": "number1", "type": "int"}, {
"name": "number2",
"type": "float"
}]
}, true);
};
NewSubjectCtrl.$inject = ['$scope', '$route', '$rootScope', '$http', '$log', '$q', '$location', 'UtilsFactory', 'SchemaRegistryFactory', 'toastFactory', 'env']
angularAPP.controller('NewSubjectCtrl', NewSubjectCtrl);
================================================
FILE: src/schema-registry/new/new.html
================================================
================================================
FILE: src/schema-registry/pagination/dirPaginationControlsTemplate.html
================================================
================================================
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
================================================
version {{subjectObject.version}}
version {{subjectObject.version}}
version {{version}}
EDIT
CANCEL
DELETE
Delete Latest Version
Delete Selected Version ({{subjectObject.version}})
Delete Subject
Schema
{{subjectObject.subjectName}} version: {{versionToBeDeleted}} will be
deleted .
DELETE
CANCEL
Schema
VALIDATE
EVOLVE SCHEMA
Info
type: {{subjectObject.Schema.type}}
name: {{subjectObject.Schema.name}}
namespace: {{subjectObject.Schema.namespace}}
{{subjectObject.Schema.doc}}
Name
Type
Default
Documentation
{{s.name}}
{{s.type}}
{{s.default}}
{{s.doc}}
type: {{schemas.type}}
name: {{schemas.name}}
namespace: {{schemas.namespace}}
{{subjectObject.Schema.doc}}
Name
Type
Default
Documentation
{{s.name}}
{{s.type}}
{{s.default}}
{{s.doc}}
Config
Successfully changed compatibility level to {{compatibilitySelect}}
History
Version {{x.version}} (Schema ID: {{x.id}})
Version 1 (Schema ID: {{completeSubjectHistory[0].id}})
================================================
FILE: webpack.config.js
================================================
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ENV = process.env.NODE_ENV || 'development';
const isProd = ENV === 'production';
console.log('Building for ' + ENV);
const config = {
watch: !isProd,
devtool: isProd ? "cheap-source-map" : "cheap-module-source-map",
entry: {
app: "./src/app.js"
},
output: {
filename: "js/[name].[hash].bundle.js",
path: path.resolve(__dirname, "dist"),
publicPath: ""
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
query: {
plugins: ['transform-decorators-legacy',
'transform-runtime',
'transform-object-rest-spread',
'transform-class-properties'],
presets: [['es2015', { modules: false }], 'stage-1']
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "url-loader?limit=10000&mimetype=application/font-woff&outputPath=&publicPath=../../"
},
{
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: "file-loader"
},
{
test: /\.html$/,
loader: "html-loader"
}
]
},
plugins: [
new ExtractTextPlugin({
filename: "assets/css/[name].[contenthash].css"
}),
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: function (module) {
return module.context && module.context.indexOf("node_modules") !== -1;
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: "manifest"
}),
new HtmlWebpackPlugin({ template: "./src/index.html" }),
new CopyWebpackPlugin([{
from: __dirname + '/src/assets',
to: path.resolve(__dirname, "dist/src/assets")
}]),
new CopyWebpackPlugin([{
from: __dirname + '/env.js',
to: path.resolve(__dirname, "dist/")
}]),
new webpack.HotModuleReplacementPlugin()
],
resolve: {
alias: {
}
},
devServer: {
host: "localhost",
port: "8080",
contentBase: path.resolve(__dirname, "dist"),
//compress: true,
historyApiFallback: true,
hot: true,
inline: true,
https: false,
noInfo: true
}
};
if (isProd) {
config.plugins.push(
new CleanWebpackPlugin(['dist']),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
join_vars: true,
if_return: true
},
output: {
comments: false
}
}))
}
module.exports = config;