This commit is contained in:
2026-05-15 22:19:14 -07:00
commit f4f046263c
2058 changed files with 236159 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('AliasOverviewController',
function($scope, $routeParams, Collections, Constants) {
$scope.resetMenu("collection-overview", Constants.IS_COLLECTION_PAGE);
$scope.refresh = function() {
$scope.selectedCollection = $scope.currentCollection;
};
$scope.refresh();
});

View File

@@ -0,0 +1,246 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('AnalysisController',
function($scope, $location, $routeParams, Luke, Analysis, Constants) {
$scope.resetMenu("analysis", Constants.IS_COLLECTION_PAGE);
$scope.refresh = function() {
Luke.schema({core: $routeParams.core}, function(data) {
$scope.fieldsAndTypes = getFieldsAndTypes(data.schema);
$scope.core = $routeParams.core;
$scope.parseQueryString(data.schema);
// @todo - set defaultSearchField either to context["analysis.fieldname"] or context["analysis.fieldtype"]
});
};
$scope.verbose = true;
var getFieldsAndTypes = function(schema) {
var aggregatedFields = schema.fields;
for (var field in schema.fields) {
var copyDests = schema.fields[field].copyDests;
for (var i in copyDests) {
var copyDest = copyDests[i];
if (!aggregatedFields[copyDest]) {
aggregatedFields[copyDest] = {};
}
}
}
var fieldsAndTypes = [];
var fields = Object.keys(aggregatedFields).sort();
for (var i in fields) {
fieldsAndTypes.push({
group: "Fields",
value: "fieldname=" + fields[i],
label: fields[i]
});
}
var dynamicFields = Object.keys(schema.dynamicFields).sort();
for (var i in dynamicFields) {
fieldsAndTypes.push({
group: "Dynamic Fields",
value: "dynamicfield=" + dynamicFields[i],
label: dynamicFields[i]
});
}
var types = Object.keys(schema.types).sort();
for (var i in types) {
fieldsAndTypes.push({
group: "Types",
value: "fieldtype=" + types[i],
label: types[i]
});
}
return fieldsAndTypes;
};
var getShortComponentName = function(longname) {
var short = -1 !== longname.indexOf( '$' )
? longname.split( '$' )[1]
: longname.split( '.' ).pop();
var match = short.match( /[A-Z]/g );
if(match != undefined) {
return match.join( '' );
} else {
return "?";
}
};
var getCaptionsForComponent = function(data) {
var captions = [];
for (var key in data[0]) {
key = key.replace(/.*#/,'');
if (key != "match" && key!="positionHistory") {
captions.push(key.replace(/.*#/,''));
}
}
return captions;
};
var getTokensForComponent = function(data) {
var tokens = [];
var previousPosition = 0;
var index=0;
for (var i in data) {
var tokenhash = data[i];
var positionDifference = tokenhash.position - previousPosition;
for (var j=positionDifference; j>1; j--) {
tokens.push({position: tokenhash.position - j+1, blank:true, index:index++});
}
var token = {position: tokenhash.position, keys:[], index:index++};
for (key in tokenhash) {
if (key == "match" || key=="positionHistory") {
//skip, to not display these keys in the UI
} else {
var tokenInfo = new Object();
tokenInfo.name = key;
tokenInfo.value = tokenhash[key];
if ('text' === key || 'raw_bytes' === key ) {
if (tokenhash.match) {
tokenInfo.extraclass = 'match'; //to highlight matching text strings
}
}
token.keys.push(tokenInfo);
}
}
tokens.push(token);
previousPosition = tokenhash.position;
}
return tokens;
};
var extractComponents = function(data, result, name) {
if (data) {
result[name] = [];
for (var i = 0; i < data.length; i += 2) {
var component = {
name: data[i],
short: getShortComponentName(data[i]),
captions: getCaptionsForComponent(data[i + 1]),
tokens: getTokensForComponent(data[i + 1])
};
result[name].push(component);
}
}
};
var processAnalysisData = function(analysis, fieldOrType) {
var fieldname;
for (fieldname in analysis[fieldOrType]) {console.log(fieldname);break;}
var response = {};
extractComponents(analysis[fieldOrType][fieldname].index, response, "index");
extractComponents(analysis[fieldOrType][fieldname].query, response, "query");
return response;
};
$scope.updateQueryString = function() {
var parts = $scope.fieldOrType.split("=");
var fieldOrType = parts[0];
var name = parts[1];
if ($scope.indexText) {
$location.search("analysis.fieldvalue", $scope.indexText);
} else if ($location.search()["analysis.fieldvalue"]) {
$location.search("analysis.fieldvalue", null);
}
if ($scope.queryText) {
$location.search("analysis.query", $scope.queryText);
} else if ($location.search()["analysis.query"]) {
$location.search("analysis.query", null);
}
if (fieldOrType == "fieldname") {
$location.search("analysis.fieldname", name);
$location.search("analysis.dynamicfield", null);
$location.search("analysis.fieldtype", null);
} else if (fieldOrType == "dynamicfield") {
$location.search("analysis.fieldname", null);
$location.search("analysis.dynamicfield", name);
$location.search("analysis.fieldtype", null);
} else {
$location.search("analysis.fieldname", null);
$location.search("analysis.dynamicfield", null);
$location.search("analysis.fieldtype", name);
}
$location.search("verbose_output", $scope.verbose ? "1" : "0");
};
$scope.parseQueryString = function (schema) {
var params = {};
var search = $location.search();
if (Object.keys(search).length == 0) {
return;
}
for (var key in search) {
params[key]=search[key];
}
$scope.indexText = search["analysis.fieldvalue"];
$scope.queryText = search["analysis.query"];
if (search["analysis.fieldname"]) {
$scope.fieldOrType = "fieldname=" + search["analysis.fieldname"];
$scope.schemaBrowserUrl = "field=" + search["analysis.fieldname"];
} else if (search["analysis.dynamicfield"]) {
params["analysis.fieldtype"] = schema.dynamicFields[search["analysis.dynamicfield"]].type;
$scope.fieldOrType = "dynamicfield=" + search["analysis.dynamicfield"];
$scope.schemaBrowserUrl = "dynamic-field=" + search["analysis.dynamicfield"];
} else {
$scope.fieldOrType = "fieldtype=" + search["analysis.fieldtype"];
$scope.schemaBrowserUrl = "type=" + search["analysis.fieldtype"];
}
if (search["verbose_output"] == undefined) {
$scope.verbose = true;
} else {
$scope.verbose = search["verbose_output"] == "1";
}
if ($scope.fieldOrType || $scope.indexText || $scope.queryText) {
params.core = $routeParams.core;
var parts = $scope.fieldOrType.split("=");
var fieldOrType = parts[0] == "fieldname" ? "field_names" : "field_types";
Analysis.field(params, function(data) {
$scope.result = processAnalysisData(data.analysis, fieldOrType);
});
}
};
$scope.changeFieldOrType = function() {
var parts = $scope.fieldOrType.split("=");
if (parts[0]=='fieldname') {
$scope.schemaBrowserUrl = "field=" + parts[1];
} else if (parts[0]=='dynamicfield') {
$scope.schemaBrowserUrl = "dynamic-field=" + parts[1];
} else {
$scope.schemaBrowserUrl = "type=" + parts[1];
}
};
$scope.toggleVerbose = function() {
$scope.verbose = !$scope.verbose;
$scope.updateQueryString();
};
$scope.refresh();
}
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('CollectionOverviewController',
function($scope, $routeParams, Collections, Constants) {
$scope.resetMenu("collection-overview", Constants.IS_COLLECTION_PAGE);
$scope.refresh = function() {
Collections.status({}, function(data) {
$scope.selectedCollection = data.cluster.collections[$routeParams.core];
$scope.selectedCollection.name = $routeParams.core;
$scope.rootUrl = Constants.ROOT_URL;
});
};
$scope.showReplica = function(replica) {
replica.show = !replica.show;
}
$scope.hideShard = function(shard) {
shard.hide = !shard.hide;
}
$scope.refresh();
});

View File

@@ -0,0 +1,306 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('CollectionsController',
function($scope, $routeParams, $location, $timeout, Collections, CollectionsV2, Zookeeper, Constants, ConfigSets){
$scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
$scope.refresh = function() {
$scope.rootUrl = Constants.ROOT_URL + "#/~collections/" + $routeParams.collection;
Zookeeper.liveNodes({}, function(data) {
$scope.availableNodeSet = [];
var children = data.tree[0].children;
for (var child in children) {
$scope.availableNodeSet.push(children[child].text);
}
});
Collections.status(function (data) {
$scope.collections = [];
for (var name in data.cluster.collections) {
if (name.startsWith("._designer_")) {
continue;
}
var collection = data.cluster.collections[name];
collection.name = name;
collection.type = 'collection';
var shards = collection.shards;
collection.shards = [];
for (var shardName in shards) {
var shard = shards[shardName];
shard.name = shardName;
shard.collection = collection.name;
var replicas = shard.replicas;
shard.replicas = [];
for (var replicaName in replicas) {
var replica = replicas[replicaName];
replica.name = replicaName;
replica.collection = collection.name;
replica.shard = shard.name;
shard.replicas.push(replica);
}
collection.shards.push(shard);
}
$scope.collections.push(collection);
if ($routeParams.collection == name) {
$scope.collection = collection;
}
}
// Fetch aliases using LISTALIASES to get properties
Collections.listaliases(function (adata) {
// TODO: Population of aliases array duplicated in app.js
$scope.aliases = [];
for (var key in adata.aliases) {
props = {};
if (key in adata.properties) {
props = adata.properties[key];
}
var alias = {name: key, collections: adata.aliases[key], type: 'alias', properties: props};
$scope.aliases.push(alias);
if ($routeParams.collection == 'alias_' + key) {
$scope.collection = alias;
}
}
// Decide what is selected in list
if ($routeParams.collection && !$scope.collection) {
alert("No collection or alias called " + $routeParams.collection);
$location.path("/~collections");
}
});
$scope.liveNodes = data.cluster.liveNodes;
});
ConfigSets.configs(function(data) {
$scope.configs = [];
var items = data.configSets;
for (var i in items) {
$scope.configs.push({name: items[i]});
}
});
};
$scope.hideAll = function() {
$scope.showRename = false;
$scope.showAdd = false;
$scope.showDelete = false;
$scope.showSwap = false;
$scope.showCreateAlias = false;
$scope.showDeleteAlias = false;
};
$scope.showAddCollection = function() {
$scope.hideAll();
$scope.showAdd = true;
$scope.newCollection = {
name: "",
routerName: "compositeId",
numShards: 1,
configName: "",
nrtReplicas: 0,
tlogReplicas: 0,
pullReplicas: 0,
replicationFactor: 1
};
};
$scope.toggleCreateAlias = function() {
$scope.hideAll();
$scope.showCreateAlias = true;
}
$scope.toggleDeleteAlias = function() {
$scope.hideAll();
$scope.showDeleteAlias = true;
}
$scope.cancelCreateAlias = $scope.cancelDeleteAlias = function() {
$scope.hideAll();
}
$scope.createAlias = function() {
var collections = [];
for (var i in $scope.aliasCollections) {
collections.push($scope.aliasCollections[i].name);
}
Collections.createAlias({name: $scope.aliasToCreate, collections: collections.join(",")}, function(data) {
$scope.cancelCreateAlias();
$scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
$location.path("/~collections/alias_" + $scope.aliasToCreate);
});
}
$scope.deleteAlias = function() {
Collections.deleteAlias({name: $scope.collection.name}, function(data) {
$scope.hideAll();
$scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
$location.path("/~collections/");
});
};
$scope.addCollection = function() {
if (!$scope.newCollection.name) {
$scope.addMessage = "Please provide a core name";
} else if (false) { //@todo detect whether core exists
$scope.AddMessage = "A core with that name already exists";
} else if ( $scope.newCollection.pullReplicas > 0 && ($scope.newCollection.nrtReplicas + $scope.newCollection.tlogReplicas == 0))
{
$scope.addMessage = "A collection can't be made up of just PULL replicas";
} else {
var coll = $scope.newCollection;
var params = {
name: coll.name,
"router.name": coll.routerName,
numShards: coll.numShards,
"collection.configName": coll.configName
};
if (coll.shards) params.shards = coll.shards;
if (coll.routerField) params["router.field"] = coll.routerField;
if (coll.createNodeSet) params.createNodeSet = coll.createNodeSet.join(",");
if ($scope.replicaTypesChosen()) {
params["nrtReplicas"] = coll.nrtReplicas;
params["tlogReplicas"] = coll.tlogReplicas;
params["pullReplicas"] = coll.pullReplicas;
} else {
params["replicationFactor"] = coll.replicationFactor;
}
Collections.add(params, function(data) {
$scope.cancelAddCollection();
$scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
$location.path("/~collections/" + $scope.newCollection.name);
});
}
};
$scope.cancelAddCollection = function() {
delete $scope.addMessage;
$scope.showAdd = false;
};
$scope.showDeleteCollection = function() {
$scope.hideAll();
if ($scope.collection) {
$scope.showDelete = true;
} else {
alert("No collection selected.");
}
};
$scope.deleteCollection = function() {
if ($scope.collection.name == $scope.collectionDeleteConfirm) {
Collections.delete({name: $scope.collection.name}, function (data) {
$location.path("/~collections");
});
} else {
$scope.deleteMessage = "Collection names do not match.";
}
};
$scope.reloadCollection = function() {
if (!$scope.collection) {
alert("No collection selected.");
return;
}
CollectionsV2.reloadCollection($scope.collection.name, function(error, data,response) {
if (error) {
$scope.reloadFailure = true;
$timeout(function() {$scope.reloadFailure=false}, 1000);
$location.path("/~collections");
} else {
$scope.reloadSuccess = true;
$timeout(function() {$scope.reloadSuccess=false}, 1000);
}
});
};
$scope.toggleAddReplica = function(shard) {
$scope.hideAll();
shard.showAdd = !shard.showAdd;
delete $scope.addReplicaMessage;
Zookeeper.liveNodes({}, function(data) {
$scope.nodes = [];
var children = data.tree[0].children;
for (var child in children) {
$scope.nodes.push(children[child].text);
}
});
};
$scope.toggleRemoveReplica = function(replica) {
$scope.hideAll();
replica.showRemove = !replica.showRemove;
};
$scope.toggleRemoveShard = function(shard) {
$scope.hideAll();
shard.showRemove = !shard.showRemove;
};
$scope.deleteShard = function(shard) {
Collections.deleteShard({collection: shard.collection, shard:shard.name}, function(data) {
shard.deleted = true;
$timeout(function() {
$scope.refresh();
}, 2000);
});
}
$scope.deleteReplica = function(replica) {
Collections.deleteReplica({collection: replica.collection, shard:replica.shard, replica:replica.name}, function(data) {
replica.deleted = true;
$timeout(function() {
$scope.refresh();
}, 2000);
});
}
$scope.addReplica = function(shard) {
var params = {
collection: shard.collection,
shard: shard.name,
type: shard.replicaType
}
if (shard.replicaNodeName && shard.replicaNodeName != "") {
params.node = shard.replicaNodeName;
}
Collections.addReplica(params, function(data) {
shard.replicaAdded = true;
$timeout(function () {
shard.replicaAdded = false;
shard.showAdd = false;
$scope.refresh();
}, 2000);
});
};
$scope.toggleShard = function(shard) {
shard.show = !shard.show;
}
$scope.toggleReplica = function(replica) {
replica.show = !replica.show;
}
$scope.replicaTypesChosen = function () {
if ($scope.newCollection) {
return ( $scope.newCollection.nrtReplicas + $scope.newCollection.tlogReplicas + $scope.newCollection.pullReplicas > 0 );
}
}
$scope.refresh();
}
);

View File

@@ -0,0 +1,95 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('CoreOverviewController',
function($scope, $rootScope, $routeParams, Luke, CoreSystem, Update, Replication, Ping, Constants) {
$scope.resetMenu("overview", Constants.IS_CORE_PAGE);
$scope.refreshIndex = function() {
Luke.index({core: $routeParams.core},
function(data) {
$scope.index = data.index;
delete $scope.statsMessage;
},
function(error) {
$scope.statsMessage = "Luke is not configured";
}
);
};
$scope.refreshReplication = function() {
Replication.details({core: $routeParams.core},
function(data) {
$scope.isFollower = data.details.isFollower == "true";
$scope.isLeader = data.details.isLeader == "true";
$scope.replication = data.details;
},
function(error) {
$scope.replicationMessage = "Replication is not configured";
});
};
$scope.refreshSystem = function() {
CoreSystem.get({core: $routeParams.core},
function(data) {
$scope.core = data.core;
delete $scope.systemMessage;
},
function(error) {
$scope.systemMessage = "/admin/system Handler is not configured";
}
);
};
$scope.refreshPing = function() {
Ping.status({core: $routeParams.core}, function(data) {
if (data.error) {
$scope.healthcheckStatus = false;
if (data.error.code == 503) {
$scope.healthcheckMessage = 'Ping request handler is not configured with a healthcheck file.';
}
} else {
$scope.healthcheckStatus = data.status == "enabled";
}
});
};
$scope.toggleHealthcheck = function() {
if ($scope.healthcheckStatus) {
Ping.disable(
{core: $routeParams.core},
function(data) {$scope.healthcheckStatus = false},
function(error) {$scope.healthcheckMessage = error}
);
} else {
Ping.enable(
{core: $routeParams.core},
function(data) {$scope.healthcheckStatus = true},
function(error) {$scope.healthcheckMessage = error}
);
}
};
$scope.refresh = function() {
$scope.refreshIndex();
$scope.refreshReplication();
$scope.refreshSystem();
$scope.refreshPing();
};
$scope.refresh();
});

View File

@@ -0,0 +1,180 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('CoreAdminController',
function($scope, $routeParams, $location, $timeout, $route, Cores, Update, Constants){
$scope.resetMenu("cores", Constants.IS_ROOT_PAGE);
$scope.selectedCore = $routeParams.corename; // use 'corename' not 'core' to distinguish from /solr/:core/
$scope.refresh = function() {
Cores.get(function(data) {
var coreCount = 0;
var cores = data.status;
for (_obj in cores) coreCount++;
$scope.hasCores = coreCount >0;
if (!$scope.selectedCore && coreCount==0) {
$scope.showAddCore();
return;
} else if (!$scope.selectedCore) {
for (firstCore in cores) break;
$scope.selectedCore = firstCore;
$location.path("/~cores/" + $scope.selectedCore).replace();
}
$scope.core = cores[$scope.selectedCore];
$scope.corelist = [];
$scope.swapCorelist = [];
for (var core in cores) {
$scope.corelist.push(cores[core]);
if (cores[core] != $scope.core) {
$scope.swapCorelist.push(cores[core]);
}
}
if ($scope.swapCorelist.length>0) {
$scope.swapOther = $scope.swapCorelist[0].name;
}
});
};
$scope.showAddCore = function() {
$scope.hideAll();
$scope.showAdd = true;
$scope.newCore = {
name: "new_core",
dataDir: "data",
instanceDir: "new_core",
config: "solrconfig.xml",
schema: "schema.xml",
collection: "",
shard: ""
};
};
$scope.addCore = function() {
if (!$scope.newCore.name) {
$scope.addMessage = "Please provide a core name";
} else if (false) { //@todo detect whether core exists
$scope.AddMessage = "A core with that name already exists";
} else {
var params = {
name: $scope.newCore.name,
instanceDir: $scope.newCore.instanceDir,
config: $scope.newCore.config,
schema: $scope.newCore.schema,
dataDir: $scope.newCore.dataDir
};
if ($scope.isCloud) {
params.collection = $scope.newCore.collection;
params.shard = $scope.newCore.shard;
}
Cores.add(params, function(data) {
$location.path("/~cores/" + $scope.newCore.name);
$scope.cancelAddCore();
});
}
};
$scope.cancelAddCore = function() {
delete $scope.addMessage;
$scope.showAdd = false
};
$scope.unloadCore = function() {
var answer = confirm( 'Do you really want to unload Core "' + $scope.selectedCore + '"?' );
if( !answer ) return;
Cores.unload({core: $scope.selectedCore}, function(data) {
$location.path("/~cores");
});
};
$scope.showRenameCore = function() {
$scope.hideAll();
$scope.showRename = true;
};
$scope.renameCore = function() {
if (!$scope.other) {
$scope.renameMessage = "Please provide a new name for the " + $scope.selectedCore + " core";
} else if ($scope.other == $scope.selectedCore) {
$scope.renameMessage = "New name must be different from the current one";
} else {
Cores.rename({core:$scope.selectedCore, other: $scope.other}, function(data) {
$location.path("/~cores/" + $scope.other);
$scope.cancelRename();
});
}
};
$scope.cancelRenameCore = function() {
$scope.showRename = false;
delete $scope.renameMessage;
$scope.other = "";
};
$scope.showSwapCores = function() {
$scope.hideAll();
$scope.showSwap = true;
};
$scope.swapCores = function() {
if (!$scope.swapOther) {
$scope.swapMessage = "Please select a core to swap with";
} else if ($scope.swapOther == $scope.selectedCore) {
$scope.swapMessage = "Cannot swap with the same core";
} else {
Cores.swap({core: $scope.selectedCore, other: $scope.swapOther}, function(data) {
$location.path("/~cores/" + $scope.swapOther);
delete $scope.swapOther;
$scope.cancelSwapCores();
});
}
};
$scope.cancelSwapCores = function() {
delete $scope.swapMessage;
$scope.showSwap = false;
}
$scope.reloadCore = function() {
if ($scope.initFailures[$scope.selectedCore]) {
delete $scope.initFailures[$scope.selectedCore];
$scope.showInitFailures = Object.keys(data.initFailures).length>0;
}
Cores.reload({core: $scope.selectedCore},
function(data) {
if (data.error) {
$scope.reloadFailure = true;
$timeout(function() {
$scope.reloadFailure = false;
$route.reload();
}, 1000);
} else {
$scope.reloadSuccess = true;
$timeout(function () {
$scope.reloadSuccess = false;
$route.reload();
}, 1000);
}
});
};
$scope.hideAll = function() {
$scope.showRename = false;
$scope.showAdd = false;
$scope.showSwap = false;
};
$scope.refresh();
}
);

View File

@@ -0,0 +1,137 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//helper for formatting JSON and others
var DOC_PLACEHOLDER = '<doc>\n' +
'<field name="id">change.me</field>' +
'<field name="title">change.me</field>' +
'</doc>';
var ADD_PLACEHOLDER = '<add>\n' + DOC_PLACEHOLDER + '</add>\n';
solrAdminApp.controller('DocumentsController',
function($scope, $rootScope, $routeParams, $location, Luke, Update, FileUpload, Constants) {
$scope.resetMenu("documents", Constants.IS_COLLECTION_PAGE);
$scope.refresh = function () {
Luke.schema({core: $routeParams.core}, function(data) {
//TODO: handle dynamic fields
delete data.schema.fields._version_;
$scope.fields = Object.keys(data.schema.fields);
});
$scope.document = "";
$scope.handler = "/update";
$scope.type = "json";
$scope.commitWithin = 1000;
$scope.overwrite = true;
};
$scope.refresh();
$scope.changeDocumentType = function () {
$scope.placeholder = "";
if ($scope.type == 'json') {
$scope.placeholder = '{"id":"change.me","title":"change.me"}';
} else if ($scope.type == 'csv') {
$scope.placeholder = "id,title\nchange.me,change.me";
} else if ($scope.type == 'solr') {
$scope.placeholder = ADD_PLACEHOLDER;
} else if ($scope.type == 'xml') {
$scope.placeholder = DOC_PLACEHOLDER;
}
};
$scope.addWizardField = function () {
if ($scope.document == "") $scope.document = "{}";
var doc = JSON.parse($scope.document);
doc[$scope.fieldName] = $scope.fieldData;
$scope.document = JSON.stringify(doc, null, '\t');
$scope.fieldData = "";
};
$scope.submit = function () {
var contentType = "";
var postData = "";
var params = {};
var doingFileUpload = false;
if ($scope.handler[0] == '/') {
params.handler = $scope.handler.substring(1);
} else {
params.handler = 'update';
params.qt = $scope.handler;
}
params.commitWithin = $scope.commitWithin;
params.overwrite = $scope.overwrite;
params.core = $routeParams.core;
params.wt = "json";
if ($scope.type == "json" || $scope.type == "wizard") {
postData = "[" + $scope.document + "]";
contentType = "json";
} else if ($scope.type == "csv") {
postData = $scope.document;
contentType = "csv";
} else if ($scope.type == "xml") {
postData = "<add>" + $scope.document + "</add>";
contentType = "xml";
} else if ($scope.type == "upload") {
doingFileUpload = true;
params.raw = $scope.literalParams;
} else if ($scope.type == "solr") {
postData = $scope.document;
if (postData[0] == "<") {
contentType = "xml";
} else if (postData[0] == "{" || postData[0] == '[') {
contentType = "json";
} else {
alert("Cannot identify content type")
}
}
if (!doingFileUpload) {
var callback = function (success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.response = JSON.stringify(success, null, ' ');
};
var failure = function (failure) {
$scope.responseStatus = failure;
};
if (contentType == "json") {
Update.postJson(params, postData, callback, failure);
} else if (contentType == "xml") {
Update.postXml(params, postData, callback, failure);
} else if (contentType == "csv") {
Update.postCsv(params, postData, callback, failure);
}
} else {
var file = $scope.fileUpload;
console.log('file is ' + JSON.stringify(file));
var uploadUrl = "/fileUpload";
FileUpload.upload(params, $scope.fileUpload, function (success) {
$scope.responseStatus = "success";
$scope.response = JSON.stringify(success, null, ' ');
}, function (failure) {
$scope.responseStatus = "failure";
$scope.response = JSON.stringify(failure, null, ' ');
});
}
}
});

View File

@@ -0,0 +1,98 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var contentTypeMap = { xml : 'application/xml', html : 'application/xml', js : 'application/javascript', json : 'application/json', css : 'text/plain' };
var languages = {js: "javascript", xml:"xml", xsl:"xml", vm: "xml", html: "xml", json: "json"};
solrAdminApp.controller('FilesController',
function($scope, $rootScope, $routeParams, $location, Files, Constants) {
$scope.resetMenu("files", Constants.IS_COLLECTION_PAGE);
$scope.file = $location.search().file;
$scope.content = null;
$scope.baseurl = $location.absUrl().substr(0,$location.absUrl().indexOf("#")); // Including /solr/ context
$scope.refresh = function () {
var process = function (path, tree) {
var params = {core: $routeParams.core};
if (path.slice(-1) === '/') {
params.file = path.slice(0, -1);
} else if (path!=='') {
params.file = path;
}
Files.list(params, function (data) {
var filenames = Object.keys(data.files);
filenames.sort();
for (var i in filenames) {
var file = filenames[i];
var filedata = data.files[file];
var state = undefined;
var children = undefined;
if (filedata.directory) {
file = file + "/";
if ($scope.file && $scope.file.indexOf(path + file) === 0) {
state = {"opened": true};
} else {
state = {"opened": false};
}
children = [];
process(path + file, children);
}
tree.push({
text: file,
a_attr: { id: path + file},
children: children,
state: state
});
}
});
};
$scope.tree = [];
process("", $scope.tree);
if ($scope.file && $scope.file !== '' && $scope.file.split('').pop()!=='/') {
var extension;
if ($scope.file === "managed-schema") {
extension = 'xml';
} else {
extension = $scope.file.match( /\.(\w+)$/)[1] || '';
}
var contentType = (contentTypeMap[extension] || 'text/plain' ) + ';charset=utf-8';
Files.get({core: $routeParams.core, file: $scope.file, contentType: contentType}, function(data) {
$scope.content = data.data;
$scope.url = data.config.url + "?" + $.param(data.config.params); // relative URL
if (contentType.indexOf("text/plain") && (data.data.indexOf("<?xml")>=0) || data.data.indexOf("<!--")>=0) {
$scope.lang = "xml";
} else {
$scope.lang = languages[extension] || "txt";
}
});
}
};
$scope.showTreeLink = function(data) {
var file = data.node.a_attr.id;
$location.search({file:file});
};
$scope.refresh();
});

View File

@@ -0,0 +1,125 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('IndexController', function($scope, System, Cores, Constants) {
$scope.resetMenu("index", Constants.IS_ROOT_PAGE);
$scope.reload = function() {
System.get(function(data) {
$scope.system = data;
const releaseDate = parse_release_date($scope.system.lucene['solr-impl-version'])
$scope.releaseDaysOld = (new Date() - releaseDate)/1000/60/60/24;
if ("username" in data.security) {
// Needed for Kerberos, since this is the only place from where
// Kerberos username can be obtained.
sessionStorage.setItem("auth.username", data.security.username);
}
if (data.security.authenticationPlugin) {
$scope.isSecurityEnabled = true
}
// load average, unless its negative (means n/a on windows, etc)
if (data.system.systemLoadAverage >= 0) {
$scope.load_average = data.system.systemLoadAverage.toFixed(2);
}
// physical memory
var memoryMax = parse_memory_value(data.system.totalPhysicalMemorySize);
$scope.memoryTotal = parse_memory_value(data.system.totalPhysicalMemorySize - data.system.freePhysicalMemorySize);
$scope.memoryPercentage = ($scope.memoryTotal / memoryMax * 100).toFixed(1)+ "%";
$scope.memoryMax = pretty_print_bytes(memoryMax);
$scope.memoryTotalDisplay = pretty_print_bytes($scope.memoryTotal);
// swap space
var swapMax = parse_memory_value(data.system.totalSwapSpaceSize);
$scope.swapTotal = parse_memory_value(data.system.totalSwapSpaceSize - data.system.freeSwapSpaceSize);
$scope.swapPercentage = ($scope.swapTotal / swapMax * 100).toFixed(1)+ "%";
$scope.swapMax = pretty_print_bytes(swapMax);
$scope.swapTotalDisplay = pretty_print_bytes($scope.swapTotal);
// file handles
$scope.fileDescriptorPercentage = (data.system.openFileDescriptorCount / data.system.maxFileDescriptorCount *100).toFixed(1) + "%";
// java memory
var javaMemoryMax = parse_memory_value(data.jvm.memory.raw.max || data.jvm.memory.max);
$scope.javaMemoryTotal = parse_memory_value(data.jvm.memory.raw.total || data.jvm.memory.total);
$scope.javaMemoryUsed = parse_memory_value(data.jvm.memory.raw.used || data.jvm.memory.used);
$scope.javaMemoryTotalPercentage = ($scope.javaMemoryTotal / javaMemoryMax *100).toFixed(1) + "%";
$scope.javaMemoryUsedPercentage = ($scope.javaMemoryUsed / $scope.javaMemoryTotal *100).toFixed(1) + "%";
$scope.javaMemoryPercentage = ($scope.javaMemoryUsed / javaMemoryMax * 100).toFixed(1) + "%";
$scope.javaMemoryTotalDisplay = pretty_print_bytes($scope.javaMemoryTotal);
$scope.javaMemoryUsedDisplay = pretty_print_bytes($scope.javaMemoryUsed); // @todo These should really be an AngularJS Filter: {{ javaMemoryUsed | bytes }}
$scope.javaMemoryMax = pretty_print_bytes(javaMemoryMax);
// no info bar:
$scope.noInfo = !(
data.system.totalPhysicalMemorySize && data.system.freePhysicalMemorySize &&
data.system.totalSwapSpaceSize && data.system.freeSwapSpaceSize &&
data.system.openFileDescriptorCount && data.system.maxFileDescriptorCount);
// save a copy of the original commandline args
$scope.commandLineArgsUnsorted = [...data.jvm.jmx.commandLineArgs];
// get commandline args latest orderby or defaults to "Unsorted"
$scope.commandLineOrderBy = sessionStorage.getItem("commandline.orderby") || "Unsorted";
$scope.showCommandLineArgs();
});
};
$scope.toggleCommandLineOrder = function() {
$scope.commandLineOrderBy = ($scope.commandLineOrderBy=="Sorted") ? "Unsorted":"Sorted";
sessionStorage.setItem("commandline.orderby", $scope.commandLineOrderBy);
$scope.showCommandLineArgs();
}
$scope.showCommandLineArgs = function() {
if ($scope.commandLineOrderBy == "Sorted") {
$scope.commandLineArgs = [...$scope.commandLineArgsUnsorted].sort();
} else {
$scope.commandLineArgs = $scope.commandLineArgsUnsorted;
}
}
$scope.reload();
});
var parse_memory_value = function( value ) {
if( value !== Number( value ) )
{
var units = 'BKMGTPEZY';
var match = value.match( /^(\d+([,\.]\d+)?) (\w).*$/ );
var value = parseFloat( match[1] ) * Math.pow( 1024, units.indexOf( match[3].toUpperCase() ) );
}
return value;
};
const parse_release_date = function(value) {
const match = value.match( /.* (20\d\d-[0-1]\d-[0-3]\d) .*/ );
return match === null ? new Date() : new Date(match[1]);
};
var pretty_print_bytes = function(byte_value) {
var unit = null;
byte_value /= 1024;
byte_value /= 1024;
unit = 'MB';
if( 1024 <= byte_value ) {
byte_value /= 1024;
unit = 'GB';
}
return byte_value.toFixed( 2 ) + ' ' + unit;
};

View File

@@ -0,0 +1,45 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('JavaPropertiesController',
function($scope, Properties, Constants){
$scope.resetMenu("java-props", Constants.IS_ROOT_PAGE);
$scope.refresh = function() {
Properties.get(function(data) {
var sysprops = data["system.properties"];
var sep = sysprops["path.separator"]
var props = [];
for (var key in sysprops) {
var value = sysprops[key];
var values = value.split(sep);
if (value === sep) {
values = [':'];
}
props.push({
name: key.replace(/\./g, '.&#8203;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;'),
values: values
});
}
$scope.pathSeparator = sep;
$scope.props = props;
});
};
$scope.refresh();
});

View File

@@ -0,0 +1,164 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var format_time_content = function( time, timeZone ) {
var format_time_options = {};
if (timeZone && timeZone!="Local") {
format_time_options.timeZone = timeZone;
}
return time.toLocaleString( undefined, format_time_options );
}
solrAdminApp.controller('LoggingController',
function($scope, $timeout, $cookies, Logging, Constants){
$scope.resetMenu("logging", Constants.IS_ROOT_PAGE);
$scope.timezone = $cookies.logging_timezone || "Local";
$scope.refresh = function() {
Logging.events(function(data) {
$scope.since = new Date();
$scope.sinceDisplay = format_time_content($scope.since, "Local");
var events = data.history.docs;
for (var i=0; i<events.length; i++) {
var event = events[i];
var time = new Date(event.time);
event.local_time = format_time_content(time, "Local");
event.utc_time = format_time_content(time, "UTC");
event.loggerBase = event.logger.split( '.' ).pop();
if( !event.trace ) {
var lines = event.message.split( "\n" );
if( lines.length > 1) {
event.trace = event.message;
event.message = lines[0];
}
}
event.message = event.message.replace(/,/g, ',&#8203;');
event.showTrace = false;
}
$scope.events = events;
$scope.watcher = data.watcher;
/* @todo sticky_mode
// state element is in viewport
sticky_mode = ( state.position().top <= $( window ).scrollTop() + $( window ).height() - ( $( 'body' ).height() - state.position().top ) );
// initial request
if( 0 === since ) {
sticky_mode = true;
}
$scope.loggingEvents = events;
if( sticky_mode )
{
$( 'body' )
.animate
(
{ scrollTop: state.position().top },
1000
);
}
*/
});
$scope.timeout = $timeout($scope.refresh, 10000);
var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
$timeout.cancel($scope.timeout);
onRouteChangeOff();
});
};
$scope.refresh();
$scope.toggleRefresh = function() {
if(!$scope.stopped) {
$scope.stopped = true;
$timeout.cancel($scope.timeout);
} else {
$scope.stopped = false;
$scope.timeout = $timeout($scope.refresh, 10000);
}
};
$scope.toggleTimezone = function() {
$scope.timezone = ($scope.timezone=="Local") ? "UTC":"Local";
$cookies.logging_timezone = $scope.timezone;
}
$scope.toggleRow = function(event) {
event.showTrace =! event.showTrace;
};
}
)
.controller('LoggingLevelController',
function($scope, Logging) {
$scope.resetMenu("logging-levels");
var packageOf = function(logger) {
var parts = logger.name.split(".");
return !parts.pop() ? "" : parts.join(".");
};
var shortNameOf = function(logger) {return logger.name.split(".").pop();}
var makeTree = function(loggers, packag) {
var tree = [];
for (var i=0; i<loggers.length; i++) {
var logger = loggers[i];
logger.packag = packageOf(logger);
logger.short = shortNameOf(logger);
if (logger.packag == packag) {
logger.children = makeTree(loggers, logger.name);
tree.push(logger);
}
}
return tree;
};
$scope.refresh = function() {
Logging.levels(function(data) {
$scope.logging = makeTree(data.loggers, "");
$scope.watcher = data.watcher;
$scope.levels = [];
for (level in data.levels) {
$scope.levels.push({name:data.levels[level], pos:level});
}
});
};
$scope.toggleOptions = function(logger) {
if (!$scope.isPermitted(permissions.CONFIG_EDIT_PERM)) {
return;
}
if (logger.showOptions) {
logger.showOptions = false;
delete $scope.currentLogger;
} else {
if ($scope.currentLogger) {
$scope.currentLogger.showOptions = false;
}
logger.showOptions = true;
$scope.currentLogger = logger;
}
};
$scope.setLevel = function(logger, newLevel) {
if (!$scope.isPermitted(permissions.CONFIG_EDIT_PERM)) {
return;
}
var setString = logger.name + ":" + newLevel;
logger.showOptions = false;
Logging.setLevel({set: setString}, function(data) {
$scope.refresh();
});
};
$scope.refresh();
});

View File

@@ -0,0 +1,420 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('LoginController',
['$scope', '$routeParams', '$rootScope', '$location', '$window', 'AuthenticationService', 'Constants',
function ($scope, $routeParams, $rootScope, $location, $window, AuthenticationService, Constants) {
$scope.showHideMenu();
$scope.subPath = $routeParams.route;
$rootScope.exceptions = {};
// Session variables set in app.js 401 interceptor
var wwwAuthHeader = sessionStorage.getItem("auth.wwwAuthHeader");
var authScheme = sessionStorage.getItem("auth.scheme");
if (wwwAuthHeader) {
// Parse www-authenticate header
var wwwHeader = wwwAuthHeader.match(/(\w+)(\s+)?(.*)/);
authScheme = "unknown";
var authParams = {};
if (wwwHeader && wwwHeader.length >= 1)
authScheme = wwwHeader[1];
if (wwwHeader && wwwHeader.length >= 3)
authParams = www_auth_parse_params(wwwHeader[3]);
if (typeof authParams === 'string' || authParams instanceof String) {
$scope.authParamsError = authParams;
} else {
$scope.authParamsError = undefined;
}
var realm = authParams['realm'];
sessionStorage.setItem("auth.realm", realm);
if (authScheme === 'Basic' || authScheme === 'xBasic') {
authScheme = 'Basic';
}
sessionStorage.setItem("auth.scheme", authScheme);
}
var supportedSchemes = ['Basic', 'Bearer', 'Negotiate', 'Certificate'];
$scope.authSchemeSupported = supportedSchemes.includes(authScheme);
if (authScheme === 'Bearer') {
// Check for OpenId redirect response
var errorText = "";
$scope.isCallback = false;
if ($scope.subPath === 'callback') {
$scope.isCallback = true;
var hash = $location.hash();
var hp = AuthenticationService.decodeHashParams(hash);
var expectedState = sessionStorage.getItem("auth.stateRandom") + "_" + sessionStorage.getItem("auth.location");
sessionStorage.setItem("auth.state", "error");
$scope.authData = AuthenticationService.getAuthDataHeader();
if (!validateState(hp['state'], expectedState)) {
$scope.error = "Problems with OpenID callback";
$scope.errorDescription = errorText;
$scope.http401 = "true";
sessionStorage.setItem("auth.state", "error");
}
else {
var flow = $scope.authData ? $scope.authData['authorization_flow'] : undefined;
console.log("Callback: authorization_flow : " +flow);
var isCodePKCE = flow == 'code_pkce';
if (isCodePKCE) {
// code flow with PKCE
var code = hp['code'];
var tokenEndpoint = $scope.authData['tokenEndpoint'];
// concurrent Solr API calls will trigger 401 and erase session's "auth.realm" in app.js
// save it before it's erased
var authRealm = sessionStorage.getItem("auth.realm");
var data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': $window.location.href.split('#')[0],
'scope': "openid " + $scope.authData['scope'],
'code_verifier': sessionStorage.getItem('codeVerifier'),
"client_id": $scope.authData['client_id']
};
console.debug(`Callback. Got code: ${code} \nCalling token endpoint:: ${tokenEndpoint} `);
AuthenticationService.getOAuthTokens(tokenEndpoint, data).then(function(response) {
var accessToken = response.access_token;
var idToken = response.id_token;
var tokenType = response.access_type;
sessionStorage.setItem("auth.realm", authRealm);
processTokensResponse(accessToken, idToken, tokenType, expectedState, hp);
}).catch(function (error) {
errorText += "Error calling token endpoint. ";
$scope.error = "Problems with OpenID callback";
$scope.errorDescription = errorText;
$scope.http401 = "true";
sessionStorage.setItem("auth.state", "error");
if (error && error.data) {
console.error("Error getting tokens: " + JSON.stringify(error.data));
} else {
console.error("Error getting tokens: " + error);
}
});
}
else {
// implicit flow
processTokensResponse(hp['access_token'], hp['id_token'], hp['token_type'], expectedState, hp);
}
}
}
}
function validateState(state, expectedState) {
if (state !== expectedState) {
$scope.error = "Problem with auth callback";
console.error("Expected state param " + expectedState + " but got " + state);
errorText += "Invalid values in state parameter. ";
return false;
}
return true;
}
function processTokensResponse(accessToken, idToken, tokenType, expectedState, hp) {
if (accessToken && hp['state']) {
// Validate token type.
if (!tokenType) {
//Assume the type is 'bearer' if it's not returned. Most IdProviders support 'bearer' by default but don't always return the type.
tokenType = "bearer";
}
else if(tokenType.toLowerCase() !== "bearer") {
console.error("Expected token_type param 'bearer', but got " + tokenType);
errorText += "Invalid values in token_type parameter. ";
}
// Unpack ID token and validate nonce, get username
if (idToken) {
var idTokenArray = idToken.split(".");
if (idTokenArray.length === 3) {
var payload = AuthenticationService.decodeJwtPart(idTokenArray[1]);
if (!payload['nonce'] || payload['nonce'] !== sessionStorage.getItem("auth.nonce")) {
errorText += "Invalid 'nonce' value, possible attack detected. Please log in again. ";
}
if (errorText === "") {
sessionStorage.setItem("auth.username", payload['sub']);
sessionStorage.setItem("auth.header", "Bearer " + accessToken);
sessionStorage.removeItem("auth.statusText");
sessionStorage.removeItem("auth.stateRandom");
sessionStorage.removeItem("auth.wwwAuthHeader");
console.log("User " + payload['sub'] + " is logged in");
var redirectTo = sessionStorage.getItem("auth.location");
console.log("Redirecting to stored location " + redirectTo);
sessionStorage.setItem("auth.state", "authenticated");
sessionStorage.removeItem("http401");
sessionStorage.setItem("auth.scheme", "Bearer");
$location.path(redirectTo).hash("");
}
} else {
console.error("Expected JWT compact id_token param but got " + idTokenArray);
errorText += "Invalid values in id_token parameter. ";
}
} else {
console.error("Callback was missing the id_token parameter, could not validate nonce.");
errorText += "Callback was missing the id_token parameter, could not validate nonce. ";
}
if (accessToken.split(".").length !== 3) {
console.error("Expected JWT compact access_token param but got " + accessToken);
errorText += "Invalid values in access_token parameter. ";
}
if (errorText !== "") {
$scope.error = "Problems with OpenID callback";
$scope.errorDescription = errorText;
$scope.http401 = "true";
}
// End callback processing
} else if (hp['error']) {
// The callback had errors
console.error("Error received from idp: " + hp['error']);
var errorDescriptions = {};
errorDescriptions['invalid_request'] = "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.";
errorDescriptions['unauthorized_client'] = "The client is not authorized to request an access token using this method.";
errorDescriptions['access_denied'] = "The resource owner or authorization server denied the request.";
errorDescriptions['unsupported_response_type'] = "The authorization server does not support obtaining an access token using this method.";
errorDescriptions['invalid_scope'] = "The requested scope is invalid, unknown, or malformed.";
errorDescriptions['server_error'] = "The authorization server encountered an unexpected condition that prevented it from fulfilling the request.";
errorDescriptions['temporarily_unavailable'] = "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.";
$scope.error = "Callback from Id Provider contained error. ";
if (hp['error_description']) {
$scope.errorDescription = decodeURIComponent(hp['error_description']);
} else {
$scope.errorDescription = errorDescriptions[hp['error']];
}
if (hp['error_uri']) {
$scope.errorDescription += " More information at " + hp['error_uri'] + ". ";
}
if (hp['state'] !== expectedState) {
$scope.errorDescription += "The state parameter returned from ID Provider did not match the one we sent.";
}
sessionStorage.setItem("auth.state", "error");
}
else{
console.error(`Invalid data received from idp: accessToken: ${accessToken},
idToken: ${idToken}, state: ${hp['state']}`);
errorText += "Invalid data received from the OpenID provider. ";
$scope.http401 = "true";
$scope.error = "Problems with OpenID callback.";
$scope.errorDescription = errorText;
sessionStorage.setItem("auth.state", "error");
}
}
if (errorText === "" && !$scope.error && authParams) {
$scope.error = authParams['error'];
$scope.errorDescription = authParams['error_description'];
$scope.authData = AuthenticationService.getAuthDataHeader();
}
$scope.authScheme = sessionStorage.getItem("auth.scheme");
$scope.authRealm = sessionStorage.getItem("auth.realm");
$scope.wwwAuthHeader = sessionStorage.getItem("auth.wwwAuthHeader");
$scope.statusText = sessionStorage.getItem("auth.statusText");
$scope.authLocation = sessionStorage.getItem("auth.location");
$scope.authLoggedinUser = sessionStorage.getItem("auth.username");
$scope.authHeader = sessionStorage.getItem("auth.header");
$scope.login = function () {
AuthenticationService.SetCredentials($scope.username, $scope.password);
$location.path($scope.authLocation); // Redirect to the location that caused the login prompt
};
$scope.logout = function() {
// reset login status
AuthenticationService.ClearCredentials();
$location.path("/");
};
$scope.jwtLogin = async function () {
var stateRandom = Math.random().toString(36).substr(2);
sessionStorage.setItem("auth.stateRandom", stateRandom);
var authState = stateRandom + "_" + sessionStorage.getItem("auth.location");
var authNonce = Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2);
sessionStorage.setItem("auth.nonce", authNonce);
var authData = AuthenticationService.getAuthDataHeader();
var flow = authData ? authData['authorization_flow'] : "implicit";
console.log("jwtLogin flow: "+ flow);
var isCodePKCE = flow == 'code_pkce';
var params = {};
if (isCodePKCE) {
console.debug("Login with 'Code PKCE' flow");
var codeVerifier = AuthenticationService.generateCodeVerifier();
var code_challenge = await AuthenticationService.generateCodeChallengeFromVerifier(codeVerifier);
var codeChallengeMethod = AuthenticationService.getCodeChallengeMethod();
sessionStorage.setItem('codeVerifier', codeVerifier);
params = {
"response_type": "code",
"client_id": $scope.authData['client_id'],
"redirect_uri": $window.location.href.split('#')[0],
"scope": "openid " + $scope.authData['scope'],
"state": authState,
"nonce": authNonce,
"response_mode": "fragment",
"code_challenge": code_challenge,
"code_challenge_method": codeChallengeMethod
};
}
else {
console.debug("Login with 'Implicit' flow");
params = {
"response_type": "id_token token",
"client_id": $scope.authData['client_id'],
"redirect_uri": $window.location.href.split('#')[0],
"scope": "openid " + $scope.authData['scope'],
"state": authState,
"nonce": authNonce,
"response_mode": 'fragment',
"grant_type": 'implicit'
};
}
var endpointBaseUrl = $scope.authData['authorizationEndpoint'];
var loc = endpointBaseUrl + "?" + paramsToString(params);
console.log("Redirecting to " + loc);
sessionStorage.setItem("auth.state", "expectCallback");
$window.location.href = loc;
function paramsToString(params) {
var arr = [];
for (var p in params) {
if( params.hasOwnProperty(p) ) {
arr.push(p + "=" + encodeURIComponent(params[p]));
}
}
return arr.join("&");
}
};
$scope.jwtIsLoginNode = function() {
var redirect = $scope.authData ? $scope.authData['redirect_uris'] : undefined;
if (redirect && Array.isArray(redirect) && redirect.length > 0) {
var isLoginNode = false;
redirect.forEach(function(uri) { // Check that current node URL is among the configured callback URIs
if ($window.location.href.startsWith(uri)) isLoginNode = true;
});
return isLoginNode;
} else {
return true; // no redirect UIRs configured, all nodes are potential login nodes
}
};
$scope.jwtFindLoginNode = function() {
var redirect = $scope.authData ? $scope.authData['redirect_uris'] : undefined;
if (redirect && Array.isArray(redirect) && redirect.length > 0) {
var loginNode = redirect[0];
redirect.forEach(function(uri) { // if current node is in list, return its callback uri
if ($window.location.href.startsWith(uri)) loginNode = uri;
});
return loginNode;
} else {
return $window.location.href.split('#')[0]; // Return base url of current URL as the url to use
}
};
// Redirect to login node if this is not a valid one
$scope.jwtGotoLoginNode = function() {
if (!$scope.jwtIsLoginNode()) {
$window.location.href = $scope.jwtFindLoginNode();
}
};
$scope.jwtLogout = function() {
// reset login status
AuthenticationService.ClearCredentials();
$location.path("/");
};
$scope.isLoggedIn = function() {
return (sessionStorage.getItem("auth.username") !== null);
};
}]);
// This function is copied and adapted from MIT-licensed https://github.com/randymized/www-authenticate/blob/master/lib/parsers.js
www_auth_parse_params= function (header) {
// This parser will definitely fail if there is more than one challenge
var params = {};
var tok, last_tok, _i, _len, key, value;
var state= 0; //0: token,
var m= header.split(/([",=])/);
for (_i = 0, _len = m.length; _i < _len; _i++) {
last_tok= tok;
tok = m[_i];
if (!tok.length) continue;
switch (state) {
case 0: // token
key= tok.trim();
state= 1; // expect equals
continue;
case 1: // expect equals
if ('=' != tok) return 'Equal sign was expected after '+key;
state= 2;
continue;
case 2: // expect value
if ('"' == tok) {
value= '';
state= 3; // expect quoted
continue;
}
else {
params[key]= value= tok.trim();
state= 9; // expect comma or end
continue;
}
case 3: // handling quoted string
if ('"' == tok) {
state= 8; // end quoted
continue;
}
else {
value+= tok;
state= 3; // continue accumulating quoted string
continue;
}
case 8: // end quote encountered
if ('"' == tok) {
// double quoted
value+= '"';
state= 3; // back to quoted string
continue;
}
if (',' == tok) {
params[key]= value;
state= 0;
continue;
}
else {
return 'Unexpected token ('+tok+') after '+value+'"';
}
continue;
case 9: // expect commma
if (',' != tok) return 'Comma expected after '+value;
state= 0;
continue;
}
}
switch (state) { // terminal state
case 0: // Empty or ignoring terminal comma
case 9: // Expecting comma or end of header
return params;
case 8: // Last token was end quote
params[key]= value;
return params;
default:
return 'Unexpected end of www-authenticate value.';
}
};

View File

@@ -0,0 +1,158 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//helper for formatting JSON and others
solrAdminApp.controller('ParamSetsController',
function($scope, $location, $routeParams, ParamSet, Constants) {
$scope.paramsetList = [];
$scope.resetMenu("paramsets", Constants.IS_COLLECTION_PAGE);
$scope.sampleAPICommand = {
"set": {
"myQueries": {
"defType": "edismax",
"rows": "5",
"df": "text_all"
}
}
}
$scope.selectParamset = function() {
$location.search("paramset", $scope.name);
$scope.getParamset($scope.name);
}
$scope.getParamset = function (paramset) {
$scope.refresh();
var params = {};
params.core = $routeParams.core;
params.wt = "json";
params.name = paramset;
ParamSet.get(params, callback, failure);
///////
function callback(success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.response = JSON.stringify(success, null, ' ');
var apiPayload = {
"set": success.response.params
};
// remove json key that is defined as "", it can't be submitted via the API.
var paramsetName = Object.keys(apiPayload.set)[0];
delete apiPayload.set[paramsetName][""]
$scope.paramsetContent = JSON.stringify(apiPayload, null, ' ');
}
function failure (failure) {
$scope.responseStatus = failure;
}
}
$scope.getParamsets = function () {
$scope.refresh();
var params = {};
params.core = $routeParams.core;
params.wt = "json";
ParamSet.get(params, callback, failure);
///////
function callback(success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.response = JSON.stringify(success, null, ' ');
$scope.paramsetList = success.response.params ? Object.keys(success.response.params) : [];
}
function failure (failure) {
$scope.responseStatus = failure;
}
}
$scope.getParamsets();
if ($routeParams.paramset){
$scope.name = $routeParams.paramset;
$scope.getParamset($routeParams.paramset);
}
$scope.refresh = function () {
$scope.paramsetContent = "";
$scope.placeholder = JSON.stringify($scope.sampleAPICommand, null, ' ');
};
$scope.refresh();
$scope.submit = function () {
var params = {};
params.core = $routeParams.core;
params.wt = "json";
ParamSet.submit(params, $scope.paramsetContent, callback, failure);
///////
function callback(success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.response = JSON.stringify(success, null, ' ');
$scope.name = null;
$scope.getParamsets();
}
function failure (failure) {
$scope.responseStatus = failure;
}
}
$scope.deleteParamset = function () {
var params = {};
params.core = $routeParams.core;
params.wt = "json";
params.name = $scope.name;
var apiPayload = {
"delete": [$scope.name]
};
ParamSet.submit(params, apiPayload, callback, failure);
///////
function callback(success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.response = JSON.stringify(success, null, ' ');
$scope.getParamsets();
}
function failure (failure) {
$scope.responseStatus = failure;
}
}
});

View File

@@ -0,0 +1,167 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('PluginsController',
function($scope, $rootScope, $routeParams, $location, Mbeans, Constants) {
$scope.resetMenu("plugins", Constants.IS_CORE_PAGE);
if ($routeParams.legacytype) {
// support legacy URLs. Angular cannot change #path without reloading controller
$location.path("/"+$routeParams.core+"/plugins");
$location.search("type", $routeParams.legacytype);
return;
}
$scope.refresh = function() {
Mbeans.stats({core: $routeParams.core}, function (data) {
var type = $location.search().type;
$scope.types = getPluginTypes(data, type);
$scope.type = getSelectedType($scope.types, type);
if ($scope.type && $routeParams.entry) {
$scope.plugins = $routeParams.entry.split(",");
openPlugins($scope.type, $scope.plugins);
} else {
$scope.plugins = [];
}
});
};
$scope.selectPluginType = function(type) {
$location.search({entry:null, type: type.lower});
$scope.type = type;
};
$scope.selectPlugin = function(plugin) {
plugin.open = !plugin.open;
if (plugin.open) {
$scope.plugins.push(plugin.name);
} else {
$scope.plugins.splice($scope.plugins.indexOf(plugin.name), 1);
}
if ($scope.plugins.length==0) {
$location.search("entry", null);
} else {
$location.search("entry", $scope.plugins.join(','));
}
}
$scope.startRecording = function() {
$scope.isRecording = true;
Mbeans.reference({core: $routeParams.core}, function(data) {
$scope.reference = data.reference;
console.log($scope.reference);
})
}
$scope.stopRecording = function() {
$scope.isRecording = false;
console.log($scope.reference);
Mbeans.delta({core: $routeParams.core}, $scope.reference, function(data) {
parseDelta($scope.types, data);
});
}
$scope.refresh();
});
var getPluginTypes = function(data, selected) {
var keys = [];
var mbeans = data["solr-mbeans"];
for (var i=0; i<mbeans.length; i+=2) {
var key = mbeans[i];
var lower = key.toLowerCase();
var plugins = getPlugins(mbeans[i+1]);
if (plugins.length == 0) continue;
keys.push({name: key,
selected: lower == selected,
changes: 0,
lower: lower,
plugins: plugins
});
}
keys.sort(function(a,b) {return a.name > b.name});
return keys;
};
var getPlugins = function(data) {
var plugins = [];
for (var key in data) {
var pluginProperties = data[key];
var stats = pluginProperties.stats;
delete pluginProperties.stats;
for (var stat in stats) {
// add breaking space after a bracket or @ to handle wrap long lines:
stats[stat] = new String(stats[stat]).replace( /([\(@])/g, '$1&#8203;');
}
plugin = {name: key, changed: false, stats: stats, open:false};
plugin.properties = pluginProperties;
plugins.push(plugin);
}
plugins.sort(function(a,b) {return a.name > b.name});
return plugins;
};
var getSelectedType = function(types, selected) {
if (selected) {
for (var i in types) {
if (types[i].lower == selected) {
return types[i];
}
}
}
};
var parseDelta = function(types, data) {
var getByName = function(list, name) {
for (var i in list) {
if (list[i].name == name) return list[i];
}
}
var mbeans = data["solr-mbeans"]
for (var i=0; i<mbeans.length; i+=2) {
var typeName = mbeans[i];
var type = getByName(types, typeName);
var plugins = mbeans[i+1];
for (var key in plugins) {
var changedPlugin = plugins[key];
if (changedPlugin._changed_) {
var plugin = getByName(type.plugins, key);
var stats = changedPlugin.stats;
delete changedPlugin.stats;
plugin.properties = changedPlugin;
for (var stat in stats) {
// add breaking space after a bracket or @ to handle wrap long lines:
plugin.stats[stat] = new String(stats[stat]).replace( /([\(@])/g, '$1&#8203;');
}
plugin.changed = true;
type.changes++;
}
}
}
};
var openPlugins = function(type, selected) {
for (var i in type.plugins) {
var plugin = type.plugins[i];
plugin.open = selected.indexOf(plugin.name)>=0;
}
}

View File

@@ -0,0 +1,308 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('QueryController',
function($scope, $route, $routeParams, $location, Query, Constants, ParamSet){
$scope.resetMenu("query", Constants.IS_COLLECTION_PAGE);
$scope._models = [];
$scope.filters = [{fq:""}];
$scope.rawParams = [{rawParam:""}];
$scope.val = {};
$scope.val['q'] = "*:*";
$scope.val['q.op'] = "OR";
$scope.val['defType'] = "";
$scope.val['indent'] = true;
$scope.useParams = [];
getParamsets();
function getParamsets() {
var params = {};
params.core = $routeParams.core;
params.wt = "json";
ParamSet.get(params, callback, failure);
///////
function callback(success) {
$scope.responseStatus = "success";
delete success.$promise;
delete success.$resolved;
$scope.paramsetList = success.response.params ? Object.keys(success.response.params) : [];
}
function failure (failure) {
$scope.responseStatus = failure;
}
}
// get list of ng-models that have a form element
function setModels(argTagName){
var fields = document.getElementsByTagName(argTagName);
for( var i = 0, iLen = fields.length; i<iLen; i++ ){
var model = fields[i].getAttribute("ng-model");
if( model ){
$scope._models.push({modelName: model, element: fields[i]});
}
}
}
function setUrlParams(){
var urlParams = $location.search();
for( var p in urlParams ){
if( !urlParams.hasOwnProperty(p) ){
continue;
}
// filters and rawParams are handled specially because of possible multiple values
if( p === "fq" ) {
addFilters(urlParams[p]);
} else {
setParam(p, urlParams[p]);
}
}
}
function setParam(argKey, argValue, argProperty){
if( argProperty ){
$scope[argProperty][argKey] = argValue;
} else if ( $scope._models.map(function(field){return field.modelName}).indexOf("val['" + argKey + "']") > -1 ) {
// parameters stored in "val" will be used in both links (admin link and REST-request)
var index = $scope._models.map(function(field){return field.modelName}).indexOf("val['" + argKey + "']");
var field = $scope._models[index].element;
if( argValue === "true" || argValue === "false"){
// use real booleans
argValue = argValue === "true";
} else if( field.tagName.toUpperCase() === "INPUT" && field.type === "checkbox" && (argValue === "on" || argValue === "off") ){
argValue = argValue === "on";
}
$scope.val[argKey] = argValue;
} else if( $scope._models.map(function(field){return field.modelName}).indexOf(argKey) > -1 ) {
// parameters that will only be used to generate the admin link
if (argKey === 'useParams'){
$scope[argKey] = argValue.split(",")
}
else {
$scope[argKey] = argValue;
}
} else {
insertToRawParams(argKey, argValue);
}
}
// store not processed values to be displayed in a field
function insertToRawParams(argKey, argValue){
if( ($scope.rawParams.length === 0) || ($scope.rawParams.length === 1 && $scope.rawParams[0].rawParam === "") ){
$scope.rawParams = [];
}
if (argValue instanceof Array) {
for (var index in argValue) {
$scope.rawParams.push({rawParam: argKey + "=" + argValue[index]});
}
} else {
$scope.rawParams.push({rawParam: argKey + "=" + argValue});
}
}
function addFilters(argObject){
if( argObject ){
$scope.filters = [];
if( Array.isArray(argObject) ){
for( var i = 0, iLen = argObject.length; i<iLen; i++ ){
$scope.filters.push({fq: argObject[i]});
}
} else {
$scope.filters.push({fq: argObject});
}
}
}
$scope.doQuery = function(isPageReload) {
var params = {};
var set = function(key, value) {
if (params[key]) {
params[key].push(value);
} else {
params[key] = [value];
}
}
var copy = function(params, query) {
for (var key in query) {
terms = query[key];
// Booleans have no length property - only set them if true
if (typeof(terms) == typeof(true) || (terms.length && terms.length > 0 && key[0]!=="$") ) {
set(key, terms);
}
}
}
// remove non-visible fields from the request-params
var purgeParams = function(params, fields, bRemove){
if( !bRemove ){
return;
}
for( var i = 0, iLen = fields.length; i<iLen; i++ ){
if( params.hasOwnProperty(fields[i]) ){
delete params[fields[i]];
}
}
}
var getDependentFields = function(argParam){
return Object.keys($scope.val)
.filter(function(param){
return param.indexOf(argParam) === 0;
});
}
var getHighlighterFieldsToPurge = function(hlMethod){
// first, select the fieldsets which ng-show includes val['hl.method'] but not val['hl.method']==${hlMethod}
// then, select their descendants that have ng-model
const selector = `div.fieldset[ng-show*="val['hl.method']"]:not([ng-show*="val['hl.method']=='${hlMethod}'"])
[ng-model]`;
// return the field names
return Array.from(document.querySelectorAll(selector), x => x.name);
}
copy(params, $scope.val);
purgeParams(params, ["q.alt", "qf", "mm", "pf", "ps", "qs", "tie", "bq", "bf"], $scope.val.defType !== "dismax" && $scope.val.defType !== "edismax");
purgeParams(params, ["uf", "pf2", "pf3", "ps2", "ps3", "boost", "stopwords", "lowercaseOperators"], $scope.val.defType !== "edismax");
purgeParams(params, getDependentFields("hl"), $scope.val.hl !== true);
purgeParams(params, getHighlighterFieldsToPurge($scope.val["hl.method"]), true);
purgeParams(params, getDependentFields("facet"), $scope.val.facet !== true);
purgeParams(params, ["spatial", "pt", "sfield", "d"], $scope.val.spatial !== true);
purgeParams(params, getDependentFields("spellcheck"), $scope.val.spellcheck !== true);
var qt = $scope.qt ? $scope.qt : "/select";
for (var filter in $scope.filters) {
copy(params, $scope.filters[filter]);
}
for (var rawIndex in $scope.rawParams) {
if ($scope.rawParams[rawIndex].rawParam) {
var rawParam = $scope.rawParams[rawIndex].rawParam.split(/[&\n]/);
for (var i in rawParam) {
var param = rawParam[i];
var equalPos = param.indexOf("=");
if (equalPos > -1) {
set(param.substring(0, equalPos), param.substring(equalPos+1));
} else {
set(param, ""); // Use empty value for params without "="
}
}
}
}
params.core = $routeParams.core;
if (qt[0] === '/') {
params.handler = qt.substring(1);
} else { // Support legacy style handleSelect=true configs
params.handler = "select";
set("qt", qt);
}
// convert useParams to array to generate nice URL.
if (!Array.isArray($scope.useParams)){
params.useParams = $scope.useParams.split(",");
}
else {
params.useParams = $scope.useParams;
}
// create rest result url
var url = Query.url(params);
// convert useParams back to string
if (Array.isArray($scope.useParams)){
params.useParams = $scope.useParams.join(",");
}
else {
params.useParams = $scope.useParams;
}
// create admin page url
var adminParams = {...params};
delete adminParams.handler;
delete adminParams.core
if (!Array.isArray(adminParams.useParams)){
adminParams.useParams = adminParams.useParams.split(",");
}
if( $scope.qt != null ) {
adminParams.qt = [$scope.qt];
}
if (isPageReload) {
if (!Array.isArray(params.useParams)){
params.useParams = params.useParams.split(",");
}
Query.query(params, function (data) {
$scope.lang = $scope.val['wt'];
if (!$scope.lang || $scope.lang === '') {
$scope.lang = "json";
}
$scope.response = data;
// Use relative URL to make it also work through proxies that may have a different host/port/context
$scope.url = url;
$scope.hostPortContext = $location.absUrl().substr(0, $location.absUrl().indexOf("#")); // For display only
});
} else {
var previousUrl = $location.$$url;
for( key in $location.search() ){
$location.search(key, null);
}
for( var key in adminParams ){
if( Array.isArray(adminParams[key]) && adminParams[key].length === 1 ){
adminParams[key] = adminParams[key][0];
}
if( typeof adminParams[key] === typeof true ){
adminParams[key] = adminParams[key].toString();
}
$location.search(key, adminParams[key]);
}
var currentUrl = $location.$$url;
if (previousUrl === currentUrl) { //if the query send with same parameters the query should be executed
$route.reload();
}
}
};
setModels("input");
setModels("textarea");
setModels("select");
setUrlParams();
if ($location.search().q) {
$scope.doQuery(true);
}
$scope.removeFilter = function(index) {
if ($scope.filters.length === 1) {
$scope.filters = [{fq: ""}];
} else {
$scope.filters.splice(index, 1);
}
};
$scope.addFilter = function(index) {
$scope.filters.splice(index+1, 0, {fq:""});
};
$scope.removeRawParam = function (index) {
if ($scope.rawParams.length === 1) {
$scope.rawParams = [{rawParam: ""}];
} else {
$scope.rawParams.splice(index, 1);
}
};
$scope.addRawParam = function (index) {
$scope.rawParams.splice(index+1, 0, {rawParam:""});
};
}
);

View File

@@ -0,0 +1,235 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('ReplicationController',
function($scope, $rootScope, $routeParams, $interval, $timeout, Replication, Constants) {
$scope.resetMenu("replication", Constants.IS_CORE_PAGE);
$scope.iterationCount = 1;
$scope.refresh = function() {
Replication.details({core:$routeParams.core}, function(response) {
var timeout;
var interval;
if ($scope.interval) $interval.cancel($scope.interval);
$scope.isFollower = (response.details.isFollower === 'true');
if ($scope.isFollower) {
$scope.progress = getProgressDetails(response.details.follower);
$scope.iterations = getIterations(response.details.follower);
$scope.versions = getFollowerVersions(response.details);
$scope.settings = getFollowerSettings(response.details);
if ($scope.settings.isReplicating) {
timeout = $timeout($scope.refresh, 1000);
} else if(!$scope.settings.isPollingDisabled && $scope.settings.pollInterval) {
interval = $scope.interval = $interval(function() {
$scope.settings.tick--;
}, 1000, $scope.settings.tick);
timeout = $timeout($scope.refresh, 1000*(1+$scope.settings.tick));
}
} else {
$scope.versions = getLeaderVersions(response.details);
}
$scope.leader = getLeaderSettings(response.details, $scope.isFollower);
var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
if (interval) $interval.cancel(interval);
if (timeout) $timeout.cancel(timeout);
onRouteChangeOff();
});
});
};
$scope.execute = function(command) {
Replication.command({core:$routeParams.core, command:command}, function(data){$scope.refresh()});
}
$scope.showIterations = function() { $scope.iterationCount = 100000}; // limitTo should accept undefined, but doesn't work.
$scope.hideIterations = function() { $scope.iterationCount = 1};
$scope.refresh();
});
var getProgressDetails = function(progress) {
progress.timeRemaining = parseSeconds(progress.timeRemaining);
progress.totalPercent = parseInt(progress.totalPercent);
if (progress.totalPercent === 0) {
progress.totalPercentWidth = "1px";
} else {
progress.totalPercentWidth = progress.totalPercent + "%";
}
progress.currentFileSizePercent = parseInt(progress.currentFileSizePercent);
if (!progress.indexReplicatedAtList) {
progress.indexReplicatedAtList = [];
}
if (!progress.replicationFailedAtList) {
progress.replicationFailedAtList = [];
}
return progress;
};
var getIterations = function(follower) {
var iterations = [];
var find = function(list, date) {
return list.filter(function(e) {return e.date == date});
};
for (var i in follower.indexReplicatedAtList) {
var date = follower.indexReplicatedAtList[i];
var iteration = {date:date, status:"replicated", latest: false};
if (date == follower.indexReplicatedAt) {
iteration.latest = true;
}
iterations.push(iteration);
}
for (var i in follower.replicationFailedAtList) {
var failedDate = follower.replicationFailedAtList[i];
var matchingIterations = find(iterations, failedDate);
if (matchingIterations[0]) {
iteration = matchingIterations[0];
iteration.status = "failed";
} else {
iteration = {date: failedDate, status:"failed", latest:false};
iterations.push(iteration);
}
if (failedDate == follower.replicationFailedAt) {
iteration.latest = true;
}
}
iterations.sort(function(a,b){ return a.date> b.date;}).reverse();
return iterations;
};
var getLeaderVersions = function(data) {
versions = {leaderSearch:{}, leader:{}};
versions.leaderSearch.version = data.indexVersion;
versions.leaderSearch.generation = data.generation;
versions.leaderSearch.size = data.indexSize;
versions.leader.version = data.leader.replicableVersion || '-';
versions.leader.generation = data.leader.replicableGeneration || '-';
versions.leader.size = '-';
return versions;
};
var getFollowerVersions = function(data) {
versions = {leaderSearch: {}, leader: {}, follower: {}};
versions.follower.version = data.indexVersion;
versions.follower.generation = data.generation;
versions.follower.size = data.indexSize;
versions.leader.version = data.follower.leaderDetails.replicableVersion || '-';
versions.leader.generation = data.follower.leaderDetails.replicableGeneration || '-';
versions.leader.size = '-';
versions.leaderSearch.version = data.follower.leaderDetails.indexVersion;
versions.leaderSearch.generation = data.follower.leaderDetails.generation;
versions.leaderSearch.size = data.follower.leaderDetails.indexSize;
versions.changedVersion = data.indexVersion !== data.follower.leaderDetails.indexVersion;
versions.changedGeneration = data.generation !== data.follower.leaderDetails.generation;
return versions;
};
var parseDateToEpoch = function(date) {
// ["Sat Mar 03 11:00:00 CET 2012", "Sat", "Mar", "03", "11:00:00", "CET", "2012"]
var parts = date.match( /^(\w+)\s+(\w+)\s+(\d+)\s+(\d+\:\d+\:\d+)\s+(\w+)\s+(\d+)$/ );
// "Sat Mar 03 2012 10:37:33"
var d = new Date( parts[1] + ' ' + parts[2] + ' ' + parts[3] + ' ' + parts[6] + ' ' + parts[4] );
return d.getTime();
}
var parseSeconds = function(time) {
var seconds = 0;
var arr = new String(time || '').split('.');
var parts = arr[0].split(':').reverse();
for (var i = 0; i < parts.length; i++) {
seconds += ( parseInt(parts[i], 10) || 0 ) * Math.pow(60, i);
}
if (arr[1] && 5 <= parseInt(arr[1][0], 10)) {
seconds++; // treat more or equal than .5 as additional second
}
return seconds;
}
var getFollowerSettings = function(data) {
var settings = {};
settings.leaderUrl = data.follower.leaderUrl;
settings.isPollingDisabled = data.follower.isPollingDisabled == 'true';
settings.pollInterval = data.follower.pollInterval;
settings.isReplicating = data.follower.isReplicating == 'true';
settings.nextExecutionAt = data.follower.nextExecutionAt;
if(settings.isReplicating) {
settings.isApprox = true;
settings.tick = parseSeconds(settings.pollInterval);
} else if (!settings.isPollingDisabled && settings.pollInterval) {
if( settings.nextExecutionAt ) {
settings.nextExecutionAtEpoch = parseDateToEpoch(settings.nextExecutionAt);
settings.currentTime = parseDateToEpoch(data.follower.currentDate);
if( settings.nextExecutionAtEpoch > settings.currentTime) {
settings.isApprox = false;
settings.tick = ( settings.nextExecutionAtEpoch - settings.currentTime) / 1000;
}
}
}
return settings;
};
var getLeaderSettings = function(details, isFollower) {
var leader = {};
var leaderData = isFollower ? details.follower.leaderDetails.leader : details.leader;
leader.replicationEnabled = leaderData.replicationEnabled == "true";
leader.replicateAfter = leaderData.replicateAfter.join(", ");
if (leaderData.confFiles) {
leader.files = [];
var confFiles = leaderData.confFiles.split(',');
for (var i=0; i<confFiles.length; i++) {
var file = confFiles[i];
var short = file;
var title = file;
if (file.indexOf(":")>=0) {
title = file.replace(':', ' » ');
var parts = file.split(':');
if (isFollower) {
short = parts[1];
} else {
short = parts[0];
}
}
leader.files.push({title:title, name:short});
}
}
return leader;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,713 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var cookie_schema_browser_autoload = 'schema-browser_autoload';
solrAdminApp.controller('SchemaController',
function($scope, $routeParams, $location, $cookies, $timeout, Luke, Constants, Schema, Config) {
$scope.resetMenu("schema", Constants.IS_COLLECTION_PAGE);
$scope.refresh = function () {
Luke.schema({core: $routeParams.core}, function (schema) {
Luke.raw({core: $routeParams.core}, function (index) {
var data = mergeIndexAndSchemaData(index, schema.schema);
$scope.fieldsAndTypes = getFieldsAndTypes(data);
$scope.is = {};
var search = $location.search();
leftbar = {};
$scope.isField = $scope.isDynamicField = $scope.isType = false;
$scope.showing = true;
if (search.field) {
$scope.selectedType = "Field";
$scope.is.field = true;
$scope.name = search.field;
leftbar.fields = [$scope.name];
var field = data.fields[$scope.name];
leftbar.types = [field.type];
if (field.dynamicBase) leftbar.dynamicFields = [field.dynamicBase];
if (field.copySources && field.copySources.length>0) {
leftbar.copyFieldSources = sortedObjectArray(field.copySources.sort());
}
if (field.copyDests && field.copyDests.length>0) {
leftbar.copyFieldDests = sortedObjectArray(field.copyDests.sort());
}
$scope.fieldOrType = "field=" + $scope.name;
} else if (search["dynamic-field"]) {
$scope.selectedType = "Dynamic Field";
$scope.is.dynamicField = true;
$scope.name = search["dynamic-field"];
leftbar.dynamicFields = [$scope.name];
leftbar.types = [data.dynamic_fields[$scope.name].type];
$scope.fieldOrType = "dynamic-field=" + $scope.name;
} else if (search.type) {
$scope.selectedType = "Type";
$scope.is.type = true;
$scope.name = search.type;
leftbar.types = [$scope.name];
leftbar.fields = filterFields("fields", data, $scope.name);
leftbar.dynamicFields = filterFields("dynamic_fields", data, $scope.name);
$scope.fieldOrType = "type=" + $scope.name;
} else {
$scope.showing = false;
}
$scope.leftbar = leftbar;
$scope.core = $routeParams.core;
$scope.uniqueKeyField = data.unique_key_field;
$scope.similarity = data.similarity;
if ($scope.similarity && $scope.similarity.className) {
$scope.similarity.className = shortenPackages($scope.similarity.className);
}
$scope.isUniqueKeyField = ($scope.selectedType == "Field" && $scope.name == $scope.uniqueKeyField);
$scope.display = getFieldProperties(data, $routeParams.core, $scope.is, $scope.name);
$scope.analysis = getAnalysisInfo(data, $scope.is, $scope.name);
$scope.isAutoload = $cookies[cookie_schema_browser_autoload] == "true";
if ($scope.isAutoload) {
$scope.toggleTerms();
}
$scope.types = Object.keys(schema.schema.types);
});
});
Config.get({core: $routeParams.core}, function(data) {
$scope.isSchemaUpdatable = (data.config.hasOwnProperty('schemaFactory') == false || data.config.schemaFactory.class == "ManagedIndexSchemaFactory");
});
};
$scope.refresh();
$scope.selectFieldTypeManipulationOption = function() {
$scope.fieldTypeObj = $scope.fieldTypeManipulationOption.template;
}
$scope.showHelp = function (id) {
if ($scope.helpId && ($scope.helpId === id || id === '')) {
delete $scope.helpId;
} else {
$scope.helpId = id;
}
};
$scope.selectFieldOrType = function() {
$location.search($scope.fieldOrType);
}
$scope.toggleAnalyzer = function(analyzer) {
analyzer.show = !analyzer.show;
}
$scope.loadTermInfo = function() {
var params = {fl: $scope.name, core: $routeParams.core};
if ($scope.topTermsCount) {
params.numTerms = $scope.topTermsCount;
}
$scope.isLoadingTerms = true;
Luke.field(params, function (data) {
$scope.isLoadingTerms = false;
$scope.termInfo = getTermInfo(data.fields[$scope.name]);
if (!$scope.topTermsCount) {
$scope.topTermsCount = $scope.termInfo.termCount;
}
});
}
$scope.toggleTerms = function() {
$scope.showTerms = !$scope.showTerms;
if ($scope.showTerms) {
$scope.loadTermInfo();
}
}
$scope.loadAllTerms = function() {
$scope.topTermsCount = $scope.termInfo.maxTerms;
$scope.loadTermInfo();
}
$scope.toggleAutoload = function() {
$scope.isAutoload = !$scope.isAutoload;
$cookies[cookie_schema_browser_autoload] = $scope.isAutoload;
console.log("cookie: " + $cookies[cookie_schema_browser_autoload]);
}
$scope.hideAll = function() {
$scope.showAddField = false;
$scope.showAddDynamicField = false;
$scope.showAddCopyField = false;
$scope.showManipulateFieldType = false;
}
$scope.toggleAddField = function() {
if ($scope.showAddField && $scope.adding == "field") {
$scope.hideAll();
} else {
$scope.hideAll();
$scope.showAddField = true;
$scope.adding = "field";
$scope.newField = {
stored: "true",
indexed: "true",
uninvertible: "true"
}
delete $scope.addErrors;
}
}
$scope.addField = function() {
delete $scope.addErrors;
var data = {"add-field": $scope.newField};
Schema.post({core: $routeParams.core}, data, function(data) {
if (data.errors) {
$scope.addErrors = data.errors[0].errorMessages;
if (typeof $scope.addErrors === "string") {
$scope.addErrors = [$scope.addErrors];
}
} else {
$scope.added = true;
$timeout(function() {
$scope.showAddField = false;
$scope.added = false;
$scope.refresh();
}, 1500);
}
});
}
$scope.toggleAddDynamicField = function() {
if ($scope.showAddField && $scope.adding == "dynamicField") {
$scope.hideAll();
} else {
$scope.hideAll();
$scope.showAddField = true;
$scope.adding = "dynamicField";
$scope.newField = {
stored: "true",
indexed: "true"
}
delete $scope.addErrors;
}
}
$scope.addDynamicField = function() {
delete $scope.addErrors;
var data = {"add-dynamic-field": $scope.newField};
Schema.post({core: $routeParams.core}, data, function(data) {
if (data.errors) {
$scope.addErrors = data.errors[0].errorMessages;
if (typeof $scope.addErrors === "string") {
$scope.addErrors = [$scope.addErrors];
}
} else {
$scope.added = true;
$timeout(function() {
$scope.showAddField = false;
$scope.added = false;
$scope.refresh();
}, 1500);
}
});
}
$scope.toggleAddCopyField = function() {
if ($scope.showAddCopyField) {
$scope.hideAll();
} else {
$scope.hideAll();
$scope.showAddCopyField = true;
$scope.copyField = {};
delete $scope.addCopyFieldErrors;
}
}
$scope.addCopyField = function() {
delete $scope.addCopyFieldErrors;
var data = {"add-copy-field": $scope.copyField};
Schema.post({core: $routeParams.core}, data, function(data) {
if (data.errors) {
$scope.addCopyFieldErrors = data.errors[0].errorMessages;
if (typeof $scope.addCopyFieldErrors === "string") {
$scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
}
} else {
$scope.showAddCopyField = false;
$timeout($scope.refresh, 1500);
}
});
}
$scope.toggleDelete = function() {
if ($scope.showDelete) {
$scope.showDelete = false;
} else {
if ($scope.is.field) {
$scope.deleteData = {'delete-field': {name: $scope.name}};
} else if ($scope.is.dynamicField) {
$scope.deleteData = {'delete-dynamic-field': {name: $scope.name}};
} else {
alert("TYPE NOT KNOWN");
}
$scope.showDelete = true;
}
}
$scope.delete = function() {
Schema.post({core: $routeParams.core}, $scope.deleteData, function(data) {
if (data.errors) {
$scope.deleteErrors = data.errors[0].errorMessages;
if (typeof $scope.deleteErrors === "string") {
$scope.deleteErrors = [$scope.deleteErrors];
}
} else {
$scope.deleted = true;
$timeout(function() {
$location.search("");
}, 1500
);
}
});
}
$scope.toggleDeleteCopyField = function(field) {
field.show = !field.show;
delete field.errors;
}
$scope.deleteCopyField = function(field, source, dest) {
data = {'delete-copy-field': {source: source, dest: dest}};
Schema.post({core: $routeParams.core}, data, function(data) {
if (data.errors) {
field.errors = data.errors[0].errorMessages;
if (typeof $scope.deleteErrors === "string") {
field.errors = [field.errors];
}
} else {
field.deleted = true;
$timeout($scope.refresh, 1500);
}
});
}
$scope.toggleManipulateFieldType = function() {
if ($scope.showManipulateFieldType) {
$scope.hideAll();
} else {
$scope.hideAll();
$scope.showManipulateFieldType = true;
$scope.adding = "fieldType";
$scope.fieldTypeObj = ""
delete $scope.addErrors;
}
$scope.fieldTypeManipulationOptions = getFieldTypeManipulationOptions();
$scope.fieldTypeManipulationOption = $scope.fieldTypeManipulationOptions[0];
$scope.selectFieldTypeManipulationOption();
}
$scope.manipulateFieldType = function() {
delete $scope.manipulateFieldTypeErrors;
var data = JSON.parse($scope.fieldTypeObj);
Schema.post({core: $routeParams.core}, data, function(data) {
if (data.errors) {
$scope.manipulateFieldTypeErrors = data.errors[0].errorMessages;
if (typeof $scope.manipulateFieldTypeErrors === "string") {
$scope.manipulateFieldTypeErrors = [$scope.manipulateFieldTypeErrors];
}
} else {
$scope.added = true;
$timeout(function() {
$scope.showManipulateFieldType = false;
$scope.added = false;
$scope.refresh();
}, 1500);
}
});
}
}
);
var getFieldsAndTypes = function(data) {
var fieldsAndTypes = [];
var fields = Object.keys(data.fields).sort();
for (var i in fields) {
fieldsAndTypes.push({
group: "Fields",
value: "field=" + fields[i],
label: fields[i]
});
}
var dynamic_fields = Object.keys(data.dynamic_fields).sort();
for (var i in dynamic_fields) {
fieldsAndTypes.push({
group: "Dynamic Fields",
value: "dynamic-field=" + dynamic_fields[i],
label: dynamic_fields[i]
});
}
var types = Object.keys(data.types).sort();
for (var i in types) {
fieldsAndTypes.push({
group: "Types",
value: "type=" + types[i],
label: types[i]
});
}
return fieldsAndTypes;
};
var filterFields = function(type, data, name) {
var fields = [];
for (var i in data.types[name].fields) {
var field = data.types[name].fields[i];
if (data[type][field]) {
fields.push(field)
}
}
return fields.sort();
}
var mergeIndexAndSchemaData = function(index, schema) {
var data = {
unique_key_field: null,
similarity: null,
key: {},
fields: {},
dynamic_fields: {},
types: {},
relations: {
f_df: {},
f_t: {},
df_f: {},
df_t: {},
t_f: {},
t_df: {}
}
};
data.fields = index.fields;
data.key = index.info.key;
data.unique_key_field = schema.uniqueKeyField;
data.similarity = schema.similarity;
data.dynamic_fields = schema.dynamicFields;
data.types = schema.types;
for (var field in schema.fields) {
data.fields[field] =
$.extend({}, data.fields[field], schema.fields[field]);
}
for (var field in data.fields) {
var copy_dests = data.fields[field].copyDests;
for (var i in copy_dests) {
var copy_dest = copy_dests[i];
if (!data.fields[copy_dest]) {
data.fields[copy_dest] = {
partial: true,
copySources: []
};
}
if (data.fields[copy_dest].partial) {
data.fields[copy_dest].copySources.push(field);
}
}
var copy_sources = data.fields[field].copySources;
for (var i in copy_sources) {
var copy_source = copy_sources[i];
if (!data.fields[copy_source]) {
data.fields[copy_source] = {
partial: true,
copyDests: []
};
}
if (data.fields[copy_source].partial) {
data.fields[copy_source].copyDests.push(field);
}
}
data.relations.f_t[field] = data.fields[field].type;
if (!data.relations.t_f[data.fields[field].type]) {
data.relations.t_f[data.fields[field].type] = [];
}
data.relations.t_f[data.fields[field].type].push(field);
if (data.fields[field].dynamicBase) {
data.relations.f_df[field] = data.fields[field].dynamicBase;
if (!data.relations.df_f[data.fields[field].dynamicBase]) {
data.relations.df_f[data.fields[field].dynamicBase] = [];
}
data.relations.df_f[data.fields[field].dynamicBase].push(field);
}
}
for (var dynamic_field in data.dynamic_fields) {
data.relations.df_t[dynamic_field] = data.dynamic_fields[dynamic_field].type;
if (!data.relations.t_df[data.dynamic_fields[dynamic_field].type]) {
data.relations.t_df[data.dynamic_fields[dynamic_field].type] = [];
}
data.relations.t_df[data.dynamic_fields[dynamic_field].type].push(dynamic_field);
}
return data;
};
var getFieldProperties = function(data, core, is, name) {
var display = {};
display.partialState = is.field && !!data.fields[name].partial;
display.columns = [];
display.rows = [];
var allFlags = "";
var addRow = function(name, flags) {
if (flags[0]!='(') {
display.rows.push({name:name, flags:flags});
for (var i in flags) {
if (flags[i]!="-" && allFlags.indexOf(flags[i])<0) {
allFlags+=flags[i];
}
}
} else {
display.rows.push({name:name, comment:flags});
}
}
// Identify the rows for our field property table
if (is.field && data.fields[name]) {
if (data.fields[name].flags) {
addRow('Properties', data.fields[name].flags);
}
if (data.fields[name].schema) {
addRow('Schema', data.fields[name].schema);
}
if (data.fields[name].index) {
addRow('Index', data.fields[name].index);
}
display.docs = data.fields[name].docs;
display.docsUrl = "#/" + core + "/query?q=" + name + ":[* TO *]";
display.distinct = data.fields[name].distinct;
display.positionIncrementGap = data.fields[name].positionIncrementGap;
if (data.types[data.fields[name].type]) {
display.similarity = data.types[data.fields[name].type].similarity;
} else {
display.similarity = null;
}
} else if (is.dynamicField && data.dynamic_fields[name] && data.dynamic_fields[name].flags) {
addRow('Properties', data.dynamic_fields[name].flags);
display.similarity = data.types[data.dynamic_fields[name].type].similarity;
} else if (is.type && data.types[name]) {
display.similarity = data.types[name].similarity;
}
if (display.similarity && display.similarity.className) {
display.similarity.className = shortenPackages(display.similarity.className);
}
// identify columns in field property table:
for (var key in data.key) {
if (allFlags.indexOf(key)>=0) {
display.columns.push({key: key, name: data.key[key]});
}
}
// identify rows and cell values in field property table:
for (var i in display.rows) {
var row = display.rows[i];
row.cells = [];
if (!row.flags) {
continue; // Match the special case in the LukeRequestHandler
}
for (var j in display.columns) {
var flag = display.columns[j].key;
row.cells.push({key: flag, value: row.flags.indexOf(flag)>=0});
}
}
return display;
};
var getAnalysisInfo = function(data, is, name) {
var analysis = {};
if (is.field) {
var type = data.relations.f_t[name];
analysis.query = "analysis.fieldname=" + name;
}
else if (is.dynamicField) {
var type = data.relations.df_t[name];
analysis.query = "analysis.dynamicfield=" + name;
}
else if (is.type) {
var type = name;
analysis.query = "analysis.fieldtype=" + name;
}
var processComponentType = function (label, key, componentTypeData) {
if (componentTypeData) {
var components = [];
for (var componentName in componentTypeData) {
var componentData = componentTypeData[componentName];
var component = {className: componentData.className, args:[]};
if (componentData.args) {
for (var argName in componentData.args) {
var argValue = componentData.args[argName];
if (argValue == "1" || argValue == "true") {
component.args.push({name: argName, booleanValue:true});
} else if (argValue == "0" || argValue == "false") {
component.args.push({name: argName, booleanValue:false});
} else {
component.args.push({name: argName, value:argValue});
}
}
}
components.push(component);
}
return {label: label, key: key, components: components};
} else {
return {label: label, key: key};
}
}
var buildAnalyzer = function (analyzerData) {
var analyzer = {};
analyzer.className = analyzerData.className;
analyzer.componentTypes = [];
if (analyzerData.tokenizer) {
analyzer.componentTypes.push(processComponentType("Char Filters", "charFilters", analyzerData.charFilters));
analyzer.componentTypes.push(processComponentType("Tokenizer", "tokenizer", {tokenizer: analyzerData.tokenizer}));
analyzer.componentTypes.push(processComponentType("Token Filters", "tokenFilters", analyzerData.filters));
}
return analyzer;
}
analysis.data = data.types[type];
if (analysis.data) {
analysis.analyzers = [
{key: "index", name: "Index", detail: buildAnalyzer(analysis.data.indexAnalyzer)},
{key: "query", name: "Query", detail: buildAnalyzer(analysis.data.queryAnalyzer)}
];
}
return analysis;
}
var getTermInfo = function(data) {
var termInfo = {};
if (data && data.topTerms) {
termInfo.topTerms = [];
var currentGroup = {count: 0}
for (var i = 0; i < data.topTerms.length; i += 2) {
var count = data.topTerms[i + 1];
if (currentGroup.count != count) {
currentGroup = {count: count, terms: []};
termInfo.topTerms.push(currentGroup);
}
currentGroup.terms.push(data.topTerms[i]);
}
termInfo.termCount = data.topTerms.length / 2;
termInfo.maxTerms = data.distinct;
}
if(data && data.histogram) {
termInfo.histogram = [];
termInfo.histogramMax = 0;
for (var i = 0; i < data.histogram.length; i += 2) {
termInfo.histogram.push({key: data.histogram[i], value: data.histogram[i + 1]});
termInfo.histogramMax = Math.max(termInfo.histogramMax, data.histogram[i + 1]);
}
}
return termInfo;
};
var getFieldTypeManipulationOptions = function() {
return [
{
value: "Add FieldType Template",
label: "Add FieldType",
template: "{\n" +
" \"add-field-type\": {\n" +
" \"name\": \"myNewTxtField\",\n" +
" \"class\": \"solr.TextField\",\n" +
" \"positionIncrementGap\": \"100\",\n" +
" \"analyzer\": {\n" +
" \"charFilters\": [\n" +
" {\n" +
" \"class\": \"solr.PatternReplaceCharFilterFactory\",\n" +
" \"replacement\": \"$1$1\",\n" +
" \"pattern\": \"([a-zA-Z])\\\\\\\\1+\"\n" +
" }\n" +
" ],\n" +
" \"tokenizer\": {\n" +
" \"class\": \"solr.WhitespaceTokenizerFactory\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}"
},
{
value: "Delete FieldType Template",
label: "Delete FieldType",
template: "{\n" +
" \"delete-field-type\": {\n" +
" \"name\": \"myNewTxtField\"\n" +
" }\n" +
"}"
},
{
value: "Replace FieldType Template",
label: "Replace FieldType",
template: "{\n" +
" \"replace-field-type\": {\n" +
" \"name\": \"myNewTxtField\",\n" +
" \"class\": \"solr.TextField\",\n" +
" \"positionIncrementGap\": \"100\",\n" +
" \"analyzer\": {\n" +
" \"tokenizer\": {\n" +
" \"class\": \"solr.StandardTokenizerFactory\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}"
}
];
};
var sortedObjectArray = function(list) {
var objarr = [];
for (var i in list) {
objarr.push({"name": list[i]});
}
return objarr;
};
var shortenPackages = function(className) {
return className.replace("org.apache.solr", "o.a.s").replace("org.apache.lucene", "o.a.l");
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var MB_FACTOR = 1024*1024;
solrAdminApp.controller('SegmentsController', function($scope, $routeParams, $interval, Segments, Constants) {
$scope.resetMenu("segments", Constants.IS_CORE_PAGE);
$scope.refresh = function() {
Segments.get({core: $routeParams.core}, function(data) {
var segments = data.segments;
var segmentSizeInBytesMax = getLargestSegmentSize(segments);
$scope.segmentMB = Math.floor(segmentSizeInBytesMax / MB_FACTOR);
$scope.xaxis = calculateXAxis(segmentSizeInBytesMax);
$scope.documentCount = 0;
$scope.deletionCount = 0;
$scope.segments = [];
for (var name in segments) {
var segment = segments[name];
var segmentSizeInBytesLog = Math.log(segment.sizeInBytes);
var segmentSizeInBytesMaxLog = Math.log(segmentSizeInBytesMax);
segment.totalSize = Math.floor((segmentSizeInBytesLog / segmentSizeInBytesMaxLog ) * 100);
segment.deletedDocSize = Math.floor((segment.delCount / segment.size) * segment.totalSize);
if (segment.delDocSize <= 0.001) delete segment.deletedDocSize;
segment.aliveDocSize = segment.totalSize - segment.deletedDocSize;
$scope.segments.push(segment);
$scope.documentCount += segment.size;
$scope.deletionCount += segment.delCount;
}
$scope.deletionsPercentage = calculateDeletionsPercentage($scope.documentCount, $scope.deletionCount);
});
};
$scope.toggleAutoRefresh = function() {
$scope.autorefresh = !$scope.autorefresh;
if ($scope.autorefresh) {
$scope.interval = $interval($scope.refresh, 1000);
var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
$interval.cancel($scope.interval);
onRouteChangeOff();
});
} else if ($scope.interval) {
$interval.cancel($scope.interval);
}
};
$scope.refresh();
});
var calculateXAxis = function(segmentInBytesMax) {
var steps = [];
var log = Math.log(segmentInBytesMax);
for (var j=0, step=log/4; j<3; j++, step+=log/4) {
steps.push({pos:j, value:Math.floor((Math.pow(Math.E, step))/MB_FACTOR)})
}
return steps;
};
var getLargestSegmentSize = function(segments) {
var max = 0;
for (var name in segments) {
max = Math.max(max, segments[name].sizeInBytes);
}
return max;
};
var calculateDeletionsPercentage = function(docCount, delCount) {
if (docCount == 0) {
return 0;
} else {
var percent = delCount / docCount * 100;
return Math.round(percent * 100) / 100;
}
};

View File

@@ -0,0 +1,88 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('SQLQueryController',
function($scope, $routeParams, $location, Query, Constants) {
$scope.resetMenu("sqlquery", Constants.IS_COLLECTION_PAGE);
$scope.qt = "sql";
$scope.doExplanation = false
$scope.gridOptions = {
enableSorting: false,
enableRowHashing:false,
enableColumnMenus:false,
columnDefs:[],
data:[],
onRegisterApi: function(gridApi) {
$scope.gridApi = gridApi;
}
};
$scope.hostPortContext = $location.absUrl().substr(0,$location.absUrl().indexOf("#")); // For display only
$scope.doQuery = function() {
var params = {};
params.core = $routeParams.core;
params.handler = $scope.qt;
var stmt = $scope.stmt
if(!stmt.toLowerCase().replace(/(\r\n|\n|\r)/gm," ").includes(' limit ')) {
params.stmt = [stmt + " limit 10"]
} else {
params.stmt = [$scope.stmt]
}
$scope.lang = "json";
$scope.response = null;
$scope.url = "";
$scope.gridOptions.data =[]
$scope.gridOptions.columnDefs = []
var url = Query.url(params);
Query.query(params, function(data) {
var jsonData = JSON.parse(data.toJSON().data);
$scope.lang = "json";
$scope.url = url;
$scope.sqlError = null;
$scope.sqlData = [];
if(jsonData != undefined){
var docs = jsonData['result-set'].docs
//get all docs
for (var i = 0; i < docs.length; i++) {
var doc = docs[i]
//get all the properties
if(doc.hasOwnProperty("EOF")){
if(doc.hasOwnProperty("EXCEPTION")){
$scope.sqlError = doc.EXCEPTION
}
} else {
$scope.gridOptions.data.push(doc);
}
}
}
//Build the columnFields from data
var fields = $scope.gridOptions.data[1];
for (var property in fields) {
if (fields.hasOwnProperty(property)) {
$scope.gridOptions.columnDefs.push({"name":property, "type":{}})
}
}
$scope.gridApi.core.notifyDataChange
});
};
}
);

View File

@@ -0,0 +1,239 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('StreamController',
function($scope, $routeParams, $location, Query, Constants) {
$scope.resetMenu("stream", Constants.IS_COLLECTION_PAGE);
$scope.stream = {
wt: 'json',
expr: $scope.expr,
indent: 'on'
};
$scope.qt = "stream";
$scope.doExplanation = false
$scope.doStream = function() {
var params = {};
params.core = $routeParams.core;
params.handler = $scope.qt;
params.expr = [$scope.expr]
if($scope.doExplanation){
params.explain = [$scope.doExplanation]
}
$scope.lang = "json";
$scope.response = null;
$scope.url = "";
var url = Query.url(params);
Query.query(params, function(data) {
var jsonData = JSON.parse(data.toJSON().data);
if (undefined != jsonData["explanation"]) {
$scope.showExplanation = true;
streamGraphSubController($scope, jsonData["explanation"])
delete jsonData["explanation"]
} else {
$scope.showExplanation = false;
}
data.data = JSON.stringify(jsonData,null,2);
$scope.lang = "json";
$scope.response = data;
$scope.url = url;
$scope.hostPortContext = $location.absUrl().substr(0,$location.absUrl().indexOf("#")); // For display only
});
};
if ($location.search().expr) {
$scope.expr = $location.search()["expr"];
$scope.doStream();
}
}
);
var streamGraphSubController = function($scope, explanation) {
$scope.showGraph = true;
$scope.pos = 0;
$scope.rows = 8;
$scope.resetGraph = function() {
$scope.pos = 0;
$scope.initGraph();
}
$scope.initGraph = function(explanation) {
data = explanation
var leafCount = 0;
var maxDepth = 0;
var rootNode = {};
leafCount = 0;
let recurse = function(dataNode, depth) {
if (depth > maxDepth) {
maxDepth = depth;
}
let graphNode = {
name: dataNode.expressionNodeId,
implementingClass: 'unknown',
data: {}
};
["expressionNodeId", "expressionType", "functionName", "implementingClass", "expression", "note", "helpers"].forEach(function(key) {
graphNode.data[key] = dataNode[key];
});
if (dataNode.children && dataNode.children.length > 0) {
graphNode.children = [];
dataNode.children.forEach(function(n) {
graphNode.children.push(recurse(n, depth + 1));
});
} else {
++leafCount;
}
return graphNode;
}
$scope.showPaging = false;
$scope.isRadial = false;
$scope.explanationData = recurse(data, 1);
$scope.depth = maxDepth + 1;
$scope.leafCount = leafCount;
};
$scope.initGraph(explanation);
};
solrAdminApp.directive('explanationGraph', function(Constants) {
return {
restrict: 'EA',
scope: {
data: "=",
leafCount: "=",
depth: "="
},
link: function(scope, element, attrs) {
var helper_path_class = function(p) {
var classes = ['link'];
return classes.join(' ');
};
var helper_node_class = function(d) {
var classes = ['node'];
if (d.data && d.data.expressionType) {
classes.push(d.data.expressionType);
}
return classes.join(' ');
};
var helper_node_text = function(d) {
if (d.data && d.data.functionName) {
return d.data.functionName;
}
return d.name
};
var helper_tooltip = function(d) {
return [
"Function: " + d.data.functionName,
"Type: " + d.data.expressionType,
"Class: " + d.data.implementingClass.replace("org.apache.solr.client.solrj.io", "o.a.s.c.s.i"),
"=============",
d.data.expression
].join("\n");
}
scope.$watch("data", function(newValue, oldValue) {
if (newValue) {
flatGraph(element, scope.data, scope.depth, scope.leafCount);
}
});
var flatGraph = function(element, graphData, depth, leafCount) {
var w = 100 + (depth * 100),
h = leafCount * 40;
var tree = d3.layout.tree().size([h, w]);
var diagonal = d3.svg.diagonal().projection(function(d) {
return [d.y * .7, d.x];
});
d3.select('#canvas', element).html('');
var vis = d3.select('#canvas', element).append('svg')
.attr('width', w)
.attr('height', h)
.append('g')
.attr('transform', 'translate(25, 0)');
var nodes = tree.nodes(graphData);
var link = vis.selectAll('path.link')
.data(tree.links(nodes))
.enter().append('path')
.attr('class', helper_path_class)
.attr('d', diagonal);
var node = vis.selectAll('g.node')
.data(nodes)
.enter().append('g')
.attr('class', helper_node_class)
.attr('transform', function(d) {
return 'translate(' + d.y * .7 + ',' + d.x + ')';
})
node.append('circle')
.attr('r', 4.5);
node.append('title')
.text(helper_tooltip);
node.append('text')
.attr('dx', function(d) {
return 8;
})
.attr('dy', function(d) {
return 5;
})
.attr('text-anchor', function(d) {
return 'start';
})
.text(helper_node_text)
};
}
};
})

View File

@@ -0,0 +1,50 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
solrAdminApp.controller('ThreadsController',
function($scope, Threads, Constants){
$scope.resetMenu("threads", Constants.IS_ROOT_PAGE);
$scope.refresh = function() {
Threads.get(function(data) {
var threadDump = data.system.threadDump;
var threads = [];
for (var i=1; i<threadDump.length; i+=2) {
var thread = threadDump[i];
if (!!thread.stackTrace) {
var stackTrace = [];
for (var j=0; j<thread.stackTrace.length; j++) {
var trace = thread.stackTrace[j].replace("(", "\u200B("); // allow wrapping to happen, \u200B is a zero-width space
stackTrace.push({id:thread.id + ":" + j, trace: trace});
}
thread.stackTrace = stackTrace;
}
threads.push(thread);
}
$scope.threads = threads;
});
};
$scope.toggleStacktrace = function(thread) {
thread.showStackTrace = !thread.showStackTrace;
};
$scope.toggleStacktraces = function() {
$scope.showAllStacktraces = !$scope.showAllStacktraces;
for (var i=0; i<$scope.threads.length; i++) {
$scope.threads[i].showStackTrace = $scope.showAllStacktraces;
}
};
$scope.refresh();
});

View File

@@ -0,0 +1,37 @@
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* This controller is called whenever no other routes match.
* It is a place to intercept to look for special flows such as authentication callbacks (that do not support fragment in URL).
* Normal action is to redirect to dashboard "/" if no login is in progress
*/
solrAdminApp.controller('UnknownController',
['$scope', '$window', '$routeParams', '$location', 'Constants', 'AuthenticationService',
function($scope, $window, $routeParams, $location, Constants, AuthenticationService) {
var fragment = $window.location.hash.startsWith("#/") ? $window.location.hash.substring(2) : $window.location.hash;
// Check if the URL is actually a callback from Identiy provider
if (AuthenticationService.isJwtCallback(fragment)) {
console.log("Detected an authentication callback, redirecting to /#/login/callback");
$location.path("/login/callback").hash(fragment);
} else {
console.log("Redirecting from unknown path " + fragment + " to Dashboard");
$location.path("/").hash("");
}
}
]
);