diff --git a/custom-recipes/pi-system-af-tree/recipe.py b/custom-recipes/pi-system-af-tree/recipe.py
index ed4c7fd..a2d954f 100644
--- a/custom-recipes/pi-system-af-tree/recipe.py
+++ b/custom-recipes/pi-system-af-tree/recipe.py
@@ -42,6 +42,7 @@ def next_tree_item(tree_data):
logger.info("Initialization with config config={}".format(logger.filter_secrets(config)))
auth_type, username, password, server_url, is_ssl_check_disabled = get_credentials(config)
+is_ssl_check_disabled = config.get("is_ssl_check_disabled", False) # Because no advanced parameter switch
network_timer = PerformanceTimer()
processing_timer = PerformanceTimer()
diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js
index 09c97d8..df033fa 100644
--- a/js/pi-system_treecontroller.js
+++ b/js/pi-system_treecontroller.js
@@ -1,113 +1,162 @@
var app = angular.module('piSystemTreeApp.module', []);
-app.service('TreeDataService', function() {
+app.service('TreeDataService', function () {
// This will store the shared tree data
this.treeData = [];
// Optional: helper methods
- this.setTreeData = function(data) {
+ this.setTreeData = function (data) {
this.treeData = data;
};
- this.getTreeData = function() {
+ this.getTreeData = function () {
return this.treeData;
};
});
app.controller('AfExplorerFormCtrl', [
- '$scope',
- '$stateParams',
- 'CodeMirrorSettingService',
- 'TreeDataService',
- function($scope, $stateParams, CodeMirrorSettingService, TreeDataService) {
-
+ '$scope',
+ '$stateParams',
+ 'CodeMirrorSettingService',
+ 'TreeDataService',
+ function ($scope, $stateParams, CodeMirrorSettingService, TreeDataService) {
+
$scope.paramDesc = {
'parameterSetId': 'basic-auth',
'mandatory': true
};
-
+
$scope.treeData = TreeDataService.getTreeData();
$scope.config.attributeList = $scope.config.attributeList || [];
$scope.config.selectedAttributes = $scope.config.selectedAttributes || [];
-
+
$scope.editorOptions = CodeMirrorSettingService.get("text/plain");
- $scope.init = function() {
- DataikuAPI.plugins.listAccessiblePresets('pi-system', $stateParams.projectKey, 'basic-auth').success(function (data) {
- $scope.inlineParams = data.inlineParams;
- $scope.inlinePluginParams = data.inlinePluginParams;
- $scope.accessiblePresets = [];
- if (data.definableInline) {
- $scope.accessiblePresets.push({
- name:"INLINE",
- label:"Manually defined", usable:true,
- description: "Define values for these parameters"
- });
- }
- data.presets.forEach(function(p) {
- $scope.accessiblePresets.push({name:"PRESET " + p.name, label:p.name, usable:p.usable, description:p.description});
- });
- $scope.accessibleParameterSetDescriptions = $scope.accessiblePresets.map(function(p) {
- return p.description || 'No description';
- });
- }).error(setErrorInScope.bind($scope.errorScope));
+ $scope.init = function () {
+ DataikuAPI.plugins.listAccessiblePresets('pi-system', $stateParams.projectKey, 'basic-auth').success(function (data) {
+ $scope.inlineParams = data.inlineParams;
+ $scope.inlinePluginParams = data.inlinePluginParams;
+ $scope.accessiblePresets = [];
+ if (data.definableInline) {
+ $scope.accessiblePresets.push({
+ name: "INLINE",
+ label: "Manually defined", usable: true,
+ description: "Define values for these parameters"
+ });
+ }
+ data.presets.forEach(function (p) {
+ $scope.accessiblePresets.push({ name: "PRESET " + p.name, label: p.name, usable: p.usable, description: p.description });
+ });
+ $scope.accessibleParameterSetDescriptions = $scope.accessiblePresets.map(function (p) {
+ return p.description || 'No description';
+ });
+ }).error(setErrorInScope.bind($scope.errorScope));
+ if ($scope.authConfigured() === true) {
+ $scope.authSectionVisible = false;
+ $scope.showTreeData = true;
+ }
+ $scope.config.template = $scope.config.template || "-- Any --";
};
-
- $scope.getServers = function(){
- $scope.callPythonDo({parameterName: "server_name"}).then(function(data){
+
+ $scope.getServers = function () {
+ $scope.callPythonDo({ parameterName: "server_name" }).then(function (data) {
$scope.server_name = data.choices;
});
};
- $scope.getDatabases = function() {
- $scope.callPythonDo({parameterName: "database_name"}).then(function(data){
+ $scope.getDatabases = function () {
+ $scope.callPythonDo({ parameterName: "database_name" }).then(function (data) {
$scope.database_name = data.choices;
});
};
-
- $scope.initializeTree = function(){
- console.log("initialization: ");
- console.log($scope.config.treeData);
- if (!$scope.config.treeData || $scope.config.treeData.length === 0){
- $scope.callPythonDo({method: "get_children_from_db", parent: $scope.config.database_name}).then(function(data){
+
+ $scope.authSectionVisible = $scope.authSectionVisible || true;
+
+ $scope.toggleAuthSection = function () {
+ $scope.authSectionVisible = !$scope.authSectionVisible;
+ };
+
+ $scope.authConfigured = function () {
+ console.log('authConfigured check');
+ return $scope.hasPreset() && $scope.config.database_name && $scope.config.database_name.length > 0 && $scope.config.server_name && $scope.config.server_name.length > 0;
+ }
+ $scope.explore = function () {
+ console.log("coucou");
+ if ($scope.authConfigured()) {
+ console.log("here");
+ $scope.showTreeData = true;
+ }
+ };
+
+ $scope.hasPreset = function () {
+ return $scope.config.credentials && $scope.config.credentials.mode && $scope.config.credentials.mode !== 'NONE' && $scope.config.credentials.name
+ }
+
+ $scope.initializeTree = function () {
+ console.log("initialization: ");
+ console.log($scope.config.treeData);
+ if (!$scope.config.treeData || $scope.config.treeData.length === 0) {
+ $scope.callPythonDo({ method: "get_children_from_db", parent: $scope.config.database_name }).then(function (data) {
console.log("ALX:data2=" + JSON.stringify(data));
TreeDataService.setTreeData(data.choices);
- $scope.config.treeData = TreeDataService.getTreeData();
- });
- }
+ $scope.config.treeData = TreeDataService.getTreeData();
+ });
+ }
};
-
- $scope.getChildrenFromDB = function(item){
- console.log("ALX:gcfd:" + JSON.stringify(item));
- return $scope.callPythonDo({ method: "get_children_from_db", parent: item })
- .then(function (data) {
- console.log("ALX:data1=" + JSON.stringify(data));
- item.children = data.choices;
- item.children.forEach(child => {
- child.expanded = false;
+
+ $scope.getChildrenFromDB = function (item) {
+ console.log("ALX:gcfd:" + JSON.stringify(item));
+ return $scope.callPythonDo({ method: "get_children_from_db", parent: item })
+ .then(function (data) {
+ console.log("ALX:data1=" + JSON.stringify(data));
+ item.children = data.choices;
+ item.children.forEach(child => {
+ child.expanded = false;
+ });
+ console.log(item);
+ return item;
});
- console.log(item);
- return item;
+ }
+
+ $scope.getTemplatesFromDB = function () {
+ $scope.callPythonDo({ method: "get_templates_from_db" }).then(function (data) {
+ $scope.config.templates = data.choices;
});
- }
-
-
- // Toggle récursif des checkboxes
- $scope.toggleChildren = function(node) {
- console.log("ALX:tc:" + JSON.stringify(node));
- node.expanded = !node.expanded;
- $scope.getChildrenFromDB(node);
- if (node.children && node.children.length) {
- node.children.forEach(function(child) {
- child.expanded = !child.expanded;
- $scope.getChildrenFromDB(child);
+ }
+
+ $scope.activeTab = 'element'; // tab par défaut
+
+ $scope.setTab = function(tab) {
+ $scope.activeTab = tab;
+ };
+
+ $scope.getCategoriesFromDB = function () {
+ $scope.config.attribute_categories = [];
+ $scope.config.element_categories = [];
+ $scope.callPythonDo({ method: "get_attribute_categories_from_db" }).then(function (data) {
+ $scope.config.attribute_categories = data.choices;
+ });
+ $scope.callPythonDo({ method: "get_element_categories_from_db" }).then(function (data) {
+ $scope.config.element_categories = data.choices;
});
}
-
- };
- $scope.doSearch = function(element_name, attribute_name){
- $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name, root_tree: $scope.config.treeData}).then(
- function(data){
+ // Toggle récursif des checkboxes
+ $scope.toggleChildren = function (node) {
+ console.log("ALX:tc:" + JSON.stringify(node));
+ node.expanded = !node.expanded;
+ $scope.getChildrenFromDB(node);
+ if (node.children && node.children.length) {
+ node.children.forEach(function (child) {
+ child.expanded = !child.expanded;
+ $scope.getChildrenFromDB(child);
+ });
+ }
+
+ };
+
+ $scope.doSearch = function (element_name, attribute_name) {
+ $scope.callPythonDo({ method: "do_search", element_name: element_name, attribute_name: attribute_name, root_tree: $scope.config.treeData }).then(
+ function (data) {
TreeDataService.setTreeData(data.choices);
$scope.config.treeData = TreeDataService.getTreeData();
$scope.config.attributeList = data.attributes;
@@ -116,101 +165,111 @@ app.controller('AfExplorerFormCtrl', [
);
};
- $scope.updateAttributeToOutput = function (attribute) {
- if (attribute.checked && $scope.config.selectedAttributes.includes(attribute)) {
- $scope.config.selectedAttributes = $scope.config.selectedAttributes.filter(attr => attr.path !== attribute.path);
- }
- else {
- console.log("Adding attribute to output:", attribute);
-
- if (!$scope.config || !$scope.config.attributeList || $scope.config.selectedAttributes.includes(attribute)) {
- return;
+ $scope.updateAttributeToOutput = function (attribute) {
+ if (attribute.checked && $scope.config.selectedAttributes.includes(attribute)) {
+ $scope.config.selectedAttributes = $scope.config.selectedAttributes.filter(attr => attr.path !== attribute.path);
}
- const attrInConfig = $scope.config.attributeList.find(attr => attr.path === attribute.path);
+ else {
+ console.log("Adding attribute to output:", attribute);
- if (attrInConfig) {
- $scope.config.selectedAttributes.push(attribute);
- attrInConfig.checked = true;
- } else {
- console.warn("Attribute not found in config:", attribute.path);
+ if (!$scope.config || !$scope.config.attributeList || $scope.config.selectedAttributes.includes(attribute)) {
+ return;
+ }
+ const attrInConfig = $scope.config.attributeList.find(attr => attr.path === attribute.path);
+
+ if (attrInConfig) {
+ $scope.config.selectedAttributes.push(attribute);
+ attrInConfig.checked = true;
+ } else {
+ console.warn("Attribute not found in config:", attribute.path);
+ }
}
- }
- };
+ };
-$scope.displayAttributes = function(node) {
+ $scope.displayAttributes = function (node) {
- if (!node.children || node.children.length === 0) {
+ if (!node.children || node.children.length === 0) {
$scope.getChildrenFromDB(node).then(newNode => {
- processNode(newNode);
+ processNode(newNode);
});
- } else {
+ } else {
processNode(node);
- };
- }
+ };
+ }
-function processNode(node) {
- $scope.config.attributeList = [];
- $scope.config.selectedAttributes = [];
- node.children.forEach(child => {
+ function processNode(node) {
+ if (node.title !== $scope.config.element_name) {
+ $scope.config.element_name = "";
+ }
+ $scope.config.attributeList = [];
+ $scope.config.selectedAttributes = [];
+ node.children.forEach(child => {
if (child.type === "attribute") {
- $scope.config.attributeList.push({
- "name": child.title,
- "path": child.path
- });
+ $scope.config.attributeList.push({
+ "title": child.title,
+ "path": child.path,
+ "description": child.description
+ });
}
- });
-}
+ });
+ }
-}]);
+ }]);
-app.directive('treeNode', function() {
+app.directive('treeNode', function () {
return {
restrict: 'E',
- scope: { node: '=' },
+ scope: {
+ node: '=', // mutable object (tree structure changes)
+ getChildrenFromDB: '<', // function reference
+ displayAttributes: '<', // function reference
+ config: '<' // read/write nested properties only
+ },
+
template: `
-
-
▼
▶
-
-
-
-
-
- {{ node.title }}
+
+ {{ node.title }}
+
-
-
- -
-
+
`,
- link: function(scope) {
- scope.toggleChildren = scope.$parent.toggleChildren;
- scope.getChildrenFromDB = scope.$parent.getChildrenFromDB;
- scope.doSearch = scope.$parent.doSearch;
- scope.config = scope.$parent.config;
- scope.attributeList = scope.config.attributeList || [];
- scope.displayAttributes = scope.$parent.displayAttributes;
- scope.toggleExpand = function(node) {
+
+ link: function (scope) {
+
+ // Toggle expansion + lazy loading
+ scope.toggleExpand = function (node) {
node.expanded = !node.expanded;
if (node.expanded && (!node.children || !node.children.length)) {
@@ -218,20 +277,20 @@ app.directive('treeNode', function() {
}
};
- scope.hasAttributes = function(node) {
- if (!Array.isArray(scope.$parent.config.attributeList) || scope.$parent.config.attributeList.length === 0) {
+
+ scope.hasAttributes = function (node) {
+ if (!Array.isArray(scope.config.attributeList) ||
+ !scope.config.attributeList.length) {
return false;
}
- return scope.$parent.config.attributeList.some(child => {
- const expected = node.title + "|" + child.title;
- return child.path.endsWith(expected);
+ return scope.config.attributeList.some(attr => {
+ const expected = node.title + "|" + attr.title;
+ return attr.path.endsWith(expected);
});
};
- scope.isElement = function(child) {
- return child.type === 'element';
- }
}
};
});
+
diff --git a/python-lib/osisoft_client.py b/python-lib/osisoft_client.py
index 71c39c1..7677f85 100644
--- a/python-lib/osisoft_client.py
+++ b/python-lib/osisoft_client.py
@@ -748,6 +748,124 @@ def search_attributes(self, database_webid, **kwargs):
else:
json_response = None
+ def search_elements(self, database_webid, name=None, description=None, category=None, template=None, full_search=True):
+ headers = self.get_requests_headers()
+ tempo_maxcount = OSIsoftConstants.DEFAULT_MAXCOUNT
+ params = {
+ "maxCount": tempo_maxcount,
+ "associations": "Paths",
+ }
+ url = self.endpoint.get_base_url() + "/assetdatabases/{}/elements".format(database_webid)
+ if name:
+ params["nameFilter"] = name
+ if description:
+ params["descriptionFilter"] = description
+ if category:
+ params["categoryName"] = category
+ if template:
+ params["templateName"] = template
+ if full_search:
+ params["searchFullHierarchy"] = True
+ json_response = self.get(url=url, headers=headers, params=params)
+ if OSIsoftConstants.DKU_ERROR_KEY in json_response:
+ yield json_response
+ start_index = 0
+ while json_response:
+ items = json_response.get(OSIsoftConstants.API_ITEM_KEY, [])
+ for item in items:
+ yield item
+ if len(items) < tempo_maxcount:
+ logger.info("No more result items")
+ return
+ start_index += tempo_maxcount
+ logger.info("Trying again with startIndex={}".format(start_index))
+ params["startIndex"] = start_index
+ json_response = self.get(url=url, headers=headers, params=params)
+
+ def batched_search(self, database, element_name, attribute_name, element_category, attribute_category, template):
+ attribute_query = {
+ "searchFullHierarchy": "true",
+ "associations": "Paths"
+ }
+ if attribute_name:
+ attribute_query["nameFilter"] = attribute_name
+ if attribute_category:
+ attribute_query["categoryName"] = attribute_category
+ elements_url = "{}/elements".format(database)
+ print("ALX:fetching direct from {}".format(elements_url))
+ request_body = {
+ "elements": {
+ "Method": "GET",
+ "Resource": "{}{}".format(
+ elements_url,
+ build_query_string("", {
+ "templateName": template,
+ "categoryName": element_category,
+ "nameFilter": element_name,
+ "searchFullHierarchy": "true",
+ "associations": "Paths"
+ }
+ )
+ )
+ },
+ "attributes": {
+ "Method": "GET",
+ "RequestTemplate": {
+ "Resource": "{{0}}{}".format(
+ build_query_string("", attribute_query)
+ ) #?searchFullHierarchy=true&selectedFields=Items.WebId;Items.Path"
+ }, # build_attribute_query(attribute_name, attribute_category)
+ "ParentIds": ["elements"],
+ "Parameters": ["$.elements.Content.Items[*].Links.Attributes"]
+ }
+ }
+ # request_body = {
+ # "database": {
+ # "Method": "GET",
+ # "Resource": "https://osisoft/piwebapi/assetdatabases?path=\\\\osisoft-pi-serv\\Well&selectedFields=WebId;Path;Links"
+ # },
+ # "elements": {
+ # "Method": "GET",
+ # "Resource": "{{0}}{}".format(
+ # build_query_string("", {
+ # "templateName": template,
+ # "categoryName": element_category,
+ # "nameFilter": element_name,
+ # "searchFullHierarchy": "true",
+ # "associations": "Paths"
+ # }
+ # )
+ # ),
+ # "ParentIds": ["database"],
+ # "Parameters": ["$.database.Content.Links.Elements"]
+ # },
+ # "attributes": {
+ # "Method": "GET",
+ # "RequestTemplate": {
+ # "Resource": "{{0}}{}".format(
+ # build_query_string("", attribute_query)
+ # ) #?searchFullHierarchy=true&selectedFields=Items.WebId;Items.Path"
+ # }, # build_attribute_query(attribute_name, attribute_category)
+ # "ParentIds": ["elements"],
+ # "Parameters": ["$.elements.Content.Items[*].Links.Attributes"]
+ # }
+ # }
+ url = self.endpoint.get_batch_endpoint()
+ headers = OSIsoftConstants.WRITE_HEADERS
+ response = self.post(url, headers=headers, data=request_body, params={})
+ json_response = response.json()
+ attributes = json_response.get("attributes", {})
+ attributes_content = attributes.get("Content", {})
+ if not isinstance(attributes_content, dict):
+ # the search returned nothing
+ return
+ attributes_content_items = attributes_content.get("Items", [])
+ for attributes_content_item in attributes_content_items:
+ content = attributes_content_item.get("Content", {})
+ sub_items = content.get("Items", [])
+ for sub_item in sub_items:
+ yield sub_item
+
def build_element_query(self, **kwargs):
element_query_keys = {
"element_name": "Name:'{}'",
@@ -857,7 +975,7 @@ def traverse_and_cache(self, path_elements, path_attributes, tree):
for path_attribute in path_attributes:
item = self.extract_item_with_name(json_response, path_attribute)
item_details = get_item_details(item)
- item_details["checked"] = True # That should not be done here
+ # item_details["checked"] = True # That should not be done here
tree.put(full_path_elements[0:counter], item_details)
counter += 1
next_url = self.extract_link_with_key(item, "Attributes")
@@ -866,6 +984,8 @@ def traverse_and_cache(self, path_elements, path_attributes, tree):
json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse_and_cache")
else:
break
+ if not before_last_json:
+ return None
items = before_last_json.get(OSIsoftConstants.API_ITEM_KEY, [])
for item in items:
item_details = get_item_details(item)
@@ -1091,7 +1211,7 @@ def close(self):
def validate_timestamp(timestamp):
- valid_formats=["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"]
+ valid_formats = ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"]
for valid_format in valid_formats:
try:
datetime.strptime(timestamp, valid_format)
@@ -1115,7 +1235,7 @@ def build_query_string(url, params):
if isinstance(value, list):
for element in value:
tokens.append(key+"="+str(element))
- else:
+ elif value is not None:
tokens.append(key+"="+str(value))
if len(tokens) > 0:
return url + "?" + "&".join(tokens)
diff --git a/python-lib/osisoft_plugin_common.py b/python-lib/osisoft_plugin_common.py
index 82cc580..4728423 100644
--- a/python-lib/osisoft_plugin_common.py
+++ b/python-lib/osisoft_plugin_common.py
@@ -154,6 +154,7 @@ def build_requests_params(**kwargs):
"boundary_type": "syncTimeBoundaryType",
"name_filter": "nameFilter",
"category_name": "categoryName",
+ "description": "descriptionFilter",
"template_name": "templateName",
"referenced_element_name_filter": "referencedElementNameFilter",
"referenced_element_template": "referencedElementTemplate",
@@ -638,8 +639,8 @@ def add(self, start_time, end_time, interval):
def get_item_details(item):
KEYS_TO_CHECK = {
- "Name": "title", "TemplateName": "template_name", "CategoryNames": "category_names",
- "HasChildren": "has_children", "Path": "path", "WebId": "id", "checked": "checked"
+ "Name": "title", "TemplateName": "template_name", "CategoryNames": "category_names", "Description": "description",
+ "HasChildren": "has_children", "Path": "path", "WebId": "id", "checked": "checked", "BaseTemplate": "BaseTemplate"
} # should we stick to python naming convention or keep pi's ones throughout ?
details = {}
for key_to_check in KEYS_TO_CHECK:
diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py
index 91b3f6d..7a3d50c 100644
--- a/resource/browse_af_tree.py
+++ b/resource/browse_af_tree.py
@@ -14,7 +14,6 @@ def do(payload, config, plugin_config, inputs):
input_dataset = dataiku.Dataset(input_dataset_name)
input_tree = input_dataset.get_dataframe(infer_with_pandas=False)
- config["is_ssl_check_disabled"] = True
if "config" in config:
config = config.get("config")
if "credentials" not in config:
@@ -23,6 +22,7 @@ def do(payload, config, plugin_config, inputs):
return {"choices": [{"label": "Pick a credential"}]}
auth_type, username, password, server_url, is_ssl_check_disabled, credential_error = get_credentials(config, can_raise=False)
+ is_ssl_check_disabled = config.get("is_ssl_check_disabled", False) # Because no advanced parameter switch
if credential_error:
return build_select_choices(credential_error)
@@ -40,7 +40,6 @@ def do(payload, config, plugin_config, inputs):
return build_select_choices("Fill in the server address")
is_debug_mode = check_debug_mode(config)
- is_ssl_check_disabled = True
client = OSIsoftClient(server_url, auth_type, username, password, is_ssl_check_disabled=is_ssl_check_disabled, is_debug_mode=is_debug_mode)
@@ -51,7 +50,35 @@ def do(payload, config, plugin_config, inputs):
database_name = config.get("database_name")
parent = payload.get("parent", {})
return get_children_from_db(client, parent, database_name=database_name)
+ if method == "get_templates_from_db":
+ database_name = config.get("database_name")
+ parent = payload.get("parent", {})
+ # ret = get_items_from_db(client, parent, "ElementTemplates", database_name=database_name)
+ ret = get_template_hierarchy_from_db(client, parent, database_name=database_name)
+ return ret
+ if method == "get_attribute_categories_from_db":
+ database_name = config.get("database_name")
+ parent = payload.get("parent", {})
+ ret = get_items_from_db(client, parent, "AttributeCategories", database_name=database_name)
+ return ret
+ if method == "get_element_categories_from_db":
+ database_name = config.get("database_name")
+ parent = payload.get("parent", {})
+ ret = get_items_from_db(client, parent, "ElementCategories", database_name=database_name)
+ return ret
if method == "do_search":
+ template_name = config.get("template", None)
+ category_name = config.get("element_category", None)
+ if template_name == "-- Any --":
+ template_name = None
+ if category_name == "-- Any --":
+ category_name = None
+ element_category = config.get("element_category", None)
+ if element_category == "-- Any --":
+ element_category = None
+ attribute_category = config.get("attribute_category", None)
+ if attribute_category == "-- Any --":
+ attribute_category = None
database_name = config.get("database_name")
element_name = config.get("element_name")
attribute_name = config.get("attribute_name")
@@ -61,26 +88,12 @@ def do(payload, config, plugin_config, inputs):
attributes = []
# https://dku-qa-osi.francecentral.cloudapp.azure.com/piwebapi/assetdatabases/F1RD3VEt1yTvt0ip6-a5yeEVsgbMcrwu_Je0qg9btcZIvPswT1NJU09GVC1QSS1TRVJWXFdFTEw
database_webid = database_name.split("/")[-1]
- # element_query_keys = {
- # "element_name": "Name:'{}'",
- # "search_root_path": "Root:'{}'",
- # "element_template": "Template:'{}'",
- # "element_type": "Type:'{}'",
- # "element_category": "CategoryName:'{}'"
- # }
- # attribute_query_keys = {
- # "attribute_name": "Name:'{}'",
- # "attribute_category": "CategoryName:'{}'",
- # "attribute_value_type": "Type:'{}'"
- # }
- for attribute in client.search_attributes(
- database_webid,
- attribute_name=attribute_name,
- element_name=element_name,
- search_associations="Paths"
- ):
- attribute["checked"] = False
- attributes.append(attribute)
+
+ attributes = []
+ for result in client.batched_search(database_name, element_name, attribute_name, element_category, attribute_category, template_name):
+ # result["checked"] = True
+ attributes.append(result)
+
attributes = duplicate_linked_attributes(attributes)
items = []
for attribute in attributes:
@@ -123,6 +136,25 @@ def get_query_catalogs(cnx, config):
return {"choices": [user, password]}
+def get_items_from_db(client, parent_node, link_key, database_name=None):
+ default_choice = {"title": "-- Any --"}
+ if isinstance(parent_node, dict):
+ url = parent_node.get("url", database_name)
+ else:
+ url = parent_node
+ this_node = next(client.get_next_item_from_url(url))
+ links = this_node.get("Links", {})
+ items_url = links.get(link_key)
+ items = []
+ items.append(default_choice)
+ if items_url:
+ for item in client.get_next_item_from_url(items_url):
+ item = get_item_details(item)
+ item["type"] = link_key
+ items.append(item)
+ return {"choices": items}
+
+
def get_children_from_db(client, parent_node, database_name=None):
if isinstance(parent_node, dict):
url = parent_node.get("url", database_name)
@@ -150,19 +182,50 @@ def get_children_from_db(client, parent_node, database_name=None):
if child.get("has_children"):
child["children"] = []
children.append(child)
-
return {"choices": children}
-# method2:
-# we dig, but this time it's index[token name], and we store as we go in the child, with the real data indexed in a list and just the rank pointing to it
-# to build the final tree, we browse the index, get the index data, rebuild the struct from there
-# Tree class ? put(path, data), get(path, data)
+
+def get_template_hierarchy_from_db(client, parent_node, database_name=None):
+ if isinstance(parent_node, dict):
+ url = parent_node.get("url", database_name)
+ else:
+ url = parent_node
+ default_choice = {"title": "-- Any --"}
+ this_node = next(client.get_next_item_from_url(url))
+ links = this_node.get("Links", {})
+ element_templates_url = links.get("ElementTemplates")
+ children = [default_choice]
+ rebuilt_tree = []
+ if element_templates_url:
+ element_templates = client.get_next_item_from_url(element_templates_url)
+ for element_template in element_templates:
+ child = get_item_details(element_template)
+ child["type"] = "template"
+ child["children"] = []
+ children.append(child)
+ rebuilt_tree = nest_children(children)
+ return {"choices": rebuilt_tree}
+
+
+def nest_children(items):
+ name_to_item = {item["title"]: item for item in items}
+ tree = []
+ for item in items:
+ parent_name = item.get("BaseTemplate")
+ if parent_name is None or parent_name not in name_to_item:
+ tree.append(item)
+ else:
+ parent = name_to_item[parent_name]
+ if "children" not in parent:
+ parent["children"] = []
+ parent["children"].append(item)
+ return tree
def rebuild_tree(client, items, root_tree=None):
# builds an active tree containing all the items and their parent up to the root
tree = Tree(root_tree=root_tree)
- tree.print()
+ # tree.print()
while len(items) > 1:
item = items.pop()
if item is None:
diff --git a/resource/pi-system_af-explorer.css b/resource/pi-system_af-explorer.css
index 575492b..c03843a 100644
--- a/resource/pi-system_af-explorer.css
+++ b/resource/pi-system_af-explorer.css
@@ -14,3 +14,73 @@
.pi-system-explorer__tree-view, .pi-system-explorer__center-view {
border: 1px solid #ccc; padding: 10px; border-radius: 5px;
}
+
+.pi-system-explorer__authentication-header {
+ background-color: #f0f0f0;
+ padding: 10px;
+ cursor: pointer;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ font-weight: bold;
+}
+
+.pi-system-explorer__authentication-body {
+ border: 1px solid #ccc; padding: 10px; border-radius: 5px;
+}
+
+.custom-table {
+ border-collapse: collapse;
+ width: 100%;
+ font-family: Arial, sans-serif;
+}
+
+.custom-table th,
+.custom-table td {
+ border: 1px solid #ccc;
+ padding: 8px 12px;
+ text-align: left;
+}
+
+.custom-table th {
+ background-color: #f4f4f4;
+ font-weight: bold;
+}
+
+.custom-table tr:nth-child(even) {
+ background-color: #fafafa;
+}
+
+.custom-table tr:hover {
+ background-color: #f1f7ff;
+}
+
+.tab-container {
+ font-family: Arial, sans-serif;
+}
+
+.tab-header {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 6px 0;
+ display: flex;
+ border-bottom: 2px solid #ddd;
+}
+
+.tab-header li {
+ padding: 8px 16px;
+ cursor: pointer;
+ background: #f5f5f5;
+ border: 1px solid #ddd;
+ border-bottom: none;
+ margin-right: 4px;
+}
+
+.tab-header li.active {
+ background: #fff;
+ font-weight: bold;
+}
+
+.tab-content {
+ border-top: none;
+ padding: 10px;
+}
diff --git a/resource/pi-system_af-explorer.html b/resource/pi-system_af-explorer.html
index d331c88..a5ef7d4 100644
--- a/resource/pi-system_af-explorer.html
+++ b/resource/pi-system_af-explorer.html
@@ -1,93 +1,138 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
-
+
+
-
Elements
-
-
-
+
+
+
+
+
+
+
-
Attributes
-
-
-
+
+
+
+
+
+
+
+
+
+
-