From: elinuxhenrik
Date: Thu, 19 Mar 2020 13:52:24 +0000 (+0100)
Subject: First version of NonRT RIC Controlpanel
X-Git-Tag: BronzeRC0~22
X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=f507d92d55ee77fad16cc024ea95c869e0d5dc32;p=portal%2Fnonrtric-controlpanel.git
First version of NonRT RIC Controlpanel
Change-Id: Ie068bbc18c8c07d8fcb5a6608c07f223cda6f88b
Issue-ID: NONRTRIC-168
Signed-off-by: elinuxhenrik
---
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1fe9c87
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,53 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+/logs
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node
+/node_modules
+
+/.classpath
+/.project
+/.settings
+target
+/.mvn/wrapper/maven-wrapper.jar
+/.tox
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+### visual studio ###
+.vs
+**/.vscode
+
+# OSx cruft
+**/.DS_Store
+
+# documentation
+.tox
+docs/_build/*
diff --git a/LICENSES.txt b/LICENSES.txt
new file mode 100644
index 0000000..4ec6357
--- /dev/null
+++ b/LICENSES.txt
@@ -0,0 +1,30 @@
+LICENSES.txt
+
+Unless otherwise specified, all software contained herein is licensed
+under the Apache License, Version 2.0 (the "Software License");
+you may not use this software except in compliance with the Software
+License. You may obtain a copy of the Software License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Software License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the Software License for the specific language governing permissions
+and limitations under the Software License.
+
+
+
+Unless otherwise specified, all documentation contained herein is licensed
+under the Creative Commons License, Attribution 4.0 Intl. (the
+"Documentation License"); you may not use this documentation except in
+compliance with the Documentation License. You may obtain a copy of the
+Documentation License at
+
+https://creativecommons.org/licenses/by/4.0/
+
+Unless required by applicable law or agreed to in writing, documentation
+distributed under the Documentation License is distributed on an "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Documentation License for the specific language governing
+permissions and limitations under the Documentation License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0317f18
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# O-RAN-SC NonRT RIC Controlpanel Web Application
+
+The O-RAN NonRT RIC Controlpanel provides administrative and operator functions for a NearRT RIC through the A1 API.
+This web app consists of an Angular (version 8) front end
+and a Java (version 11) Spring-Boot (version 2.1) back end.
+
+Please see the documentation in the docs/ folder.
+
+The backend server publishes live API documentation at the
+URL `http://your-host-name-here:8080/swagger-ui.html`
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+Modifications Copyright (C) 2019 Nordix Foundation
+Licensed 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.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1e9828a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,122 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.5.RELEASE
+
+
+ org.o-ran-sc.portal.nonrtric.controlpanel
+ nonrtric-controlpanel-parent
+ NonRT RIC Controlpanel project
+ pom
+ 1.0.0-SNAPSHOT
+
+ 11
+
+ AT&T Intellectual Property
+ O-RAN-SC
+ 2019
+ apache_v2
+ ========================LICENSE_START=================================
+ ========================LICENSE_END===================================
+
+
+ webapp-frontend
+ webapp-backend
+
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+
+ ${lmp.organization.name}
+ ${lmp.inception.year}
+ ${lmp.project.name}
+ ${lmp.license.name}
+ ${lmp.process.start.tag}
+ ${lmp.process.end.tag}
+ false
+
+
+
+ first
+
+ update-file-header
+
+ process-sources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${java.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ 3.6.0.1398
+
+
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+ 1.20
+
+
+
+
+
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..c86cfdf
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,30 @@
+# documentation only
+[tox]
+minversion = 2.0
+envlist =
+ docs,
+ docs-linkcheck,
+skipsdist = true
+
+[testenv:docs]
+basepython = python3
+deps =
+ sphinx
+ sphinx-rtd-theme
+ sphinxcontrib-httpdomain
+ recommonmark
+ lfdocs-conf
+
+commands =
+ sphinx-build -W -b html -n -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/html
+ echo "Generated docs available in {toxinidir}/docs/_build/html"
+whitelist_externals = echo
+
+[testenv:docs-linkcheck]
+basepython = python3
+deps = sphinx
+ sphinx-rtd-theme
+ sphinxcontrib-httpdomain
+ recommonmark
+ lfdocs-conf
+commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees ./docs/ {toxinidir}/docs/_build/linkcheck
diff --git a/webapp-backend/.gitignore b/webapp-backend/.gitignore
new file mode 100644
index 0000000..f4a6f7b
--- /dev/null
+++ b/webapp-backend/.gitignore
@@ -0,0 +1,34 @@
+/.classpath
+/.project
+/.settings
+/target/
+/logs/
+/.mvn/wrapper/maven-wrapper.jar
+bin/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+/application-tlab2.properties
+/application.properties
+/controlpanel-users.json
diff --git a/webapp-backend/README.md b/webapp-backend/README.md
new file mode 100644
index 0000000..9a06c6c
--- /dev/null
+++ b/webapp-backend/README.md
@@ -0,0 +1,26 @@
+# NonRT RIC Controlpanel Web Application Backend
+
+The NonRT RIC Controlpanel back-end provides REST services to the Controlpanel
+front-end Typescript features running in the user's browser. For
+production use, it also serves the Angular application files.
+
+Please see the documentation in the docs/ folder.
+
+The backend server publishes live API documentation at the
+URL `http://your-host-name-here:8080/swagger-ui.html`
+
+## License
+
+Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+Modifications Copyright (C) 2019 Nordix Foundation
+Licensed 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.
diff --git a/webapp-backend/config/.gitignore b/webapp-backend/config/.gitignore
new file mode 100644
index 0000000..edd66f1
--- /dev/null
+++ b/webapp-backend/config/.gitignore
@@ -0,0 +1,2 @@
+/key.properties
+/portal.properties
diff --git a/webapp-backend/config/key.properties.template b/webapp-backend/config/key.properties.template
new file mode 100644
index 0000000..79c63d0
--- /dev/null
+++ b/webapp-backend/config/key.properties.template
@@ -0,0 +1,21 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides a secret key for the NonRT RIC Controlpanel.
+
+cipher.enc.key =
diff --git a/webapp-backend/config/portal.properties.template b/webapp-backend/config/portal.properties.template
new file mode 100644
index 0000000..f0d6eca
--- /dev/null
+++ b/webapp-backend/config/portal.properties.template
@@ -0,0 +1,34 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+# The following properties are the same in every deployment
+
+portal.api.impl.class = org.oransc.portal.nonrtric.controlpanel.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+
+# The following properties are DIFFERENT in every deployment
+
+# URL of portal login screen
+ecomp_redirect_url = http://localhost/portal
+# URL of portal API
+ecomp_rest_url = http://localhost/portal
+# Value assigned by portal instance
+ueb_app_key = abcdef1234567890
diff --git a/webapp-backend/eclipse-formatter.xml b/webapp-backend/eclipse-formatter.xml
new file mode 100644
index 0000000..7339434
--- /dev/null
+++ b/webapp-backend/eclipse-formatter.xml
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-backend/pom.xml b/webapp-backend/pom.xml
new file mode 100644
index 0000000..96ebb1d
--- /dev/null
+++ b/webapp-backend/pom.xml
@@ -0,0 +1,348 @@
+
+
+
+ 4.0.0
+
+ org.o-ran-sc.portal.nonrtric.controlpanel
+ nonrtric-controlpanel-parent
+ 1.0.0-SNAPSHOT
+
+ nonrtric-controlpanel-be
+ NonRT RIC Controlpanel Webapp backend
+
+ 2.9.2
+ 2.7.1
+ 1.7.9
+ 2.8.1
+ 1.18.0
+
+ 0
+
+
+
+ onap-releases
+ ONAP - Release Repository
+ https://nexus.onap.org/content/repositories/releases
+
+
+
+
+
+ org.onap.portal.sdk
+ epsdk-fw
+ 2.6.0
+
+
+ commons-logging
+ commons-logging
+
+
+ log4j
+ log4j
+
+
+ log4j
+ apache-log4j-extras
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ junit
+ junit
+
+
+ commons-fileupload
+ commons-fileupload
+
+
+ commons-beanutils
+ commons-beanutils
+
+
+
+ org.powermock
+ powermock-module-junit4
+
+
+
+ org.powermock
+ powermock-api-mockito
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.apache.axis2
+ axis2-kernel
+ ${apache-axis2.version}
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ ch.qos.logback
+ logback-core
+
+
+ io.springfox
+ springfox-swagger2
+ ${springfox.version}
+
+
+ io.springfox
+ springfox-swagger-ui
+ ${springfox.version}
+
+
+ org.immutables
+ value
+ ${immutable.version}
+ provided
+
+
+ org.immutables
+ gson
+ ${immutable.version}
+
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ net.revelc.code.formatter
+ formatter-maven-plugin
+ ${formatter-maven-plugin.version}
+
+ ${project.basedir}/eclipse-formatter.xml
+
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ ${spotless-maven-plugin.version}
+
+
+
+
+ com,java,javax,org
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+
+
+ src
+
+
+ **/*.json
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ true
+
+
+ ${project.version}-b${build.number}
+
+
+
+
+
+ maven-resources-plugin
+
+
+ copy-resources
+ validate
+
+ copy-resources
+
+
+ ${project.build.directory}/classes/resources/
+
+
+ ${project.parent.basedir}/webapp-frontend/dist/controlpanelApp/
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.4
+
+
+ default-prepare-agent
+
+ prepare-agent
+
+
+
+ default-report
+ prepare-package
+
+ report
+
+
+
+
+
+
+ io.fabric8
+ docker-maven-plugin
+ 0.30.0
+
+ true
+
+ ${env.CONTAINER_PULL_REGISTRY}
+ ${env.CONTAINER_PUSH_REGISTRY}
+
+
+
+
+ o-ran-sc/nonrtric-controlpanel:${project.version}
+
+ openjdk:11-jre-slim
+
+
+ ${project.version}
+
+
+ artifact
+
+
+
+ mkdir /logs
+ chmod -R 777 /logs
+
+
+
+
+ java
+ -Xms128m
+ -Xmx256m
+ -cp
+ maven:maven/${project.artifactId}-${project.version}.${project.packaging}
+ -Dloader.main=org.oransc.portal.nonrtric.controlpanel.ControlpanelApplication
+ -Djava.security.egd=file:/dev/./urandom
+ org.springframework.boot.loader.PropertiesLauncher
+
+
+
+
+
+
+
+
+
+
+ build
+ push
+
+
+
+
+
+
+
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelApplication.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelApplication.java
new file mode 100644
index 0000000..76bb8bc
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelApplication.java
@@ -0,0 +1,60 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.portal.nonrtric.controlpanel;
+
+import java.lang.invoke.MethodHandles;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+// Limit scan to controlpanel classes; exclude generated API classes
+@ComponentScan("org.oransc.portal.nonrtric.controlpanel")
+public class ControlpanelApplication {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ public static void main(String[] args) {
+ SpringApplication.run(ControlpanelApplication.class);
+ // Ensure this appears on the console by using level WARN
+ logger.warn("main: version '{}' successful start",
+ getImplementationVersion(MethodHandles.lookup().lookupClass()));
+ }
+
+ /**
+ * Gets version details for the specified class.
+ *
+ * @param clazz
+ * Class to get the version
+ *
+ * @return the value of the MANIFEST.MF property Implementation-Version as
+ * written by maven when packaged in a jar; 'unknown' otherwise.
+ */
+ public static String getImplementationVersion(Class> clazz) {
+ String classPath = clazz.getResource(clazz.getSimpleName() + ".class").toString();
+ return classPath.startsWith("jar") ? clazz.getPackage().getImplementationVersion() : "unknown-not-jar";
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelConstants.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelConstants.java
new file mode 100644
index 0000000..913f803
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelConstants.java
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel;
+
+public abstract class ControlpanelConstants {
+
+ private ControlpanelConstants() {
+ // Sonar insists on hiding the constructor
+ }
+
+ public static final String ENDPOINT_PREFIX = "/api";
+ // Factor out method names used in multiple controllers
+ public static final String VERSION_METHOD = "version";
+ public static final String APP_NAME_AC = "AC";
+ public static final String APP_NAME_MC = "MC";
+ // The role names are defined by ONAP Portal.
+ // The prefix "ROLE_" is required by Spring.
+ // These are used in Java code annotations that require constants.
+ public static final String ROLE_NAME_STANDARD = "Standard_User";
+ public static final String ROLE_NAME_ADMIN = "System_Administrator";
+ private static final String ROLE_PREFIX = "ROLE_";
+ public static final String ROLE_ADMIN = ROLE_PREFIX + ROLE_NAME_ADMIN;
+ public static final String ROLE_STANDARD = ROLE_PREFIX + ROLE_NAME_STANDARD;
+ public static final String POLICY_CONTROLLER_USERNAME = "admin";
+ public static final String POLICY_CONTROLLER_PASSWORD = "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U";
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelUserManager.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelUserManager.java
new file mode 100644
index 0000000..663526b
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelUserManager.java
@@ -0,0 +1,183 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompRole;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Provides simple user-management services.
+ *
+ * This first implementation serializes user details to a file.
+ */
+public class ControlpanelUserManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // This default value is only useful for development and testing.
+ public static final String USER_FILE_PATH = "controlpanel-users.json";
+
+ private final File userFile;
+ private final List users;
+
+ /**
+ * Development/test-only constructor that uses default file path.
+ *
+ * @param clear
+ * If true, start empty and remove any existing file.
+ *
+ * @throws IOException
+ * On file error
+ */
+ public ControlpanelUserManager(boolean clear) throws IOException {
+ this(USER_FILE_PATH);
+ if (clear) {
+ logger.debug("ctor: removing file {}", userFile.getAbsolutePath());
+ File f = new File(ControlpanelUserManager.USER_FILE_PATH);
+ if (f.exists())
+ Files.delete(f.toPath());
+ users.clear();
+ }
+ }
+
+ /**
+ * Constructur that accepts a file path
+ *
+ * @param userFilePath
+ * File path
+ * @throws IOException
+ * If file cannot be read
+ */
+ public ControlpanelUserManager(final String userFilePath) throws IOException {
+ logger.debug("ctor: userfile {}", userFilePath);
+ if (userFilePath == null)
+ throw new IllegalArgumentException("Missing or empty user file property");
+ userFile = new File(userFilePath);
+ logger.debug("ctor: managing users in file {}", userFile.getAbsolutePath());
+ if (userFile.exists()) {
+ final ObjectMapper mapper = new ObjectMapper();
+ users = mapper.readValue(userFile, new TypeReference>() {});
+ } else {
+ users = new ArrayList<>();
+ }
+ }
+
+ /**
+ * Gets the current users.
+ *
+ * @return List of EcompUser objects, possibly empty
+ */
+ public List getUsers() {
+ return this.users;
+ }
+
+ /**
+ * Gets the user with the specified login Id
+ *
+ * @param loginId
+ * Desired login Id
+ * @return User object; null if Id is not known
+ */
+ public EcompUser getUser(String loginId) {
+ for (EcompUser u : this.users) {
+ if (u.getLoginId().equals(loginId)) {
+ logger.debug("getUser: match on {}", loginId);
+ return u;
+ }
+ }
+ logger.debug("getUser: no match on {}", loginId);
+ return null;
+ }
+
+ private void saveUsers() throws IOException {
+ final ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(userFile, users);
+ }
+
+ /*
+ * Allow at most one thread to create a user at one time.
+ */
+ public synchronized void createUser(EcompUser user) throws PortalAPIException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("createUser: loginId is {}", user.getLoginId());
+ }
+ if (users.contains(user))
+ throw new PortalAPIException("User exists: " + user.getLoginId());
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+ /*
+ * Allow at most one thread to modify a user at one time. We still have
+ * last-edit-wins of course.
+ */
+ public synchronized void updateUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: loginId is {}", loginId);
+ int index = users.indexOf(user);
+ if (index < 0)
+ throw new PortalAPIException("User does not exist: " + user.getLoginId());
+ users.remove(index);
+ users.add(user);
+ try {
+ saveUsers();
+ } catch (Exception ex) {
+ throw new PortalAPIException("Save failed", ex);
+ }
+ }
+
+ // Test infrastructure
+ public static void main(String[] args) throws Exception {
+ ControlpanelUserManager dum = new ControlpanelUserManager(false);
+ EcompUser user = new EcompUser();
+ user.setActive(true);
+ user.setLoginId("demo");
+ user.setFirstName("First");
+ user.setLastName("Last");
+ EcompRole role = new EcompRole();
+ role.setId(1L);
+ role.setName(ControlpanelConstants.ROLE_NAME_ADMIN);
+ Set roles = new HashSet<>();
+ roles.add(role);
+ user.setRoles(roles);
+ dum.createUser(user);
+ logger.debug("Created user {}", user);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/AdminConfiguration.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/AdminConfiguration.java
new file mode 100644
index 0000000..224d0a9
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/AdminConfiguration.java
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import org.oransc.portal.nonrtric.controlpanel.ControlpanelUserManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * Creates an instance of the user manager.
+ */
+@Configuration
+@Profile("!test")
+public class AdminConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Populated by the autowired constructor
+ private final String userfile;
+
+ @Autowired
+ public AdminConfiguration(@Value("${userfile}") final String userfile) {
+ logger.debug("ctor userfile '{}'", userfile);
+ this.userfile = userfile;
+ }
+
+ @Bean
+ // The bean (method) name must be globally unique
+ public ControlpanelUserManager userManager() throws IOException {
+ return new ControlpanelUserManager(userfile);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/PortalApiConfiguration.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/PortalApiConfiguration.java
new file mode 100644
index 0000000..615d705
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/PortalApiConfiguration.java
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.config;
+
+import java.lang.invoke.MethodHandles;
+
+import org.onap.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy;
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@Profile("!test")
+public class PortalApiConfiguration {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Instantiates the EPSDK-FW servlet that implements the API called by Portal.
+ * Needed because this app is not configured to scan the EPSDK-FW packages;
+ * there's also a chance that Spring-Boot does not automatically
+ * process @WebServlet annotations.
+ *
+ * @return Servlet registration bean for the Portal Rest API proxy servlet.
+ */
+ @Bean
+ public ServletRegistrationBean portalApiProxyServletBean() {
+ logger.debug("portalApiProxyServletBean");
+ PortalRestAPIProxy servlet = new PortalRestAPIProxy();
+ final ServletRegistrationBean servletBean =
+ new ServletRegistrationBean<>(servlet, PortalApiConstants.API_PREFIX + "/*");
+ servletBean.setName("PortalRestApiProxyServlet");
+ return servletBean;
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SpringContextCache.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SpringContextCache.java
new file mode 100644
index 0000000..5ceab55
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SpringContextCache.java
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.config;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Allows non-Spring-managed classes to obtain the Spring application context.
+ */
+@Component
+public class SpringContextCache implements ApplicationContextAware {
+
+ private ApplicationContext applicationContext = null;
+
+ @Override
+ public void setApplicationContext(final ApplicationContext appContext) {
+ applicationContext = appContext;
+ }
+
+ public ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SwaggerConfiguration.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SwaggerConfiguration.java
new file mode 100644
index 0000000..7e7cd94
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/SwaggerConfiguration.java
@@ -0,0 +1,69 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.portal.nonrtric.controlpanel.config;
+
+import org.oransc.portal.nonrtric.controlpanel.ControlpanelApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * http://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfiguration {
+
+ /**
+ * @return new Docket
+ */
+ @Bean
+ public Docket api() {
+ return new Docket(DocumentationType.SWAGGER_2).select() //
+ .apis(RequestHandlerSelectors.basePackage(ControlpanelApplication.class.getPackage().getName())) //
+ .paths(PathSelectors.any()) //
+ .build() //
+ .apiInfo(apiInfo());
+ }
+
+ private ApiInfo apiInfo() {
+ final String version = ControlpanelApplication.class.getPackage().getImplementationVersion();
+ return new ApiInfoBuilder() //
+ .title("NonRT RIC Controlpanel backend") //
+ .description("Proxies access to NearRT RIC.")//
+ .termsOfServiceUrl("Terms of service") //
+ .contact(new Contact("NonRT RIC Controlpanel Dev Team", //
+ "http://no-docs-yet.org/", //
+ "noreply@O-RAN-SC.org")) //
+ .license("Apache 2.0 License").licenseUrl("http://www.apache.org/licenses/LICENSE-2.0") //
+ .version(version == null ? "version not available" : version) //
+ .build();
+ }
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/WebSecurityConfiguration.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/WebSecurityConfiguration.java
new file mode 100644
index 0000000..4b65958
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/config/WebSecurityConfiguration.java
@@ -0,0 +1,124 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.config;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+
+import org.onap.portalsdk.core.onboarding.util.PortalApiConstants;
+import org.oransc.portal.nonrtric.controlpanel.ControlpanelUserManager;
+import org.oransc.portal.nonrtric.controlpanel.controller.PolicyController;
+import org.oransc.portal.nonrtric.controlpanel.controller.SimpleErrorController;
+import org.oransc.portal.nonrtric.controlpanel.portalapi.PortalAuthManager;
+import org.oransc.portal.nonrtric.controlpanel.portalapi.PortalAuthenticationFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(securedEnabled = true)
+@Profile("!test")
+public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Although constructor arguments are recommended over field injection,
+ // this results in fewer lines of code.
+ @Value("${portalapi.security}")
+ private Boolean portalapiSecurity;
+ @Value("${portalapi.appname}")
+ private String appName;
+ @Value("${portalapi.username}")
+ private String userName;
+ @Value("${portalapi.password}")
+ private String password;
+ @Value("${portalapi.decryptor}")
+ private String decryptor;
+ @Value("${portalapi.usercookie}")
+ private String userCookie;
+
+ @Autowired
+ ControlpanelUserManager userManager;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ logger.debug("configure: portalapi.username {}", userName);
+ // A chain of ".and()" always baffles me
+ http.authorizeRequests().anyRequest().authenticated();
+ http.headers().frameOptions().disable();
+ http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
+ http.addFilterBefore(portalAuthenticationFilterBean(), BasicAuthenticationFilter.class);
+ }
+
+ /**
+ * Resource paths that do not require authentication, especially including
+ * Swagger-generated documentation.
+ */
+ @SuppressWarnings("squid:S1075") // URIs should not be hardcoded
+ protected static final String[] OPEN_PATHS = { //
+ "/v2/api-docs", //
+ "/swagger-resources/**", //
+ "/swagger-ui.html", //
+ "/webjars/**", //
+ PortalApiConstants.API_PREFIX + "/**", //
+ PolicyController.CONTROLLER_PATH + "/" + PolicyController.VERSION_METHOD, //
+ SimpleErrorController.ERROR_PATH};
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // This disables Spring security, but not the app's filter.
+ web.ignoring().antMatchers(OPEN_PATHS);
+ }
+
+ @Bean
+ public PortalAuthManager portalAuthManagerBean() throws ClassNotFoundException, InstantiationException,
+ IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+ return new PortalAuthManager(appName, userName, password, decryptor, userCookie);
+ }
+
+ /*
+ * If this is annotated with @Bean, it is created automatically AND REGISTERED,
+ * and Spring processes annotations in the source of the class. However, the
+ * filter is added in the chain apparently in the wrong order. Alternately, with
+ * no @Bean and added to the chain up in the configure() method in the desired
+ * order, the ignoring() matcher pattern configured above causes Spring to
+ * bypass this filter, which seems to me means the filter participates
+ * correctly.
+ */
+ public PortalAuthenticationFilter portalAuthenticationFilterBean() throws ClassNotFoundException,
+ InstantiationException, IllegalAccessException, IOException, InvocationTargetException, NoSuchMethodException {
+ return new PortalAuthenticationFilter(portalapiSecurity, portalAuthManagerBean(), this.userManager);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/CustomResponseEntityExceptionHandler.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/CustomResponseEntityExceptionHandler.java
new file mode 100644
index 0000000..a515cfd
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/CustomResponseEntityExceptionHandler.java
@@ -0,0 +1,83 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.controller;
+
+import java.lang.invoke.MethodHandles;
+import org.oransc.portal.nonrtric.controlpanel.model.ErrorTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestClientResponseException;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+/**
+ * Catches certain exceptions. This controller advice factors out try-catch
+ * blocks in many controller methods.
+ *
+ * Also see:
+ * https://www.baeldung.com/exception-handling-for-rest-with-spring
+ * https://www.springboottutorial.com/spring-boot-exception-handling-for-rest-services
+ */
+@ControllerAdvice
+public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
+
+ // Superclass has "logger" that is exposed here, so use a different name
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Logs the error and generates a JSON response when a REST controller method
+ * takes a RestClientResponseException. This is thrown by the Http client when a
+ * remote method returns a non-2xx code. All the controller methods are proxies
+ * in that they just forward the request along to a remote system, so if that
+ * remote system fails, return 502 plus some details about the failure, rather
+ * than the generic 500 that Spring-Boot will return on an uncaught exception.
+ *
+ * Why 502? I quote:
HTTP server received an invalid response from a
+ * server it consulted when acting as a proxy or gateway.
+ *
+ * @param ex
+ * The exception
+ *
+ * @param request
+ * The original request
+ *
+ * @return A response entity with status code 502 plus some details in the body.
+ */
+ @ExceptionHandler({RestClientResponseException.class})
+ public final ResponseEntity handleProxyMethodException(Exception ex, WebRequest request) {
+ // Capture the full stack trace in the log.
+ log.error("handleProxyMethodException: request {}, exception {}", request.getDescription(false),
+ ex.getMessage());
+ if (ex instanceof HttpStatusCodeException) {
+ HttpStatusCodeException hsce = (HttpStatusCodeException) ex;
+ return new ResponseEntity<>(new ErrorTransport(hsce.getRawStatusCode(), hsce.getResponseBodyAsString(),
+ ex.toString(), request.getDescription(false)), HttpStatus.BAD_GATEWAY);
+ } else {
+ return new ResponseEntity<>(new ErrorTransport(500, ex), HttpStatus.BAD_GATEWAY);
+ }
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/Html5PathsController.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/Html5PathsController.java
new file mode 100644
index 0000000..1ed6d0a
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/Html5PathsController.java
@@ -0,0 +1,67 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.controller;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * Listens for requests to known Angular routes.
+ */
+@Controller
+public class Html5PathsController {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /**
+ * Forwards the browser to the index (main) page upon request of a known route.
+ * This unfortunately requires duplication of the Angular route strings in the
+ * path mappings on this method. Could switch to a regex pattern instead someday
+ * if the routes change too often.
+ *
+ * https://stackoverflow.com/questions/44692781/configure-spring-boot-to-redirect-404-to-a-single-page-app
+ *
+ * @param request
+ * HttpServletRequest
+ * @param response
+ * HttpServletResponse
+ * @throws IOException
+ * On error
+ */
+ @GetMapping("/policy")
+ public void forwardAngularRoutes(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ URL url = new URL(request.getScheme(), request.getServerName(), request.getServerPort(), "/index.html");
+ if (logger.isDebugEnabled())
+ logger.debug("forwardAngularRoutes: {} redirected to {}", request.getRequestURI(), url);
+ response.sendRedirect(url.toString());
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/PolicyController.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/PolicyController.java
new file mode 100644
index 0000000..3d08135
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/controller/PolicyController.java
@@ -0,0 +1,139 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.controller;
+
+import io.swagger.annotations.ApiOperation;
+
+import java.lang.invoke.MethodHandles;
+
+import javax.servlet.http.HttpServletResponse;
+import org.oransc.portal.nonrtric.controlpanel.ControlpanelConstants;
+import org.oransc.portal.nonrtric.controlpanel.policyagentapi.PolicyAgentApi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.annotation.Secured;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Proxies calls from the front end to the Policy agent API.
+ *
+ * If a method throws RestClientResponseException, it is handled by
+ * {@link CustomResponseEntityExceptionHandler#handleProxyMethodException(Exception, org.springframework.web.context.request.WebRequest)}
+ * which returns status 502. All other exceptions are handled by Spring which
+ * returns status 500.
+ */
+@RestController
+@RequestMapping(value = PolicyController.CONTROLLER_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
+public class PolicyController {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // Publish paths in constants so tests are easy to write
+ public static final String CONTROLLER_PATH = ControlpanelConstants.ENDPOINT_PREFIX + "/policy";
+ // Endpoints
+ public static final String VERSION_METHOD = ControlpanelConstants.VERSION_METHOD;
+ public static final String POLICY_TYPES_METHOD = "policytypes";
+ public static final String POLICY_TYPE_ID_NAME = "policy_type_id";
+ public static final String POLICIES_NAME = "policies";
+ public static final String POLICY_INSTANCE_ID_NAME = "policy_instance_id";
+
+ // Populated by the autowired constructor
+ private final PolicyAgentApi policyAgentApi;
+
+ @Autowired
+ public PolicyController(final PolicyAgentApi policyAgentApi) {
+ Assert.notNull(policyAgentApi, "API must not be null");
+ this.policyAgentApi = policyAgentApi;
+ logger.debug("ctor: configured with client type {}", policyAgentApi.getClass().getName());
+ }
+
+ /*
+ * The fields are defined in the Policy Control Typescript interface.
+ */
+ @ApiOperation(value = "Gets the policy types from Near Realtime-RIC")
+ @GetMapping(POLICY_TYPES_METHOD)
+ @Secured({ControlpanelConstants.ROLE_ADMIN, ControlpanelConstants.ROLE_STANDARD})
+ public ResponseEntity getAllPolicyTypes(HttpServletResponse response) {
+ logger.debug("getAllPolicyTypes");
+ return this.policyAgentApi.getAllPolicyTypes();
+ }
+
+ @ApiOperation(value = "Returns the policy instances for the given policy type.")
+ @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME)
+ @Secured({ControlpanelConstants.ROLE_ADMIN, ControlpanelConstants.ROLE_STANDARD})
+ public ResponseEntity getPolicyInstances(@PathVariable(POLICY_TYPE_ID_NAME) String policyTypeIdString) {
+ logger.debug("getPolicyInstances {}", policyTypeIdString);
+ return this.policyAgentApi.getPolicyInstancesForType(policyTypeIdString);
+ }
+
+ @ApiOperation(value = "Returns a policy instance of a type")
+ @GetMapping(POLICY_TYPES_METHOD + "/{" + POLICY_TYPE_ID_NAME + "}/" + POLICIES_NAME + "/{" + POLICY_INSTANCE_ID_NAME
+ + "}")
+ @Secured({ControlpanelConstants.ROLE_ADMIN, ControlpanelConstants.ROLE_STANDARD})
+ public ResponseEntity
", //
+ "", //
+ "");
+ }
+
+ /**
+ * Searches the request for a cookie with the specified name.
+ *
+ * @param request
+ * HttpServletRequest
+ * @param cookieName
+ * Cookie name
+ * @return Cookie, or null if not found.
+ */
+ private Cookie getCookie(HttpServletRequest request, String cookieName) {
+ Cookie[] cookies = request.getCookies();
+ if (cookies != null)
+ for (Cookie cookie : cookies)
+ if (cookie.getName().equals(cookieName))
+ return cookie;
+ return null;
+ }
+
+ /**
+ * Gets the ECOMP Portal service cookie value.
+ *
+ * @param request
+ * @return Cookie value, or null if not found.
+ */
+ private String getPortalSessionId(HttpServletRequest request) {
+ Cookie ep = getCookie(request, PortalApiConstants.EP_SERVICE);
+ if (ep == null)
+ return null;
+ return ep.getValue();
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalRestCentralServiceImpl.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalRestCentralServiceImpl.java
new file mode 100644
index 0000000..ea97b1c
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalRestCentralServiceImpl.java
@@ -0,0 +1,93 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.portalapi;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.onap.portalsdk.core.onboarding.crossapi.IPortalRestCentralService;
+import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
+import org.onap.portalsdk.core.restful.domain.EcompUser;
+import org.oransc.portal.nonrtric.controlpanel.ControlpanelUserManager;
+import org.oransc.portal.nonrtric.controlpanel.config.SpringContextCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Implements the contract used by the Portal to transmit user details to this
+ * on-boarded application. The requests are intercepted first by a servlet in
+ * the EPSDK-FW library, which proxies the calls to these methods.
+ *
+ * An instance of this class is created upon first request to the API. But this
+ * class is found and instantiated via Class.forName(), so cannot use Spring
+ * annotations.
+ */
+public class PortalRestCentralServiceImpl implements IPortalRestCentralService {
+
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Autowired
+ private SpringContextCache springContextCache;
+ private final PortalAuthManager authManager;
+ private final ControlpanelUserManager userManager;
+
+ public PortalRestCentralServiceImpl() throws IOException, PortalAPIException {
+ final ApplicationContext context = springContextCache.getApplicationContext();
+ authManager = context.getBean(PortalAuthManager.class);
+ userManager = context.getBean(ControlpanelUserManager.class);
+ }
+
+ /*
+ * Answers the Portal API credentials.
+ */
+ @Override
+ public Map getAppCredentials() throws PortalAPIException {
+ logger.debug("getAppCredentials");
+ return authManager.getAppCredentials();
+ }
+
+ /*
+ * Extracts the user ID from a cookie in the header
+ */
+ @Override
+ public String getUserId(HttpServletRequest request) throws PortalAPIException {
+ logger.debug("getuserId");
+ return authManager.validateEcompSso(request);
+ }
+
+ @Override
+ public void pushUser(EcompUser user) throws PortalAPIException {
+ logger.debug("pushUser: {}", user);
+ userManager.createUser(user);
+ }
+
+ @Override
+ public void editUser(String loginId, EcompUser user) throws PortalAPIException {
+ logger.debug("editUser: {}", user);
+ userManager.updateUser(loginId, user);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorAes.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorAes.java
new file mode 100644
index 0000000..27d1fc1
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorAes.java
@@ -0,0 +1,34 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorAes implements IPortalSdkDecryptor {
+
+ @Override
+ @SuppressWarnings("squid:CallToDeprecatedMethod")
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decrypt(cipherText);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorPkc.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorPkc.java
new file mode 100644
index 0000000..b0f8924
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/portalapi/PortalSdkDecryptorPkc.java
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel.portalapi;
+
+import org.onap.portalsdk.core.onboarding.exception.CipherUtilException;
+import org.onap.portalsdk.core.onboarding.util.CipherUtil;
+
+public class PortalSdkDecryptorPkc implements IPortalSdkDecryptor {
+
+ @Override
+ public String decrypt(String cipherText) throws CipherUtilException {
+ return CipherUtil.decryptPKC(cipherText);
+ }
+
+}
diff --git a/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/util/HttpsURLConnectionUtils.java b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/util/HttpsURLConnectionUtils.java
new file mode 100644
index 0000000..5b1dcb4
--- /dev/null
+++ b/webapp-backend/src/main/java/org/oransc/portal/nonrtric/controlpanel/util/HttpsURLConnectionUtils.java
@@ -0,0 +1,69 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+package org.oransc.portal.nonrtric.controlpanel.util;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+import org.apache.axis2.java.security.TrustAllTrustManager;
+
+/**
+ * Disables and enables certificate and host-name checking in
+ * HttpsURLConnection, the default JVM implementation of the HTTPS/TLS protocol.
+ * Has no effect on implementations such as Apache Http Client, Ok Http.
+ *
+ * https://stackoverflow.com/questions/23504819/how-to-disable-ssl-certificate-checking-with-spring-resttemplate/58291331#58291331
+ */
+public final class HttpsURLConnectionUtils {
+
+ private static final HostnameVerifier jvmHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+
+ private static final HostnameVerifier trivialHostnameVerifier =
+ (hostname, sslSession) -> hostname.equalsIgnoreCase(sslSession.getPeerHost());
+
+ private static final TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[] {new TrustAllTrustManager()};
+
+ public static void turnOffSslChecking() throws NoSuchAlgorithmException, KeyManagementException {
+ HttpsURLConnection.setDefaultHostnameVerifier(trivialHostnameVerifier);
+ // Install the all-trusting trust manager
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, UNQUESTIONING_TRUST_MANAGER, null);
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+
+ public static void turnOnSslChecking() throws KeyManagementException, NoSuchAlgorithmException {
+ HttpsURLConnection.setDefaultHostnameVerifier(jvmHostnameVerifier);
+ // Return it to the initial state (discovered by reflection, now hardcoded)
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, null, null);
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+
+ private HttpsURLConnectionUtils() {
+ throw new UnsupportedOperationException("Do not instantiate libraries.");
+ }
+}
diff --git a/webapp-backend/src/main/resources/ESAPI.properties b/webapp-backend/src/main/resources/ESAPI.properties
new file mode 100644
index 0000000..ca45e09
--- /dev/null
+++ b/webapp-backend/src/main/resources/ESAPI.properties
@@ -0,0 +1,385 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+#===========================================================================
+# ESAPI Configuration
+#
+# If true, then print all the ESAPI properties set here when they are loaded.
+# If false, they are not printed. Useful to reduce output when running JUnit tests.
+# If you need to troubleshoot a properties related problem, turning this on may help.
+# This is 'false' in the src/test/resources/.esapi version. It is 'true' by
+# default for reasons of backward compatibility with earlier ESAPI versions.
+ESAPI.printProperties=false
+
+# ESAPI is designed to be easily extensible. You can use the reference implementation
+# or implement your own providers to take advantage of your enterprise's security
+# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like:
+#
+# String ciphertext =
+# ESAPI.encryptor().encrypt("Secret message"); // Deprecated in 2.0
+# CipherText cipherText =
+# ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred
+#
+# Below you can specify the classname for the provider that you wish to use in your
+# application. The only requirement is that it implement the appropriate ESAPI interface.
+# This allows you to switch security implementations in the future without rewriting the
+# entire application.
+#
+# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory
+ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController
+# FileBasedAuthenticator requires users.txt file in .esapi directory
+ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator
+ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder
+ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor
+
+ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor
+ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities
+ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector
+#ESAPI.Logger=org.owasp.esapi.reference.JavaLogFactory
+ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer
+ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator
+
+#===========================================================================
+# ESAPI Authenticator
+#
+Authenticator.AllowedLoginAttempts=3
+#Authenticator.MaxOldPasswordHashes=13
+Authenticator.UsernameParameterName=username
+#Authenticator.PasswordParameterName=password
+# RememberTokenDuration (in days)
+Authenticator.RememberTokenDuration=14
+# Session Timeouts (in minutes)
+Authenticator.IdleTimeoutDuration=20
+Authenticator.AbsoluteTimeoutDuration=120
+
+#===========================================================================
+# ESAPI Encoder
+#
+# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks.
+# Failure to canonicalize input is a very common mistake when implementing validation schemes.
+# Canonicalization is automatic when using the ESAPI Validator, but you can also use the
+# following code to canonicalize data.
+#
+# ESAPI.Encoder().canonicalize( "%22hello world"" );
+#
+# Multiple encoding is when a single encoding format is applied multiple times. Allowing
+# multiple encoding is strongly discouraged.
+Encoder.AllowMultipleEncoding=false
+
+# Mixed encoding is when multiple different encoding formats are applied, or when
+# multiple formats are nested. Allowing multiple encoding is strongly discouraged.
+Encoder.AllowMixedEncoding=false
+
+# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs
+# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or
+# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important.
+Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec
+
+
+#===========================================================================
+# ESAPI Encryption
+#
+# The ESAPI Encryptor provides basic cryptographic functions with a simplified API.
+# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+# There is not currently any support for key rotation, so be careful when changing your key and salt as it
+# will invalidate all signed, encrypted, and hashed data.
+#
+# WARNING: Not all combinations of algorithms and key lengths are supported.
+# If you choose to use a key length greater than 128, you MUST download the
+# unlimited strength policy files and install in the lib directory of your JRE/JDK.
+# See http://java.sun.com/javase/downloads/index.jsp for more information.
+#
+# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API
+# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever
+# possible, these methods should be avoided as they use ECB cipher mode, which in almost
+# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default
+# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0. In general, you
+# should only use this compatibility setting if you have persistent data encrypted with
+# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL
+# you have decrypted all of your old encrypted data and then re-encrypted it with
+# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode
+# with the new 2.0 methods, make sure that you use the same cipher algorithm for both
+# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for
+# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods
+# where you can specify a SecretKey. (Note that if you are using the 256-bit AES,
+# that requires downloading the special jurisdiction policy files mentioned above.)
+#
+# ***** IMPORTANT: Do NOT forget to replace these with your own values! *****
+# To calculate these values, you can run:
+# java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor
+#
+Encryptor.MasterKey=tzfztf56ftv
+Encryptor.MasterSalt=123456ztrewq
+
+# Provides the default JCE provider that ESAPI will "prefer" for its symmetric
+# encryption and hashing. (That is it will look to this provider first, but it
+# will defer to other providers if the requested algorithm is not implemented
+# by this provider.) If left unset, ESAPI will just use your Java VM's current
+# preferred JCE provider, which is generally set in the file
+# "$JAVA_HOME/jre/lib/security/java.security".
+#
+# The main intent of this is to allow ESAPI symmetric encryption to be
+# used with a FIPS 140-2 compliant crypto-module. For details, see the section
+# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in
+# the ESAPI 2.0 Symmetric Encryption User Guide, at:
+# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html
+# However, this property also allows you to easily use an alternate JCE provider
+# such as "Bouncy Castle" without having to make changes to "java.security".
+# See Javadoc for SecurityProviderLoader for further details. If you wish to use
+# a provider that is not known to SecurityProviderLoader, you may specify the
+# fully-qualified class name of the JCE provider class that implements
+# java.security.Provider. If the name contains a '.', this is interpreted as
+# a fully-qualified class name that implements java.security.Provider.
+#
+# NOTE: Setting this property has the side-effect of changing it in your application
+# as well, so if you are using JCE in your application directly rather than
+# through ESAPI (you wouldn't do that, would you? ;-), it will change the
+# preferred JCE provider there as well.
+#
+# Default: Keeps the JCE provider set to whatever JVM sets it to.
+Encryptor.PreferredJCEProvider=
+
+# AES is the most widely used and strongest encryption algorithm. This
+# should agree with your Encryptor.CipherTransformation property.
+# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is
+# very weak. It is essentially a password-based encryption key, hashed
+# with MD5 around 1K times and then encrypted with the weak DES algorithm
+# (56-bits) using ECB mode and an unspecified padding (it is
+# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses
+# "AES/CBC/PKCSPadding". If you want to change these, change them here.
+# Warning: This property does not control the default reference implementation for
+# ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped
+# in the future.
+# @deprecated
+Encryptor.EncryptionAlgorithm=AES
+# For ESAPI Java 2.0 - New encrypt / decrypt methods use this.
+Encryptor.CipherTransformation=AES/CBC/PKCS5Padding
+
+# Applies to ESAPI 2.0 and later only!
+# Comma-separated list of cipher modes that provide *BOTH*
+# confidentiality *AND* message authenticity. (NIST refers to such cipher
+# modes as "combined modes" so that's what we shall call them.) If any of these
+# cipher modes are used then no MAC is calculated and stored
+# in the CipherText upon encryption. Likewise, if one of these
+# cipher modes is used with decryption, no attempt will be made
+# to validate the MAC contained in the CipherText object regardless
+# of whether it contains one or not. Since the expectation is that
+# these cipher modes support support message authenticity already,
+# injecting a MAC in the CipherText object would be at best redundant.
+#
+# Note that as of JDK 1.5, the SunJCE provider does not support *any*
+# of these cipher modes. Of these listed, only GCM and CCM are currently
+# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports
+# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other
+# padding modes.
+Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC
+
+# Applies to ESAPI 2.0 and later only!
+# Additional cipher modes allowed for ESAPI 2.0 encryption. These
+# cipher modes are in _addition_ to those specified by the property
+# 'Encryptor.cipher_modes.combined_modes'.
+# Note: We will add support for streaming modes like CFB & OFB once
+# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod'
+# (probably in ESAPI 2.1).
+# DISCUSS: Better name?
+Encryptor.cipher_modes.additional_allowed=CBC
+
+# 128-bit is almost always sufficient and appears to be more resistant to
+# related key attacks than is 256-bit AES. Use '_' to use default key size
+# for cipher algorithms (where it makes sense because the algorithm supports
+# a variable key size). Key length must agree to what's provided as the
+# cipher transformation, otherwise this will be ignored after logging a
+# warning.
+#
+# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing!
+Encryptor.EncryptionKeyLength=128
+
+# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV).
+# (All cipher modes except ECB require an IV.) There are two choices: we can either
+# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While
+# the IV does not need to be hidden from adversaries, it is important that the
+# adversary not be allowed to choose it. Also, random IVs are generally much more
+# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes
+# such as CFB and OFB use a different IV for each encryption with a given key so
+# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random
+# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and
+# uncomment the Encryptor.fixedIV.
+#
+# Valid values: random|fixed|specified 'specified' not yet implemented; planned for 2.1
+Encryptor.ChooseIVMethod=random
+# If you choose to use a fixed IV, then you must place a fixed IV here that
+# is known to all others who are sharing your secret key. The format should
+# be a hex string that is the same length as the cipher block size for the
+# cipher algorithm that you are using. The following is an *example* for AES
+# from an AES test vector for AES-128/CBC as described in:
+# NIST Special Publication 800-38A (2001 Edition)
+# "Recommendation for Block Cipher Modes of Operation".
+# (Note that the block size for AES is 16 bytes == 128 bits.)
+#
+Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f
+
+# Whether or not CipherText should use a message authentication code (MAC) with it.
+# This prevents an adversary from altering the IV as well as allowing a more
+# fool-proof way of determining the decryption failed because of an incorrect
+# key being supplied. This refers to the "separate" MAC calculated and stored
+# in CipherText, not part of any MAC that is calculated as a result of a
+# "combined mode" cipher mode.
+#
+# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also
+# set this property to false.
+Encryptor.CipherText.useMAC=true
+
+# Whether or not the PlainText object may be overwritten and then marked
+# eligible for garbage collection. If not set, this is still treated as 'true'.
+Encryptor.PlainText.overwrite=true
+
+# Do not use DES except in a legacy situations. 56-bit is way too small key size.
+#Encryptor.EncryptionKeyLength=56
+#Encryptor.EncryptionAlgorithm=DES
+
+# TripleDES is considered strong enough for most purposes.
+# Note: There is also a 112-bit version of DESede. Using the 168-bit version
+# requires downloading the special jurisdiction policy from Sun.
+#Encryptor.EncryptionKeyLength=168
+#Encryptor.EncryptionAlgorithm=DESede
+
+Encryptor.HashAlgorithm=SHA-512
+Encryptor.HashIterations=1024
+Encryptor.DigitalSignatureAlgorithm=SHA1withDSA
+Encryptor.DigitalSignatureKeyLength=1024
+Encryptor.RandomAlgorithm=SHA1PRNG
+Encryptor.CharacterEncoding=UTF-8
+
+# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function
+# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and
+# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for
+# the MAC, mostly to keep the overall size at a minimum.)
+#
+# Currently supported choices for JDK 1.5 and 1.6 are:
+# HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and
+# HmacSHA512 (512 bits).
+# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though
+# the JDKs support it. See the ESAPI 2.0 Symmetric Encryption User Guide
+# further details.
+Encryptor.KDF.PRF=HmacSHA256
+#===========================================================================
+# ESAPI Logging
+# Set the application name if these logs are combined with other applications
+Logger.ApplicationName=portal_nonrtric_controlpanel
+# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true
+Logger.LogEncodingRequired=false
+# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments.
+Logger.LogApplicationName=true
+# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments.
+Logger.LogServerIP=true
+# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you
+# want to place it in a specific directory.
+Logger.LogFileName=portal_nonrtric_controlpanel_esapi_log
+# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000)
+Logger.MaxLogFileSize=10000000
+
+
+#===========================================================================
+# ESAPI Intrusion Detection
+#
+# Each event has a base to which .count, .interval, and .action are added
+# The IntrusionException will fire if we receive "count" events within "interval" seconds
+# The IntrusionDetector is configurable to take the following actions: log, logout, and disable
+# (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable
+#
+# Custom Events
+# Names must start with "event." as the base
+# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here
+# You can also disable intrusion detection completely by changing
+# the following parameter to true
+#
+IntrusionDetector.Disable=false
+#
+IntrusionDetector.event.test.count=2
+IntrusionDetector.event.test.interval=10
+IntrusionDetector.event.test.actions=disable,log
+
+# Exception Events
+# All EnterpriseSecurityExceptions are registered automatically
+# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException
+# Use the fully qualified classname of the exception as the base
+
+# any intrusion is an attack
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1
+IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout
+
+# for test purposes
+# CHECKME: Shouldn't there be something in the property name itself that designates
+# that these are for testing???
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5
+IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout
+
+# rapid validation errors indicate scans or attacks in progress
+# org.owasp.esapi.errors.ValidationException.count=10
+# org.owasp.esapi.errors.ValidationException.interval=10
+# org.owasp.esapi.errors.ValidationException.actions=log,logout
+
+# sessions jumping between hosts indicates session hijacking
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10
+IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout
+
+
+#===========================================================================
+# ESAPI Validation
+#
+# The ESAPI Validator works on regular expressions with defined names. You can define names
+# either here, or you may define application specific patterns in a separate file defined below.
+# This allows enterprises to specify both organizational standards as well as application specific
+# validation rules.
+#
+Validator.ConfigurationFile=validation.properties
+Validator.ConfigurationFile.MultiValued=false
+
+# Validators used by ESAPI
+Validator.AccountName=^[a-zA-Z0-9]{3,20}$
+Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$
+Validator.RoleName=^[a-z]{1,20}$
+
+#the word TEST below should be changed to your application
+#name - only relative URL's are supported
+Validator.Redirect=^\\/test.*$
+
+# Global HTTP Validation Rules
+# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=]
+Validator.HTTPScheme=^(http|https)$
+Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$
+Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$
+Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$
+Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$
+Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,32}$
+Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$
+Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$
+Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$
+Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$
+Validator.HTTPURL=^.*$
+Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$
+
+# Validation of file related input
+Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
+Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$
diff --git a/webapp-backend/src/main/resources/application.properties b/webapp-backend/src/main/resources/application.properties
new file mode 100644
index 0000000..725f658
--- /dev/null
+++ b/webapp-backend/src/main/resources/application.properties
@@ -0,0 +1,63 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# Modifications Copyright (C) 2019 Nordix Foundation
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Defines NonRT RIC Controlpanel property keys and default values.
+# Create a copy in the launch directory to override values; or
+# copy to "application-abc.properties" as mentioned in the README.
+
+# A spring property but without a "spring" prefix;
+# the port number is chosen RANDOMLY when running tests
+server.port = 8080
+
+# path to file that stores user details;
+# use a persistent volume in a K8S deployment
+userfile = users.json
+
+# boolean flag whether to enforce Portal user and roles on requests
+portalapi.security = false
+# class that decrypts ciphertext from Portal
+portalapi.decryptor = org.oransc.portal.nonrtric.controlpanel.portalapi.PortalSdkDecryptorAes
+# name of request cookie with user ID
+portalapi.usercookie = UserId
+
+# portal credentials must be supplied at deployment time
+portalapi.appname = NonRT RIC Controlpanel
+portalapi.username =
+portalapi.password =
+
+# endpoint URLs must be supplied at deployment time
+# NOTE: change policycontroller.url.prefix to http://localhost:8081 when running
+# controlpanel locally (i.e., not inside the docker container)
+policycontroller.url.prefix = http://policy-agent-container:8081
+
+
+# Kubernetes API via https://github.com/nokia/caas-ingress
+# Set insecure=true to disable SSL certificate and hostname checking
+caasingress.insecure = true
+caasingress.aux.url.prefix = https://jar-app-props-default-caas-ingress-aux-URL
+caasingress.aux.url.suffix = /api
+caasingress.plt.url.prefix = https://jar-app-props-default-caas-ingress-plt-URL
+caasingress.plt.url.suffix = /api
+
+# Mimic slow endpoints by defining sleep period, in milliseconds
+mock.config.delay = 0
+
+# Kibana report on metrics
+metrics.url.ac = http://jar-app-props-kibana-url-ac
+metrics.url.mc = http://jar-app-props-kibana-url-mc
diff --git a/webapp-backend/src/main/resources/key.properties b/webapp-backend/src/main/resources/key.properties
new file mode 100644
index 0000000..c2cab7d
--- /dev/null
+++ b/webapp-backend/src/main/resources/key.properties
@@ -0,0 +1,22 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Test properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+cipher.enc.key = bogus
diff --git a/webapp-backend/src/main/resources/logback.xml b/webapp-backend/src/main/resources/logback.xml
new file mode 100644
index 0000000..e966bc6
--- /dev/null
+++ b/webapp-backend/src/main/resources/logback.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${pattern}
+
+
+
+
+ ${logDirectory}/${componentName}.log
+ true
+
+ ${logDirectory}/${componentName}.%i.log.zip
+ 1
+ 9
+
+
+ 10MB
+
+
+
+ ${pattern}
+
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp-backend/src/main/resources/portal.properties b/webapp-backend/src/main/resources/portal.properties
new file mode 100644
index 0000000..f0d6eca
--- /dev/null
+++ b/webapp-backend/src/main/resources/portal.properties
@@ -0,0 +1,34 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# Template for the file that provides properties for the EPSDK-FW library.
+# This file must be present on the Java classpath.
+
+# The following properties are the same in every deployment
+
+portal.api.impl.class = org.oransc.portal.nonrtric.controlpanel.portalapi.PortalRestCentralServiceImpl
+role_access_centralized = remote
+
+# The following properties are DIFFERENT in every deployment
+
+# URL of portal login screen
+ecomp_redirect_url = http://localhost/portal
+# URL of portal API
+ecomp_rest_url = http://localhost/portal
+# Value assigned by portal instance
+ueb_app_key = abcdef1234567890
diff --git a/webapp-backend/src/main/resources/static/error.html b/webapp-backend/src/main/resources/static/error.html
new file mode 100644
index 0000000..f52c9b1
--- /dev/null
+++ b/webapp-backend/src/main/resources/static/error.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+Static error page
+
+
+
+
NonRT RIC Controlpanel Error
+
The previous request could not be processed.
+Click here to reload the application
+
+
diff --git a/webapp-backend/src/main/resources/validation.properties b/webapp-backend/src/main/resources/validation.properties
new file mode 100644
index 0000000..22eaa4e
--- /dev/null
+++ b/webapp-backend/src/main/resources/validation.properties
@@ -0,0 +1,19 @@
+# ========================LICENSE_START=================================
+# O-RAN-SC
+# %%
+# Copyright (C) 2019 AT&T Intellectual Property
+# %%
+# Licensed 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.
+# ========================LICENSE_END===================================
+
+# empty file to suppress OWASP complaints emitted to stdout
diff --git a/webapp-backend/src/test/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelTestServer.java b/webapp-backend/src/test/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelTestServer.java
new file mode 100644
index 0000000..fdd8103
--- /dev/null
+++ b/webapp-backend/src/test/java/org/oransc/portal/nonrtric/controlpanel/ControlpanelTestServer.java
@@ -0,0 +1,70 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+package org.oransc.portal.nonrtric.controlpanel;
+
+import java.lang.invoke.MethodHandles;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * This class supports front-end web development. Placing this class in the test
+ * area allows excluding the mock configuration classes and the Mockito
+ * dependencies from the packaged version of the app.
+ *
+ * To launch a development server set the environment variable as listed below.
+ * This runs a Spring-Boot server with mock back-end beans, and keeps the server
+ * alive for manual testing. Supply this JVM argument:
+ *
+ *
+
+
\ No newline at end of file
diff --git a/webapp-frontend/src/app/controlpanel.component.scss b/webapp-frontend/src/app/controlpanel.component.scss
new file mode 100644
index 0000000..ddefe09
--- /dev/null
+++ b/webapp-frontend/src/app/controlpanel.component.scss
@@ -0,0 +1,379 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+.root__container {
+ width: 100vw;
+ height: 100vh;
+ display: grid;
+ grid-template-columns: auto;
+ grid-template-rows: 0.5fr auto;
+ position: relative;
+}
+
+/*
+================
+ Header
+================
+*/
+mat-sidenav-container,
+mat-sidenav-content,
+mat-sidenav {
+ height: 100%;
+}
+
+mat-sidenav {
+ width: 250px;
+}
+
+main {
+ padding: 10px;
+}
+
+/*
+ Slide Menu
+= = = = = = = = =
+*/
+.side-menu__dark {
+ color: white;
+ background: gray;
+}
+
+.side-menu__container {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+ z-index: 25;
+}
+
+.side-menu__container-active {
+ pointer-events: auto;
+}
+
+.side-menu__container::before {
+ content: '';
+ cursor: pointer;
+ position: absolute;
+ display: block;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ background-color: #0c1066;
+ opacity: 0;
+ transition: opacity 300ms linear;
+ will-change: opacity;
+}
+
+.side-menu__container-active::before {
+ opacity: 0.3;
+}
+
+.slide-menu {
+ box-sizing: border-box;
+ transform: translateX(-103%);
+ position: relative;
+ top: 0;
+ left: 0;
+ z-index: 10;
+ height: 100%;
+ width: 90%;
+ max-width: 26rem;
+ background-color: white;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 2fr 4fr 1fr;
+ grid-gap: 1rem;
+ transition: transform 300ms linear;
+ will-change: transform;
+}
+
+.slide-menu-active {
+ transform: none;
+}
+
+.menu-header.menu-header__dark {
+ background: #2B244D;
+}
+
+.menu-header {
+ background: linear-gradient(to right, rgb(181, 199, 192), #82bbb6);
+ display: grid;
+ grid-template-rows: 1fr 4fr;
+ grid-template-columns: 1fr 4fr;
+ grid-template-areas: "greeting greeting""image details";
+ box-sizing: border-box;
+ width: 100%;
+ align-content: center;
+ color: white;
+ box-shadow: 0 0.5rem 2rem rgba(0, 0, 255, 0.2);
+}
+
+mat-drawer {
+ width: 340px;
+}
+
+mat-drawer-content {
+ overflow: overlay;
+}
+
+.menumargin-top {
+ margin-top: 10px;
+}
+
+.greeting__text {
+ grid-area: greeting;
+ font-size: 1.25rem;
+ letter-spacing: 0.15rem;
+ text-transform: uppercase;
+ margin-top: 1rem;
+ justify-self: center;
+ align-self: center;
+}
+
+.account-details {
+ grid-area: details;
+ display: flex;
+ flex-flow: column;
+ margin-left: 1rem;
+ align-self: center;
+}
+
+.name__text {
+ font-size: 1.15rem;
+ margin-bottom: 0.5rem;
+}
+
+.email__text {
+ font-size: 0.9rem;
+ letter-spacing: 0.1rem;
+}
+
+.menu-body {
+ display: grid;
+ width: 100%;
+}
+
+.profile-image__container {
+ grid-area: image;
+ margin-right: 0.5rem;
+ border-radius: 50%;
+ height: 4rem;
+ width: 4rem;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: white;
+ align-self: center;
+ margin-left: 2rem;
+}
+
+.profile__image {
+ max-width: 4rem;
+}
+
+.home_bg_image {
+ height: 40em;
+ background-size: cover;
+ width: auto;
+ background-image: url('../assets/intelligence.png');
+ background-position: 50% 50%;
+}
+
+/*Header*/
+.main__header {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 1fr 1fr 0.25fr;
+ grid-template-rows: 1fr;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ height: 4rem;
+ margin: 0;
+ align-items: center;
+ transition: background-color 500ms linear;
+ animation: fadein 1s ease-in-out 0ms 1;
+}
+
+.main__header-dark {
+ background-color: #2B244D;
+ color: white;
+}
+
+.toggle-button__container {
+ cursor: pointer;
+ position: relative;
+ margin: 0 0.5rem;
+}
+
+.mode-toggle__input {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+}
+
+.mode-toggle__bg {
+ height: 1rem;
+ width: 2rem;
+ border-radius: 0.5rem;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: inline-block;
+ transition: background-color 300ms linear;
+}
+
+.mode-toggle__circle {
+ height: 1.30rem;
+ width: 1.30rem;
+ background-color: #2B244D;
+ position: absolute;
+ top: -0.2rem;
+ border-radius: 50%;
+ box-shadow: 0 0 0 rgba(0, 0, 255, 0.5);
+ transition: left 300ms linear;
+ left: 0.1rem;
+}
+
+.mode-toggle__circle-checked {
+ background-color: white;
+ left: 1.75rem;
+}
+
+.mode-toggle__bg-checked {
+ background-color: #FF0070;
+}
+
+.mode-toggle__text {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+}
+
+/*Content*/
+.left__section {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr 1fr;
+ max-width: 5rem;
+}
+
+.left__section3Col {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr 1fr 1fr;
+ max-width: 6rem;
+}
+
+.date__text {
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+ display: inline;
+ margin: 0.5rem 0;
+}
+
+/*SVGs*/
+.hamburger__icon {
+ position: relative;
+ z-index: 35;
+ height: 2rem;
+ padding: 0.5rem 1.5rem;
+ margin-right: 1rem;
+ cursor: pointer;
+}
+
+.logo__icon {
+ height: 2rem;
+ margin-left: 1rem;
+}
+
+.logo__text {
+ fill: #2B244D;
+}
+
+.logo__text-dark {
+ fill: #ffffff;
+}
+
+.hamburger__icon__fill {
+ fill: #2B244D;
+}
+
+.hamburger__icon__fill-dark {
+ fill: #ffffff;
+}
+
+/*
+================
+ Body
+================
+*/
+
+.main__container {
+ height: 100%;
+ width: 100%;
+}
+
+.main__container__body {
+ min-height: calc(100vh - 140px);
+}
+
+.main-container__bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -2;
+ opacity: 0;
+ background: white;
+ transition: opacity 300ms linear;
+}
+
+.main-container__bg-dark {
+ opacity: 1;
+ background: linear-gradient(to bottom, rgb(62, 59, 68), rgb(11, 10, 15));
+ transition: opacity 300ms linear;
+}
+
+/*
+================-
+ Footer
+================
+*/
+.main__footer {
+ background: transparent;
+ z-index: 100;
+}
+
+.main__footer-dark {
+ background-color: #2B244D;
+ color: white;
+}
+
+.main-footer__bg-dark {
+ opacity: 1;
+ background: rgb(11, 10, 15);
+ transition: opacity 300ms linear;
+}
+
+@media only screen and (max-width: 300px) {
+ .slide-menu {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/controlpanel.component.spec.ts b/webapp-frontend/src/app/controlpanel.component.spec.ts
new file mode 100644
index 0000000..4a86e91
--- /dev/null
+++ b/webapp-frontend/src/app/controlpanel.component.spec.ts
@@ -0,0 +1,54 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed, async } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ RouterTestingModule
+ ],
+ declarations: [
+ AppComponent
+ ],
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'controlpanelApp'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('controlpanelApp');
+ });
+
+ it('should render title in a h1 tag', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain('Welcome to controlpanelApp!');
+ });
+});
diff --git a/webapp-frontend/src/app/controlpanel.component.ts b/webapp-frontend/src/app/controlpanel.component.ts
new file mode 100644
index 0000000..be39fc1
--- /dev/null
+++ b/webapp-frontend/src/app/controlpanel.component.ts
@@ -0,0 +1,57 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+import { UiService } from './services/ui/ui.service';
+import { CookieService } from 'ngx-cookie';
+
+@Component({
+ selector: 'rd-root',
+ templateUrl: './controlpanel.component.html',
+ styleUrls: ['./controlpanel.component.scss']
+})
+export class ControlpanelComponent implements OnInit {
+ private showMenu = false;
+ private darkMode: boolean;
+ private 'DARK_MODE_COOKIE' = 'darkMode';
+
+ constructor(private cookieService: CookieService, private ui: UiService) {
+ }
+
+ ngOnInit() {
+ const dark = this.cookieService.get(this.DARK_MODE_COOKIE);
+ if (dark) {
+ this.ui.darkModeState.next(dark === 'yes');
+ }
+
+ this.ui.darkModeState.subscribe((value) => {
+ this.darkMode = value;
+ });
+ }
+
+ toggleMenu() {
+ this.showMenu = !this.showMenu;
+ }
+
+ modeToggleSwitch() {
+ this.ui.darkModeState.next(!this.darkMode);
+ this.cookieService.put(this.DARK_MODE_COOKIE, this.darkMode ? 'yes' : 'no');
+ }
+
+}
diff --git a/webapp-frontend/src/app/controlpanel.module.ts b/webapp-frontend/src/app/controlpanel.module.ts
new file mode 100644
index 0000000..2540edd
--- /dev/null
+++ b/webapp-frontend/src/app/controlpanel.module.ts
@@ -0,0 +1,141 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { BrowserModule } from '@angular/platform-browser';
+// tslint:disable-next-line:max-line-length
+import {
+ MatButtonModule, MatButtonToggleModule, MatCardModule, MatCheckboxModule,
+ MatDialogModule, MatExpansionModule, MatFormFieldModule, MatGridListModule,
+ MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatPaginatorModule,
+ MatProgressSpinnerModule, MatSelectModule, MatSidenavModule, MatSliderModule,
+ MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatTableModule,
+ MatTabsModule, MatToolbarModule
+} from '@angular/material';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { HttpClientModule } from '@angular/common/http';
+import { NgModule } from '@angular/core';
+import { MatRadioModule } from '@angular/material/radio';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { ChartsModule } from 'ng2-charts';
+import { MDBBootstrapModule } from 'angular-bootstrap-md';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ToastrModule } from 'ngx-toastr';
+import { ConfirmDialogComponent } from './ui/confirm-dialog/confirm-dialog.component';
+import { ControlpanelService } from './services/controlpanel/controlpanel.service';
+import { ErrorDialogComponent } from './ui/error-dialog/error-dialog.component';
+import { ErrorDialogService } from './services/ui/error-dialog.service';
+import { FlexLayoutModule } from '@angular/flex-layout';
+import { FooterComponent } from './footer/footer.component';
+import { MainComponent } from './main/main.component';
+import { MaterialDesignFrameworkModule } from 'angular6-json-schema-form';
+import { PolicyCardComponent } from './ui/policy-card/policy-card.component';
+import { PolicyControlComponent } from './policy-control/policy-control.component';
+import { PolicyInstanceComponent } from './policy-control/policy-instance.component';
+import { PolicyInstanceDialogComponent } from './policy-control/policy-instance-dialog.component';
+import { ControlpanelComponent } from './controlpanel.component';
+import { ControlpanelRoutingModule } from './controlpanel-routing.module';
+import { SidenavListComponent } from './navigation/sidenav-list/sidenav-list.component';
+import { UiService } from './services/ui/ui.service';
+import { CookieModule } from 'ngx-cookie';
+
+@NgModule({
+ declarations: [
+ ConfirmDialogComponent,
+ ErrorDialogComponent,
+ FooterComponent,
+ MainComponent,
+ PolicyCardComponent,
+ PolicyControlComponent,
+ PolicyInstanceComponent,
+ PolicyInstanceDialogComponent,
+ ControlpanelComponent,
+ SidenavListComponent
+ ],
+ imports: [
+ BrowserModule,
+ BrowserAnimationsModule,
+ ChartsModule,
+ FlexLayoutModule,
+ FormsModule,
+ HttpClientModule,
+ MatButtonModule,
+ MatButtonToggleModule,
+ MatCardModule,
+ MatCheckboxModule,
+ MatDialogModule,
+ MaterialDesignFrameworkModule,
+ MatExpansionModule,
+ MatFormFieldModule,
+ MatGridListModule,
+ MatIconModule,
+ MatInputModule,
+ MatListModule,
+ MatMenuModule,
+ MatPaginatorModule,
+ MatProgressSpinnerModule,
+ MatRadioModule,
+ MatSelectModule,
+ MatSliderModule,
+ MatSidenavModule,
+ MatSlideToggleModule,
+ MatSnackBarModule,
+ MatSortModule,
+ MatTableModule,
+ MatTabsModule,
+ MatToolbarModule,
+ MatTooltipModule,
+ MDBBootstrapModule.forRoot(),
+ ControlpanelRoutingModule,
+ ReactiveFormsModule,
+ ToastrModule.forRoot(),
+ CookieModule.forRoot()
+ ],
+ exports: [
+ ErrorDialogComponent,
+ FormsModule,
+ MatButtonModule,
+ MatButtonToggleModule,
+ MatCardModule,
+ MatDialogModule,
+ MatExpansionModule,
+ MatFormFieldModule,
+ MatGridListModule,
+ MatIconModule,
+ MatInputModule,
+ MatListModule,
+ MatSidenavModule,
+ MatSliderModule,
+ MatSlideToggleModule,
+ MatTabsModule
+ ],
+ entryComponents: [
+ ConfirmDialogComponent,
+ ErrorDialogComponent,
+ PolicyInstanceDialogComponent
+ ],
+ providers: [
+ ControlpanelService,
+ ErrorDialogService,
+ UiService
+ ],
+ bootstrap: [ControlpanelComponent]
+})
+export class ControlpanelModule { }
diff --git a/webapp-frontend/src/app/footer/footer.component.html b/webapp-frontend/src/app/footer/footer.component.html
new file mode 100644
index 0000000..34d88bb
--- /dev/null
+++ b/webapp-frontend/src/app/footer/footer.component.html
@@ -0,0 +1,25 @@
+
+
+ Copyright (C) 2019 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0.
+
+ Modifications Copyright (C) 2019 Nordix Foundation
+
\ No newline at end of file
diff --git a/webapp-frontend/src/app/footer/footer.component.scss b/webapp-frontend/src/app/footer/footer.component.scss
new file mode 100644
index 0000000..077567e
--- /dev/null
+++ b/webapp-frontend/src/app/footer/footer.component.scss
@@ -0,0 +1,30 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+.copyright__text {
+ color: gray;
+ letter-spacing: 0.1rem;
+ font-size: 10px;
+ margin-left: 10px;
+}
+
+.copyright__text-dark {
+ color: gray;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/footer/footer.component.spec.ts b/webapp-frontend/src/app/footer/footer.component.spec.ts
new file mode 100644
index 0000000..9b1d31a
--- /dev/null
+++ b/webapp-frontend/src/app/footer/footer.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FooterComponent } from './footer.component';
+
+describe('FooterComponent', () => {
+ let component: FooterComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ FooterComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FooterComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/footer/footer.component.ts b/webapp-frontend/src/app/footer/footer.component.ts
new file mode 100644
index 0000000..47e768e
--- /dev/null
+++ b/webapp-frontend/src/app/footer/footer.component.ts
@@ -0,0 +1,52 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2019 Nordix Foundation
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+import { ControlpanelSuccessTransport } from '../interfaces/controlpanel.types';
+import { ControlpanelService } from '../services/controlpanel/controlpanel.service';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.scss']
+})
+
+/**
+ * Fetches the version on load for display in the footer
+ */
+export class FooterComponent implements OnInit {
+ darkMode: boolean;
+ controlpanelVersion: string;
+
+ // Inject the service
+ constructor(private controlpanelService: ControlpanelService,
+ private ui: UiService) { }
+
+ ngOnInit() {
+ this.controlpanelVersion = '0.0';
+ // this.controlpanelService.getVersion().subscribe((res: ControlpanelSuccessTransport) => this.controlpanelVersion = res.data);
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+}
diff --git a/webapp-frontend/src/app/interfaces/controlpanel.types.ts b/webapp-frontend/src/app/interfaces/controlpanel.types.ts
new file mode 100644
index 0000000..ec6e5c8
--- /dev/null
+++ b/webapp-frontend/src/app/interfaces/controlpanel.types.ts
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by Controlpanel admin services
+
+export interface ControlpanelSuccessTransport {
+ status: number;
+ data: string;
+}
+
+export interface EcompRoleFunction {
+ name: string;
+ code: string;
+ type: string;
+ action: string;
+}
+
+export interface EcompRole {
+ id: number;
+ name: string;
+ [position: number]: EcompRoleFunction;
+}
+
+export interface EcompUser {
+ orgId?: number;
+ managerId?: string;
+ firstName?: string;
+ middleInitial?: string;
+ lastName?: string;
+ phone?: string;
+ email?: string;
+ hrid?: string;
+ orgUserId?: string;
+ orgCode?: string;
+ orgManagerUserId?: string;
+ jobTitle?: string;
+ loginId: string;
+ active: boolean;
+ [position: number]: EcompRole;
+}
diff --git a/webapp-frontend/src/app/interfaces/policy.types.ts b/webapp-frontend/src/app/interfaces/policy.types.ts
new file mode 100644
index 0000000..b6ecce6
--- /dev/null
+++ b/webapp-frontend/src/app/interfaces/policy.types.ts
@@ -0,0 +1,40 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+// Models of data used by the Policy Control
+
+export interface PolicyType {
+ name: string;
+ schema: string;
+ schemaObject: any;
+}
+
+export interface PolicyInstance {
+ id: string;
+ json: string;
+ ric: string;
+ service: string;
+ lastModified: string;
+}
+
+export interface PolicyInstanceAck {
+ status: string;
+ message: string;
+}
diff --git a/webapp-frontend/src/app/main/main.component.html b/webapp-frontend/src/app/main/main.component.html
new file mode 100644
index 0000000..f05fd50
--- /dev/null
+++ b/webapp-frontend/src/app/main/main.component.html
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/webapp-frontend/src/app/main/main.component.scss b/webapp-frontend/src/app/main/main.component.scss
new file mode 100644
index 0000000..836d80a
--- /dev/null
+++ b/webapp-frontend/src/app/main/main.component.scss
@@ -0,0 +1,27 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+.main__container {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(auto-fill, 1fr);
+ align-items: center;
+ justify-items: center;
+ height: 100%;
+}
diff --git a/webapp-frontend/src/app/main/main.component.spec.ts b/webapp-frontend/src/app/main/main.component.spec.ts
new file mode 100644
index 0000000..df9a9aa
--- /dev/null
+++ b/webapp-frontend/src/app/main/main.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MainComponent } from './main.component';
+
+describe('MainComponent', () => {
+ let component: MainComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ MainComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MainComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/main/main.component.ts b/webapp-frontend/src/app/main/main.component.ts
new file mode 100644
index 0000000..9d11525
--- /dev/null
+++ b/webapp-frontend/src/app/main/main.component.ts
@@ -0,0 +1,33 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'rd-main',
+ templateUrl: './main.component.html',
+ styleUrls: ['./main.component.scss']
+})
+export class MainComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit() { }
+
+}
diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
new file mode 100644
index 0000000..9dea90a
--- /dev/null
+++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ homeHome
+
+
+ assignmentPolicy
+
+
diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss
new file mode 100644
index 0000000..d5a9e7e
--- /dev/null
+++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.scss
@@ -0,0 +1,36 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+a {
+ text-decoration: none;
+ color: black;
+}
+
+.dark a {
+ color: white;
+}
+
+a:hover, a:active{
+ color: lightgray;
+}
+
+.nav-caption{
+ display: inline-block;
+ padding-left: 6px;
+}
diff --git a/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
new file mode 100644
index 0000000..914fa2d
--- /dev/null
+++ b/webapp-frontend/src/app/navigation/sidenav-list/sidenav-list.component.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnInit, Output, EventEmitter } from '@angular/core';
+import { UiService } from '../../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-sidenav-list',
+ templateUrl: './sidenav-list.component.html',
+ styleUrls: ['./sidenav-list.component.scss']
+})
+export class SidenavListComponent implements OnInit {
+ darkMode: boolean;
+ @Output() sidenavClose = new EventEmitter();
+
+ constructor(private ui: UiService) { }
+
+ ngOnInit() {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ public onSidenavClose = () => {
+ this.sidenavClose.emit();
+ }
+
+}
diff --git a/webapp-frontend/src/app/policy-control/policy-control.component.html b/webapp-frontend/src/app/policy-control/policy-control.component.html
new file mode 100644
index 0000000..f185207
--- /dev/null
+++ b/webapp-frontend/src/app/policy-control/policy-control.component.html
@@ -0,0 +1,80 @@
+
+
+
\ No newline at end of file
diff --git a/webapp-frontend/src/app/policy-control/policy-instance.component.scss b/webapp-frontend/src/app/policy-control/policy-instance.component.scss
new file mode 100644
index 0000000..6a18c34
--- /dev/null
+++ b/webapp-frontend/src/app/policy-control/policy-instance.component.scss
@@ -0,0 +1,52 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+.instances-table {
+ width: 90%;
+ min-width: 1200px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ background-color: grayscale($color: #eeeaea);
+}
+
+.mat-column-instanceId {
+ word-wrap: break-word;
+ white-space: unset;
+ flex: 0 0 28%;
+ width: 28%;
+}
+
+
+.table-dark {
+ background-color: #2d2d3d;
+}
+
+.action-cell {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.display-none {
+ display: none;
+}
+
+.spinner-container mat-spinner {
+ margin: 0 auto 0 auto;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/policy-control/policy-instance.component.ts b/webapp-frontend/src/app/policy-control/policy-instance.component.ts
new file mode 100644
index 0000000..ecf6f36
--- /dev/null
+++ b/webapp-frontend/src/app/policy-control/policy-instance.component.ts
@@ -0,0 +1,129 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { MatSort } from '@angular/material';
+import { Component, OnInit, ViewChild, Input, AfterViewInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyInstanceDataSource } from './policy-instance.datasource';
+import { ErrorDialogService } from '../services/ui/error-dialog.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyService } from '../services/policy/policy.service';
+import { ConfirmDialogService } from './../services/ui/confirm-dialog.service';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyInstanceDialogComponent } from './policy-instance-dialog.component';
+import { getPolicyDialogProperties } from './policy-instance-dialog.component';
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { UiService } from '../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-policy-instance',
+ templateUrl: './policy-instance.component.html',
+ styleUrls: ['./policy-instance.component.scss']
+})
+
+
+export class PolicyInstanceComponent implements OnInit, AfterViewInit {
+ instanceDataSource: PolicyInstanceDataSource;
+ @Input() policyType: PolicyType;
+ @Input() expanded: Observable;
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
+ darkMode: boolean;
+
+ constructor(
+ private policySvc: PolicyService,
+ private dialog: MatDialog,
+ private errorDialogService: ErrorDialogService,
+ private notificationService: NotificationService,
+ private confirmDialogService: ConfirmDialogService,
+ private ui: UiService) {
+ }
+
+ ngOnInit() {
+ this.instanceDataSource = new PolicyInstanceDataSource(this.policySvc, this.sort, this.notificationService, this.policyType);
+ this.expanded.subscribe((isExpanded: boolean) => this.onExpand(isExpanded));
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ ngAfterViewInit() {
+ this.instanceDataSource.sort = this.sort;
+ }
+
+ private onExpand(isExpanded: boolean) {
+ if (isExpanded) {
+ this.instanceDataSource.loadTable();
+ }
+ }
+
+ modifyInstance(instance: PolicyInstance): void {
+ this.policySvc.getPolicy(this.policyType.name, instance.id).subscribe(
+ (refreshedJson: any) => {
+ instance.json = JSON.stringify(refreshedJson);
+ this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+ },
+ (httpError: HttpErrorResponse) => {
+ this.notificationService.error('Could not refresh instance ' + httpError.message);
+ this.dialog.open(PolicyInstanceDialogComponent, getPolicyDialogProperties(this.policyType, instance, this.darkMode));
+ }
+ );
+ }
+
+ hasInstances(): boolean {
+ return this.instanceDataSource.rowCount > 0;
+ }
+
+ toLocalTime(utcTime: string): string {
+ const date = new Date(utcTime);
+ const toutc = date.toUTCString();
+ return new Date(toutc + " UTC").toLocaleString();
+
+ }
+
+ deleteInstance(instance: PolicyInstance): void {
+ this.confirmDialogService
+ .openConfirmDialog('Are you sure you want to delete this policy instance?')
+ .afterClosed().subscribe(
+ (res: any) => {
+ if (res) {
+ this.policySvc.deletePolicy(this.policyType.name, instance.id)
+ .subscribe(
+ (response: HttpResponse) => {
+ switch (response.status) {
+ case 200:
+ this.notificationService.success('Delete succeeded!');
+ this.instanceDataSource.loadTable();
+ break;
+ default:
+ this.notificationService.warn('Delete failed.');
+ }
+ },
+ (error: HttpErrorResponse) => {
+ this.errorDialogService.displayError(error.message);
+ });
+ }
+ });
+ }
+
+
+
+}
diff --git a/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts b/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts
new file mode 100644
index 0000000..b82ee75
--- /dev/null
+++ b/webapp-frontend/src/app/policy-control/policy-instance.datasource.ts
@@ -0,0 +1,103 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyInstance } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+import { PolicyType } from '../interfaces/policy.types';
+
+export class PolicyInstanceDataSource extends DataSource {
+
+ private policyInstanceSubject = new BehaviorSubject([]);
+
+ private loadingSubject = new BehaviorSubject(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(
+ private policySvc: PolicyService,
+ public sort: MatSort,
+ private notificationService: NotificationService,
+ private policyType: PolicyType) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.policySvc.getPolicyInstances(this.policyType.name)
+ .pipe(
+ catchError((her: HttpErrorResponse) => {
+ this.notificationService.error('Failed to get policy instances: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe((instances: PolicyInstance[]) => {
+ this.rowCount = instances.length;
+ this.policyInstanceSubject.next(instances);
+ });
+ }
+
+ connect(): Observable {
+ const dataMutations = [
+ this.policyInstanceSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.policyInstanceSubject.getValue()]);
+ }));
+ }
+
+ disconnect(): void {
+ this.policyInstanceSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private getSortedData(data: PolicyInstance[]) {
+ if (!this.sort || !this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'instanceId': return compare(a.id, b.id, isAsc);
+ case 'ric': return compare(a.ric, b.ric, isAsc);
+ case 'service': return compare(a.service, b.service, isAsc);
+ case 'lastModified': return compare(a.lastModified, b.lastModified, isAsc)
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: string, b: string, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/webapp-frontend/src/app/policy-control/policy-type.datasource.ts b/webapp-frontend/src/app/policy-control/policy-type.datasource.ts
new file mode 100644
index 0000000..97d792e
--- /dev/null
+++ b/webapp-frontend/src/app/policy-control/policy-type.datasource.ts
@@ -0,0 +1,106 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { CollectionViewer, DataSource } from '@angular/cdk/collections';
+import { HttpErrorResponse } from '@angular/common/http';
+import { MatSort } from '@angular/material';
+import { Observable } from 'rxjs/Observable';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { merge } from 'rxjs';
+import { of } from 'rxjs/observable/of';
+import { catchError, finalize, map } from 'rxjs/operators';
+import { PolicyType } from '../interfaces/policy.types';
+import { PolicyService } from '../services/policy/policy.service';
+import { NotificationService } from '../services/ui/notification.service';
+
+export class PolicyTypeDataSource extends DataSource {
+
+ private policyTypeSubject = new BehaviorSubject([]);
+
+ private loadingSubject = new BehaviorSubject(false);
+
+ public loading$ = this.loadingSubject.asObservable();
+
+ public rowCount = 1; // hide footer during intial load
+
+ constructor(private policySvc: PolicyService,
+ private sort: MatSort,
+ private notificationService: NotificationService) {
+ super();
+ }
+
+ loadTable() {
+ this.loadingSubject.next(true);
+ this.policySvc.getPolicyTypes()
+ .pipe(
+ catchError((her: HttpErrorResponse) => {
+ this.notificationService.error('Failed to get policy types: ' + her.message);
+ return of([]);
+ }),
+ finalize(() => this.loadingSubject.next(false))
+ )
+ .subscribe((types: PolicyType[]) => {
+ this.rowCount = types.length;
+ for (let i = 0; i < types.length; i++) {
+ const policyType = types[i];
+ try {
+ policyType.schemaObject = JSON.parse(policyType.schema);
+ } catch (jsonError) {
+ console.error('Could not parse schema: ' + policyType.schema);
+ policyType.schemaObject = { description: 'Incorrect schema: ' + jsonError };
+ }
+ }
+ this.policyTypeSubject.next(types);
+ });
+ }
+
+ connect(collectionViewer: CollectionViewer): Observable {
+ const dataMutations = [
+ this.policyTypeSubject.asObservable(),
+ this.sort.sortChange
+ ];
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getSortedData([...this.policyTypeSubject.getValue()]);
+ }));
+ }
+
+ disconnect(collectionViewer: CollectionViewer): void {
+ this.policyTypeSubject.complete();
+ this.loadingSubject.complete();
+ }
+
+ private getSortedData(data: PolicyType[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'name': return compare(a.name, b.name, isAsc);
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: any, b: any, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts b/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts
new file mode 100644
index 0000000..6cc1be1
--- /dev/null
+++ b/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { CaasIngressService } from './caas-ingress.service';
+
+describe('CaasIngressService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: CaasIngressService = TestBed.get(CaasIngressService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts b/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts
new file mode 100644
index 0000000..9f3a03b
--- /dev/null
+++ b/webapp-frontend/src/app/services/caas-ingress/caas-ingress.service.ts
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { V1PodList } from '@kubernetes/client-node';
+import { Observable } from 'rxjs';
+
+/**
+* Services for calling the Controlpanel caas-ingress endpoints to get Kubernetes details.
+*/
+@Injectable({
+ providedIn: 'root'
+})
+export class CaasIngressService {
+
+ private basePath = 'api/caas-ingress';
+ private podsPath = 'pods';
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets list of pods
+ * @returns Observable that should yield a V1PodList
+ */
+ getPodList(cluster: string, namespace: string): Observable {
+ const url = this.buildPath('pods', 'cluster', cluster, 'namespace', namespace);
+ return this.httpClient.get(url);
+ }
+
+}
diff --git a/webapp-frontend/src/app/services/controlpanel/controlpanel.service.spec.ts b/webapp-frontend/src/app/services/controlpanel/controlpanel.service.spec.ts
new file mode 100644
index 0000000..d94a779
--- /dev/null
+++ b/webapp-frontend/src/app/services/controlpanel/controlpanel.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { ControlpanelService } from './controlpanel.service';
+
+describe('ControlpanelService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ControlpanelService = TestBed.get(ControlpanelService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/services/controlpanel/controlpanel.service.ts b/webapp-frontend/src/app/services/controlpanel/controlpanel.service.ts
new file mode 100644
index 0000000..7391a7a
--- /dev/null
+++ b/webapp-frontend/src/app/services/controlpanel/controlpanel.service.ts
@@ -0,0 +1,65 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { ControlpanelSuccessTransport, EcompUser } from '../../interfaces/controlpanel.types';
+
+@Injectable({
+ providedIn: 'root'
+})
+
+/**
+ * Services to query the Controlpanel's admin endpoints.
+ */
+export class ControlpanelService {
+
+ private basePath = 'api/admin/';
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Checks app health
+ * @returns Observable that should yield a ControlpanelSuccessTransport
+ */
+ getHealth(): Observable {
+ return this.httpClient.get(this.basePath + 'health');
+ }
+
+ /**
+ * Gets Controlpanel version details
+ * @returns Observable that should yield a ControlpanelSuccessTransport object
+ */
+ getVersion(): Observable {
+ return this.httpClient.get(this.basePath + 'version');
+ }
+
+ /**
+ * Gets Controlpanel users
+ * @returns Observable that should yield a EcompUser array
+ */
+ getUsers(): Observable {
+ return this.httpClient.get(this.basePath + 'user');
+ }
+
+}
diff --git a/webapp-frontend/src/app/services/policy/policy.service.spec.ts b/webapp-frontend/src/app/services/policy/policy.service.spec.ts
new file mode 100644
index 0000000..de1f4e6
--- /dev/null
+++ b/webapp-frontend/src/app/services/policy/policy.service.spec.ts
@@ -0,0 +1,31 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { TestBed } from '@angular/core/testing';
+
+import { PolicyService } from './policy.service';
+
+describe('PolicyService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: PolicyService = TestBed.get(PolicyService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/services/policy/policy.service.ts b/webapp-frontend/src/app/services/policy/policy.service.ts
new file mode 100644
index 0000000..c5ee579
--- /dev/null
+++ b/webapp-frontend/src/app/services/policy/policy.service.ts
@@ -0,0 +1,110 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { PolicyType, PolicyInstance, PolicyInstanceAck } from '../../interfaces/policy.types';
+import { ControlpanelSuccessTransport } from '../../interfaces/controlpanel.types';
+
+/**
+ * Services for calling the policy endpoints.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class PolicyService {
+
+ private basePath = 'api/policy';
+ private policyTypePath = 'policytypes';
+ private policyPath = 'policies';
+
+ private buildPath(...args: any[]) {
+ let result = this.basePath;
+ args.forEach(part => {
+ result = result + '/' + part;
+ });
+ return result;
+ }
+
+ constructor(private httpClient: HttpClient) {
+ // injects to variable httpClient
+ }
+
+ /**
+ * Gets version details
+ * @returns Observable that should yield a String
+ */
+ getVersion(): Observable {
+ const url = this.buildPath('version');
+ return this.httpClient.get(url).pipe(
+ // Extract the string here
+ map(res => res['data'])
+ );
+ }
+
+ getPolicyTypes(): Observable {
+ const url = this.buildPath(this.policyTypePath);
+ return this.httpClient.get(url);
+ }
+
+ getPolicyInstances(policyTypeId: string): Observable {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath);
+ return this.httpClient.get(url);
+ }
+
+ /**
+ * Gets policy parameters.
+ * @returns Observable that should yield a policy instance
+ */
+ getPolicy(policyTypeId: string, policyInstanceId: string): Observable {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+ return this.httpClient.get(url);
+ }
+
+ /**
+ * Creates or replaces policy instance.
+ * @param policyTypeId ID of the policy type that the instance will have
+ * @param policyInstanceId ID of the instance
+ * @param policyJson Json with the policy content
+ * @returns Observable that should yield a response code, no data
+ */
+ putPolicy(policyTypeId: string, policyInstanceId: string, policyJson: string, ric: string): Observable {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId) + "?ric=" + ric;
+ return this.httpClient.put(url, policyJson, { observe: 'response' });
+ }
+
+ /**
+ * Deletes a policy instance.
+ * @param policyTypeId
+ * @returns Observable that should yield a response code, no data
+ */
+ deletePolicy(policyTypeId: string, policyInstanceId: string): Observable {
+ const url = this.buildPath(this.policyTypePath, policyTypeId, this.policyPath, policyInstanceId);
+ return this.httpClient.delete(url, { observe: 'response' });
+ }
+
+
+ getRics(policyTypeId: string): Observable {
+ const url = this.buildPath('rics') + '?policyType=' + policyTypeId;
+ return this.httpClient.get(url);
+ }
+}
diff --git a/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts b/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts
new file mode 100644
index 0000000..c8992eb
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/confirm-dialog.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogService } from './confirm-dialog.service';
+
+describe('ConfirmDialogService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: ConfirmDialogService = TestBed.get(ConfirmDialogService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts b/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts
new file mode 100644
index 0000000..184d64f
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/confirm-dialog.service.ts
@@ -0,0 +1,55 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import { ConfirmDialogComponent } from './../../ui/confirm-dialog/confirm-dialog.component';
+import { UiService } from './ui.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ConfirmDialogService {
+
+ darkMode: boolean;
+ panelClass = '';
+
+ constructor(private dialog: MatDialog,
+ private ui: UiService) { }
+
+ openConfirmDialog(msg: string) {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ if (this.darkMode) {
+ this.panelClass = 'dark-theme';
+ } else {
+ this.panelClass = '';
+ }
+ return this.dialog.open(ConfirmDialogComponent, {
+ panelClass: this.panelClass,
+ width: '480px',
+ position: { top: '100px' },
+ data: {
+ message: msg
+ }
+ });
+ }
+}
diff --git a/webapp-frontend/src/app/services/ui/error-dialog.service.ts b/webapp-frontend/src/app/services/ui/error-dialog.service.ts
new file mode 100644
index 0000000..82faeac
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/error-dialog.service.ts
@@ -0,0 +1,52 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { ErrorDialogComponent } from '../../ui/error-dialog/error-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
+import { Injectable } from '@angular/core';
+import { UiService } from './ui.service';
+
+@Injectable()
+export class ErrorDialogService {
+
+ darkMode: boolean;
+ panelClass = '';
+ public errorMessage = '';
+
+ constructor(private dialog: MatDialog,
+ private ui: UiService) { }
+
+ public displayError(error: string) {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ if (this.darkMode) {
+ this.panelClass = 'dark-theme';
+ } else {
+ this.panelClass = '';
+ }
+ return this.dialog.open(ErrorDialogComponent, {
+ panelClass: this.panelClass,
+ width: '400px',
+ position: { top: '100px' },
+ disableClose: true,
+ data: { 'errorMessage': error }
+ });
+ }
+}
diff --git a/webapp-frontend/src/app/services/ui/notification.service.spec.ts b/webapp-frontend/src/app/services/ui/notification.service.spec.ts
new file mode 100644
index 0000000..61110c9
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/notification.service.spec.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { TestBed } from '@angular/core/testing';
+
+import { NotificationService } from './notification.service';
+
+describe('NotificationService', () => {
+ beforeEach(() => TestBed.configureTestingModule({}));
+
+ it('should be created', () => {
+ const service: NotificationService = TestBed.get(NotificationService);
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/services/ui/notification.service.ts b/webapp-frontend/src/app/services/ui/notification.service.ts
new file mode 100644
index 0000000..09baa38
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/notification.service.ts
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Injectable } from '@angular/core';
+import { ToastrService } from 'ngx-toastr';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NotificationService {
+
+ constructor(public toastr: ToastrService) { }
+
+ successConfig = {
+ timeOut: 10000,
+ closeButton: true
+ };
+
+ warningConfig = {
+ disableTimeOut: true,
+ closeButton: true
+ };
+
+ errorConfig = {
+ disableTimeOut: true,
+ closeButton: true
+ };
+
+ success(msg: string) {
+ this.toastr.success(msg, '', this.successConfig);
+ }
+
+ warn(msg: string) {
+ this.toastr.warning(msg, '', this.warningConfig);
+ }
+
+ error(msg: string) {
+ this.toastr.error(msg, '', this.errorConfig);
+ }
+
+}
diff --git a/webapp-frontend/src/app/services/ui/ui.service.spec.ts b/webapp-frontend/src/app/services/ui/ui.service.spec.ts
new file mode 100644
index 0000000..492b4aa
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/ui.service.spec.ts
@@ -0,0 +1,34 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import {inject, TestBed} from '@angular/core/testing';
+
+import {UiService} from './ui.service';
+
+describe('UiService', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [UiService]
+ });
+ });
+
+ it('should be created', inject([UiService], (service: UiService) => {
+ expect(service).toBeTruthy();
+ }));
+});
diff --git a/webapp-frontend/src/app/services/ui/ui.service.ts b/webapp-frontend/src/app/services/ui/ui.service.ts
new file mode 100644
index 0000000..30f92d0
--- /dev/null
+++ b/webapp-frontend/src/app/services/ui/ui.service.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+
+@Injectable()
+export class UiService {
+
+ darkModeState: BehaviorSubject;
+
+ constructor() {
+ // TODO: if the user is signed in get the default value from Firebase
+ this.darkModeState = new BehaviorSubject(true);
+ }
+}
diff --git a/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html
new file mode 100644
index 0000000..c4d475a
--- /dev/null
+++ b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ {{data.message}}
+
+
\ No newline at end of file
diff --git a/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts
new file mode 100644
index 0000000..266b581
--- /dev/null
+++ b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.spec.ts
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ConfirmDialogComponent } from './confirm-dialog.component';
+
+describe('ConfirmDialogComponent', () => {
+ let component: ConfirmDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ConfirmDialogComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ConfirmDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts
new file mode 100644
index 0000000..1ad54b9
--- /dev/null
+++ b/webapp-frontend/src/app/ui/confirm-dialog/confirm-dialog.component.ts
@@ -0,0 +1,39 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+import { Component, OnInit, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+ selector: 'rd-confirm-dialog',
+ templateUrl: './confirm-dialog.component.html',
+})
+export class ConfirmDialogComponent implements OnInit {
+
+ constructor(@Inject(MAT_DIALOG_DATA) public data,
+ public dialogRef: MatDialogRef) { }
+
+ ngOnInit() {
+ }
+
+ closeDialog() {
+ this.dialogRef.close(false);
+ }
+}
diff --git a/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html
new file mode 100644
index 0000000..2b44f0f
--- /dev/null
+++ b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.html
@@ -0,0 +1,27 @@
+
+
+
+
{{data.errorMessage}}
+
+
+
+
+
diff --git a/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss
new file mode 100644
index 0000000..93c0e83
--- /dev/null
+++ b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.scss
@@ -0,0 +1,24 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+ .error_message_text {
+ overflow-y: overlay;
+ max-height: 150px;
+ }
diff --git a/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts
new file mode 100644
index 0000000..04a0684
--- /dev/null
+++ b/webapp-frontend/src/app/ui/error-dialog/error-dialog.component.ts
@@ -0,0 +1,41 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, Inject } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+
+export interface ErrorData {
+ errorMessage: string;
+}
+
+@Component({
+ selector: 'rd-error-dialog',
+ templateUrl: './error-dialog.component.html',
+ styleUrls: ['./error-dialog.component.scss']
+})
+
+export class ErrorDialogComponent {
+
+ constructor(private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: ErrorData) { }
+
+ public closeDialog = () => {
+ this.dialogRef.close();
+ }
+}
diff --git a/webapp-frontend/src/app/ui/policy-card/policy-card.component.html b/webapp-frontend/src/app/ui/policy-card/policy-card.component.html
new file mode 100644
index 0000000..2c82628
--- /dev/null
+++ b/webapp-frontend/src/app/ui/policy-card/policy-card.component.html
@@ -0,0 +1,31 @@
+
+
+
+ Policy Control
+
+ buildConfig
+
+
+
+
+
+
+
diff --git a/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss b/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss
new file mode 100644
index 0000000..30a3dfa
--- /dev/null
+++ b/webapp-frontend/src/app/ui/policy-card/policy-card.component.scss
@@ -0,0 +1,58 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+.policy__card {
+ background-color: #ffffff;
+ box-shadow: 0 0 2rem rgba(0, 0, 255, 0.1);
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr 1fr;
+ padding: 2rem;
+ margin: 2rem;
+ width: 19rem;
+ height: 30rem;
+ justify-items: center;
+ cursor: pointer;
+ border-radius: 1.75rem;
+ animation: fadein 1.25s ease-in-out 0ms 1;
+ color: #443282;
+}
+
+.add__card-dark {
+ background: linear-gradient(to bottom, rgb(78, 78, 129), rgb(45, 44, 61));
+ color: white;
+}
+
+.card__title {
+ text-transform: uppercase;
+ letter-spacing: 0.1rem;
+}
+
+.body__container {
+ align-self: end;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-flow: column;
+}
+
+.add__icon {
+ width: 10rem;
+ margin-bottom: 1.15rem;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts b/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts
new file mode 100644
index 0000000..eb678a9
--- /dev/null
+++ b/webapp-frontend/src/app/ui/policy-card/policy-card.component.spec.ts
@@ -0,0 +1,44 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {PolicyCardComponent} from './policy-card.component';
+
+describe('PolicyCardComponent', () => {
+ let component: PolicyCardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [PolicyCardComponent]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PolicyCardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts b/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts
new file mode 100644
index 0000000..902e260
--- /dev/null
+++ b/webapp-frontend/src/app/ui/policy-card/policy-card.component.ts
@@ -0,0 +1,46 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+import { UiService } from '../../services/ui/ui.service';
+
+@Component({
+ selector: 'rd-policy-card',
+ templateUrl: './policy-card.component.html',
+ styleUrls: ['./policy-card.component.scss']
+})
+export class PolicyCardComponent implements OnInit, OnDestroy {
+ darkMode: boolean;
+
+ constructor(public router: Router, private ui: UiService) { }
+
+ ngOnInit() {
+ this.ui.darkModeState.subscribe((isDark) => {
+ this.darkMode = isDark;
+ });
+ }
+
+ ngOnDestroy() { }
+
+ openDetails() {
+ this.router.navigateByUrl('../../policy');
+ }
+
+}
diff --git a/webapp-frontend/src/assets/ORANlogo.png b/webapp-frontend/src/assets/ORANlogo.png
new file mode 100644
index 0000000..4c3dfb1
Binary files /dev/null and b/webapp-frontend/src/assets/ORANlogo.png differ
diff --git a/webapp-frontend/src/assets/at_t.png b/webapp-frontend/src/assets/at_t.png
new file mode 100644
index 0000000..3cced1d
Binary files /dev/null and b/webapp-frontend/src/assets/at_t.png differ
diff --git a/webapp-frontend/src/assets/intelligence.png b/webapp-frontend/src/assets/intelligence.png
new file mode 100644
index 0000000..c40693e
Binary files /dev/null and b/webapp-frontend/src/assets/intelligence.png differ
diff --git a/webapp-frontend/src/assets/latency.png b/webapp-frontend/src/assets/latency.png
new file mode 100644
index 0000000..874d11b
Binary files /dev/null and b/webapp-frontend/src/assets/latency.png differ
diff --git a/webapp-frontend/src/assets/mockdata/config.json b/webapp-frontend/src/assets/mockdata/config.json
new file mode 100644
index 0000000..255b3fe
--- /dev/null
+++ b/webapp-frontend/src/assets/mockdata/config.json
@@ -0,0 +1,872 @@
+[
+ {
+ "metadata": {
+ "name": "Automatic Neighbor Relation",
+ "configName": "anr-appconfig",
+ "namespace": "ricxapp"
+ },
+ "descriptor": {
+ "$id": "http://example.com/root.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "definitions": {},
+ "properties": {
+ "controls": {
+ "$id": "#/properties/controls",
+ "properties": {
+ "active": {
+ "$id": "#/properties/controls/properties/active",
+ "default": false,
+ "examples": [
+ true
+ ],
+ "title": "The Active Schema",
+ "type": "boolean"
+ },
+ "interfaceId": {
+ "$id": "#/properties/controls/properties/interfaceId",
+ "properties": {
+ "globalENBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+ "properties": {
+ "bits": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/bits",
+ "default": 0,
+ "examples": [
+ 28
+ ],
+ "title": "The Bits Schema",
+ "maximum": 1024,
+ "type": "integer"
+ },
+ "id": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/id",
+ "default": 0,
+ "examples": [
+ 202251
+ ],
+ "title": "The Id Schema",
+ "type": "integer"
+ },
+ "plmnid": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnid",
+ "default": "",
+ "examples": [
+ "310150"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Plmnid Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "plmnid",
+ "id",
+ "bits"
+ ],
+ "title": "The Globalenbid Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "globalENBId"
+ ],
+ "title": "The Interfaceid Schema",
+ "type": "object"
+ },
+ "subscription": {
+ "$id": "#/properties/controls/properties/subscription",
+ "properties": {
+ "retries": {
+ "$id": "#/properties/controls/properties/subscription/properties/retries",
+ "default": 0,
+ "examples": [
+ 1
+ ],
+ "title": "The Retries Schema",
+ "type": "integer"
+ },
+ "retryto": {
+ "$id": "#/properties/controls/properties/subscription/properties/retryto",
+ "default": 0,
+ "examples": [
+ 2
+ ],
+ "title": "The Retryto Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "retries",
+ "retryto"
+ ],
+ "title": "The Subscription Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "active",
+ "subscription",
+ "interfaceId"
+ ],
+ "title": "The Controls Schema",
+ "type": "object"
+ },
+ "db": {
+ "$id": "#/properties/db",
+ "properties": {
+ "host": {
+ "$id": "#/properties/db/properties/host",
+ "default": "",
+ "examples": [
+ "localhost"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Host Schema",
+ "type": "string"
+ },
+ "namespaces": {
+ "$id": "#/properties/db/properties/namespaces",
+ "items": {
+ "$id": "#/properties/db/properties/namespaces/items",
+ "default": "",
+ "examples": [
+ "sdl",
+ "rnib"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Namespaces Schema",
+ "type": "array"
+ },
+ "port": {
+ "$id": "#/properties/db/properties/port",
+ "default": 0,
+ "examples": [
+ 6379
+ ],
+ "title": "The Port Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "host",
+ "port",
+ "namespaces"
+ ],
+ "title": "The Db Schema",
+ "type": "object"
+ },
+ "local": {
+ "$id": "#/properties/local",
+ "properties": {
+ "host": {
+ "$id": "#/properties/local/properties/host",
+ "default": "",
+ "examples": [
+ ":8080"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Host Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "host"
+ ],
+ "title": "The Local Schema",
+ "type": "object"
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "default": 0,
+ "examples": [
+ 3
+ ],
+ "title": "The Level Schema",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "level"
+ ],
+ "title": "The Logger Schema",
+ "type": "object"
+ },
+ "metrics": {
+ "$id": "#/properties/metrics",
+ "items": {
+ "$id": "#/properties/metrics/items",
+ "properties": {
+ "description": {
+ "$id": "#/properties/metrics/items/properties/description",
+ "default": "",
+ "examples": [
+ "The total number of UE context creation events"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Description Schema",
+ "type": "string"
+ },
+ "enabled": {
+ "$id": "#/properties/metrics/items/properties/enabled",
+ "default": false,
+ "examples": [
+ true
+ ],
+ "title": "The Enabled Schema",
+ "type": "boolean"
+ },
+ "name": {
+ "$id": "#/properties/metrics/items/properties/name",
+ "default": "",
+ "examples": [
+ "UEContextCreated"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Name Schema",
+ "type": "string"
+ },
+ "type": {
+ "$id": "#/properties/metrics/items/properties/type",
+ "default": "",
+ "examples": [
+ "counter"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Type Schema",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "type",
+ "enabled",
+ "description"
+ ],
+ "title": "The Items Schema",
+ "type": "object"
+ },
+ "title": "The Metrics Schema",
+ "type": "array"
+ },
+ "rmr": {
+ "$id": "#/properties/rmr",
+ "properties": {
+ "maxSize": {
+ "$id": "#/properties/rmr/properties/maxSize",
+ "default": 0,
+ "examples": [
+ 2072
+ ],
+ "title": "The Maxsize Schema",
+ "type": "integer"
+ },
+ "numWorkers": {
+ "$id": "#/properties/rmr/properties/numWorkers",
+ "default": 0,
+ "examples": [
+ 1
+ ],
+ "title": "The Numworkers Schema",
+ "type": "integer"
+ },
+ "protPort": {
+ "$id": "#/properties/rmr/properties/protPort",
+ "default": "",
+ "examples": [
+ "tcp:4560"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Protport Schema",
+ "type": "string"
+ },
+ "rxMessages": {
+ "$id": "#/properties/rmr/properties/rxMessages",
+ "items": {
+ "$id": "#/properties/rmr/properties/rxMessages/items",
+ "default": "",
+ "examples": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Rxmessages Schema",
+ "type": "array"
+ },
+ "txMessages": {
+ "$id": "#/properties/rmr/properties/txMessages",
+ "items": {
+ "$id": "#/properties/rmr/properties/txMessages/items",
+ "default": "",
+ "examples": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ],
+ "pattern": "^(.*)$",
+ "title": "The Items Schema",
+ "type": "string"
+ },
+ "title": "The Txmessages Schema",
+ "type": "array"
+ }
+ },
+ "required": [
+ "protPort",
+ "maxSize",
+ "numWorkers",
+ "txMessages",
+ "rxMessages"
+ ],
+ "title": "The Rmr Schema",
+ "type": "object"
+ }
+ },
+ "required": [
+ "local",
+ "logger",
+ "rmr",
+ "db",
+ "controls",
+ "metrics"
+ ],
+ "title": "The Root Schema",
+ "type": "object"
+ },
+ "config": {
+ "controls": {
+ "active": true,
+ "interfaceId": {
+ "globalENBId": {
+ "bits": 28,
+ "id": 202251,
+ "plmnid": "310150"
+ }
+ },
+ "subscription": {
+ "retries": 1,
+ "retryto": 2
+ }
+ },
+ "db": {
+ "host": "localhost",
+ "namespaces": [
+ "sdl",
+ "rnib"
+ ],
+ "port": 6379
+ },
+ "local": {
+ "host": ":8080"
+ },
+ "logger": {
+ "level": 3
+ },
+ "metrics": [
+ {
+ "description": "The total number of UE context creation events",
+ "enabled": true,
+ "name": "UEContextCreated",
+ "type": "counter"
+ },
+ {
+ "description": "The total number of UE context release events",
+ "enabled": true,
+ "name": "UEContextReleased",
+ "type": "counter"
+ }
+ ],
+ "rmr": {
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "protPort": "tcp:4560",
+ "rxMessages": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "txMessages": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ]
+ }
+ },
+ "layout": [
+ {
+ "key": "controls.active",
+ "title": "Active"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId",
+ "title": "Global ENB Id"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.interfaceId.globalENBId.plmnid",
+ "title": "Plmn Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.id",
+ "title": "Id"
+
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.bits",
+ "title": "Bits"
+ }
+ ]
+ },
+ {
+ "key": "controls.subscription",
+ "title": "Subscription"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.subscription.retries",
+ "title": "Retries"
+ },
+ {
+ "key": "controls.subscription.retryto",
+ "title": "Retry to"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "metadata": {
+ "name": "UE Event Collector",
+ "configName": "UEEC-appconfig",
+ "namespace": "ricxapp"
+ },
+ "descriptor": {
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://example.com/root.json",
+ "type": "object",
+ "title": "The Root Schema",
+ "required": [
+ "local",
+ "logger",
+ "rmr",
+ "db",
+ "controls",
+ "metrics"
+ ],
+ "properties": {
+ "local": {
+ "$id": "#/properties/local",
+ "type": "object",
+ "title": "The Local Schema",
+ "required": [
+ "host"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/local/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ ":8080"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ },
+ "logger": {
+ "$id": "#/properties/logger",
+ "type": "object",
+ "title": "The Logger Schema",
+ "required": [
+ "level"
+ ],
+ "properties": {
+ "level": {
+ "$id": "#/properties/logger/properties/level",
+ "type": "integer",
+ "title": "The Level Schema",
+ "default": 0,
+ "examples": [
+ 3
+ ]
+ }
+ }
+ },
+ "rmr": {
+ "$id": "#/properties/rmr",
+ "type": "object",
+ "title": "The Rmr Schema",
+ "required": [
+ "protPort",
+ "maxSize",
+ "numWorkers",
+ "txMessages",
+ "rxMessages"
+ ],
+ "properties": {
+ "protPort": {
+ "$id": "#/properties/rmr/properties/protPort",
+ "type": "string",
+ "title": "The Protport Schema",
+ "default": "",
+ "examples": [
+ "tcp:4560"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "maxSize": {
+ "$id": "#/properties/rmr/properties/maxSize",
+ "type": "integer",
+ "title": "The Maxsize Schema",
+ "default": 0,
+ "examples": [
+ 2072
+ ]
+ },
+ "numWorkers": {
+ "$id": "#/properties/rmr/properties/numWorkers",
+ "type": "integer",
+ "title": "The Numworkers Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "txMessages": {
+ "$id": "#/properties/rmr/properties/txMessages",
+ "type": "array",
+ "title": "The Txmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/txMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_REQ",
+ "RIC_SUB_DEL_REQ"
+ ],
+ "pattern": "^(.*)$"
+ }
+ },
+ "rxMessages": {
+ "$id": "#/properties/rmr/properties/rxMessages",
+ "type": "array",
+ "title": "The Rxmessages Schema",
+ "items": {
+ "$id": "#/properties/rmr/properties/rxMessages/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "RIC_SUB_RESP",
+ "RIC_SUB_FAILURE",
+ "RIC_SUB_DEL_RESP",
+ "RIC_SUB_DEL_FAILURE",
+ "RIC_INDICATION"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "db": {
+ "$id": "#/properties/db",
+ "type": "object",
+ "title": "The Db Schema",
+ "required": [
+ "host",
+ "port",
+ "namespaces"
+ ],
+ "properties": {
+ "host": {
+ "$id": "#/properties/db/properties/host",
+ "type": "string",
+ "title": "The Host Schema",
+ "default": "",
+ "examples": [
+ "localhost"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "port": {
+ "$id": "#/properties/db/properties/port",
+ "type": "integer",
+ "title": "The Port Schema",
+ "default": 0,
+ "examples": [
+ 6379
+ ]
+ },
+ "namespaces": {
+ "$id": "#/properties/db/properties/namespaces",
+ "type": "array",
+ "title": "The Namespaces Schema",
+ "items": {
+ "$id": "#/properties/db/properties/namespaces/items",
+ "type": "string",
+ "title": "The Items Schema",
+ "default": "",
+ "examples": [
+ "sdl",
+ "rnib"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ },
+ "controls": {
+ "$id": "#/properties/controls",
+ "type": "object",
+ "title": "The Controls Schema",
+ "required": [
+ "active",
+ "requestorId",
+ "ranFunctionId",
+ "ricActionId",
+ "interfaceId"
+ ],
+ "properties": {
+ "active": {
+ "$id": "#/properties/controls/properties/active",
+ "type": "boolean",
+ "title": "The Active Schema",
+ "default": false,
+ "examples": [
+ true
+ ]
+ },
+ "requestorId": {
+ "$id": "#/properties/controls/properties/requestorId",
+ "type": "integer",
+ "title": "The Requestorid Schema",
+ "default": 0,
+ "examples": [
+ 66
+ ]
+ },
+ "ranFunctionId": {
+ "$id": "#/properties/controls/properties/ranFunctionId",
+ "type": "integer",
+ "title": "The Ranfunctionid Schema",
+ "default": 0,
+ "examples": [
+ 1
+ ]
+ },
+ "ricActionId": {
+ "$id": "#/properties/controls/properties/ricActionId",
+ "type": "integer",
+ "title": "The Ricactionid Schema",
+ "default": 0,
+ "examples": [
+ 0
+ ]
+ },
+ "interfaceId": {
+ "$id": "#/properties/controls/properties/interfaceId",
+ "type": "object",
+ "title": "The Interfaceid Schema",
+ "required": [
+ "globalENBId"
+ ],
+ "properties": {
+ "globalENBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId",
+ "type": "object",
+ "title": "The Globalenbid Schema",
+ "required": [
+ "plmnId",
+ "eNBId"
+ ],
+ "properties": {
+ "plmnId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/plmnId",
+ "type": "string",
+ "title": "The Plmnid Schema",
+ "default": "",
+ "examples": [
+ "43962"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "eNBId": {
+ "$id": "#/properties/controls/properties/interfaceId/properties/globalENBId/properties/eNBId",
+ "type": "string",
+ "title": "The Enbid Schema",
+ "default": "",
+ "examples": [
+ "43962"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "metrics": {
+ "$id": "#/properties/metrics",
+ "type": "array",
+ "title": "The Metrics Schema",
+ "items": {
+ "$id": "#/properties/metrics/items",
+ "type": "object",
+ "title": "The Items Schema",
+ "required": [
+ "name",
+ "type",
+ "enabled",
+ "description"
+ ],
+ "properties": {
+ "name": {
+ "$id": "#/properties/metrics/items/properties/name",
+ "type": "string",
+ "title": "The Name Schema",
+ "default": "",
+ "examples": [
+ "UEContextCreated"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "type": {
+ "$id": "#/properties/metrics/items/properties/type",
+ "type": "string",
+ "title": "The Type Schema",
+ "default": "",
+ "examples": [
+ "counter"
+ ],
+ "pattern": "^(.*)$"
+ },
+ "enabled": {
+ "$id": "#/properties/metrics/items/properties/enabled",
+ "type": "boolean",
+ "title": "The Enabled Schema",
+ "default": false,
+ "examples": [
+ true
+ ]
+ },
+ "description": {
+ "$id": "#/properties/metrics/items/properties/description",
+ "type": "string",
+ "title": "The Description Schema",
+ "default": "",
+ "examples": [
+ "The total number of UE context creation events"
+ ],
+ "pattern": "^(.*)$"
+ }
+ }
+ }
+ }
+ }
+ },
+ "config": {
+ "local": {
+ "host": ":8080"
+ },
+ "logger": {
+ "level": 3
+ },
+ "rmr": {
+ "protPort": "tcp:4560",
+ "maxSize": 2072,
+ "numWorkers": 1,
+ "txMessages": [ "RIC_SUB_REQ", "RIC_SUB_DEL_REQ" ],
+ "rxMessages": [ "RIC_SUB_RESP", "RIC_SUB_FAILURE", "RIC_SUB_DEL_RESP", "RIC_SUB_DEL_FAILURE", "RIC_INDICATION" ]
+ },
+ "db": {
+ "host": "localhost",
+ "port": 6379,
+ "namespaces": [ "sdl", "rnib" ]
+ },
+ "controls": {
+ "active": true,
+ "requestorId": 66,
+ "ranFunctionId": 1,
+ "ricActionId": 0,
+ "interfaceId": {
+ "globalENBId": {
+ "plmnId": "43962",
+ "eNBId": "43962"
+ }
+ }
+ },
+ "metrics": [
+ {
+ "name": "UEContextCreated",
+ "type": "counter",
+ "enabled": true,
+ "description": "The total number of UE context creation events"
+ },
+ {
+ "name": "UEContextReleased",
+ "type": "counter",
+ "enabled": true,
+ "description": "The total number of UE context release events"
+ }
+ ]
+ },
+ "layout": [
+ {
+ "key": "controls.active",
+ "title": "Active"
+ },
+ {
+ "key": "controls.requestorId",
+ "title": "Requestor Id"
+ },
+ {
+ "key": "controls.ranFunctionId",
+ "title": "RAN Function Id"
+ },
+ {
+ "key": "controls.ricActionId",
+ "title": "RIC Action Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId",
+ "title": "Global ENB Id"
+ },
+ {
+ "type": "flex",
+ "flex-flow": "row wrap",
+ "items": [
+ {
+ "key": "controls.interfaceId.globalENBId.plmnId",
+ "title": "Plmn Id"
+ },
+ {
+ "key": "controls.interfaceId.globalENBId.eNBId",
+ "title": "ENB Id"
+
+ }
+ ]
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/webapp-frontend/src/assets/mockdata/db.json b/webapp-frontend/src/assets/mockdata/db.json
new file mode 100644
index 0000000..31bd473
--- /dev/null
+++ b/webapp-frontend/src/assets/mockdata/db.json
@@ -0,0 +1,36 @@
+{
+ "config": [
+ {
+ "id": "jsonURL",
+ "value": "http://localhost:3000"
+ },
+ {
+ "id": "host",
+ "value": "http://localhost:3000"
+ },
+ {
+ "id": "metricspath",
+ "value": "/a1ric/metrics"
+ },
+ {
+ "id": "delaypath",
+ "value": "/a1ric/delay"
+ },
+ {
+ "id": "loadpath",
+ "value": "/a1ric/load"
+ }
+ ],
+ "metrics": {
+ "latency": 11,
+ "load": 100,
+ "ricload": 100,
+ "time": 123
+ },
+ "delay": {
+ "delay": 64877
+ },
+ "load": {
+ "load": 1
+ }
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/assets/mockdata/routes.json b/webapp-frontend/src/assets/mockdata/routes.json
new file mode 100644
index 0000000..0745958
--- /dev/null
+++ b/webapp-frontend/src/assets/mockdata/routes.json
@@ -0,0 +1,4 @@
+{
+ "/a1ric/*": "/$1",
+ "/:resource/:id/show": "/:resource/:id"
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/assets/oran-logo.png b/webapp-frontend/src/assets/oran-logo.png
new file mode 100644
index 0000000..c3b6ce5
Binary files /dev/null and b/webapp-frontend/src/assets/oran-logo.png differ
diff --git a/webapp-frontend/src/assets/policy.png b/webapp-frontend/src/assets/policy.png
new file mode 100644
index 0000000..11df15d
Binary files /dev/null and b/webapp-frontend/src/assets/policy.png differ
diff --git a/webapp-frontend/src/assets/profile_default.png b/webapp-frontend/src/assets/profile_default.png
new file mode 100644
index 0000000..2b90bf0
Binary files /dev/null and b/webapp-frontend/src/assets/profile_default.png differ
diff --git a/webapp-frontend/src/assets/xAppControl.png b/webapp-frontend/src/assets/xAppControl.png
new file mode 100644
index 0000000..9458a85
Binary files /dev/null and b/webapp-frontend/src/assets/xAppControl.png differ
diff --git a/webapp-frontend/src/environments/environment.prod.ts b/webapp-frontend/src/environments/environment.prod.ts
new file mode 100644
index 0000000..fb04b9c
--- /dev/null
+++ b/webapp-frontend/src/environments/environment.prod.ts
@@ -0,0 +1,22 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+export const environment = {
+ production: true
+};
diff --git a/webapp-frontend/src/environments/environment.ts b/webapp-frontend/src/environments/environment.ts
new file mode 100644
index 0000000..5aca243
--- /dev/null
+++ b/webapp-frontend/src/environments/environment.ts
@@ -0,0 +1,35 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+ production: false
+};
+
+/*
+ * For easier debugging in development mode, you can import the following file
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
+ *
+ * This import should be commented out in production mode because it will have a negative impact
+ * on performance if an error is thrown.
+ */
+// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
diff --git a/webapp-frontend/src/favicon.ico b/webapp-frontend/src/favicon.ico
new file mode 100644
index 0000000..00b0fd0
Binary files /dev/null and b/webapp-frontend/src/favicon.ico differ
diff --git a/webapp-frontend/src/index.html b/webapp-frontend/src/index.html
new file mode 100644
index 0000000..56b34c9
--- /dev/null
+++ b/webapp-frontend/src/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ NonRT RIC Controlpanel
+
+
+
+
+
+
+
+
diff --git a/webapp-frontend/src/karma.conf.js b/webapp-frontend/src/karma.conf.js
new file mode 100644
index 0000000..421a75d
--- /dev/null
+++ b/webapp-frontend/src/karma.conf.js
@@ -0,0 +1,50 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../coverage'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false
+ });
+};
diff --git a/webapp-frontend/src/main.ts b/webapp-frontend/src/main.ts
new file mode 100644
index 0000000..8aaa09a
--- /dev/null
+++ b/webapp-frontend/src/main.ts
@@ -0,0 +1,32 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * Modifications Copyright (C) 2020 Nordix Foundation
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { ControlpanelModule } from './app/controlpanel.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(ControlpanelModule)
+ .catch(err => console.error(err));
diff --git a/webapp-frontend/src/polyfills.ts b/webapp-frontend/src/polyfills.ts
new file mode 100644
index 0000000..56ca438
--- /dev/null
+++ b/webapp-frontend/src/polyfills.ts
@@ -0,0 +1,104 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/guide/browser-support
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
+ * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
+ */
+
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ */
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ * because those flags need to be set before `zone.js` being loaded, and webpack
+ * will put import in the top of bundle, so user need to create a separate file
+ * in this directory (for example: zone-flags.ts), and put the following flags
+ * into that file, and then add the following code before importing zone.js.
+ * import './zone-flags.ts';
+ *
+ * The flags allowed in zone-flags.ts are listed here.
+ *
+ * The following flags will work for all browsers.
+ *
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+ *
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ *
+ * (window as any).__Zone_enable_cross_context_check = true;
+ *
+ */
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
diff --git a/webapp-frontend/src/styles.scss b/webapp-frontend/src/styles.scss
new file mode 100644
index 0000000..c37fc42
--- /dev/null
+++ b/webapp-frontend/src/styles.scss
@@ -0,0 +1,45 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+@import '~bootstrap/dist/css/bootstrap.min.css';
+@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
+@import './styles/dark-theme';
+
+/* for sidenav to take a whole page */
+html,
+body {
+ margin: 0;
+ height: 100%;
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+/* notification */
+.confirm-dialog-container span.content-span {
+ padding: 35px 16px 20px 16px;
+ text-align: center;
+ font-size: 20px;
+}
+
+.rd-global-page-title {
+ margin-left: 0.5%;
+ color: #432c85;
+ font-size: 25px;
+ font-weight: 100;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/styles/dark-theme.scss b/webapp-frontend/src/styles/dark-theme.scss
new file mode 100644
index 0000000..6214081
--- /dev/null
+++ b/webapp-frontend/src/styles/dark-theme.scss
@@ -0,0 +1,37 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+
+@import '~@angular/material/theming';
+@include mat-core();
+
+.dark-theme {
+ color: white;
+ $dark-primary: mat-palette($mat-blue-grey);
+ $dark-accent: mat-palette($mat-amber, A200, A100, A400);
+ $dark-warn: mat-palette($mat-red);
+
+ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
+
+ @include angular-material-theme($dark-theme);
+}
+
+.dark-theme .rd-global-page-title {
+ color: white;
+}
\ No newline at end of file
diff --git a/webapp-frontend/src/test.ts b/webapp-frontend/src/test.ts
new file mode 100644
index 0000000..0256e63
--- /dev/null
+++ b/webapp-frontend/src/test.ts
@@ -0,0 +1,39 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2019 AT&T Intellectual Property
+ * %%
+ * Licensed 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.
+ * ========================LICENSE_END===================================
+ */
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/webapp-frontend/src/tsconfig.app.json b/webapp-frontend/src/tsconfig.app.json
new file mode 100644
index 0000000..190fd30
--- /dev/null
+++ b/webapp-frontend/src/tsconfig.app.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/webapp-frontend/src/tsconfig.spec.json b/webapp-frontend/src/tsconfig.spec.json
new file mode 100644
index 0000000..de77336
--- /dev/null
+++ b/webapp-frontend/src/tsconfig.spec.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/webapp-frontend/src/tslint.json b/webapp-frontend/src/tslint.json
new file mode 100644
index 0000000..30581c6
--- /dev/null
+++ b/webapp-frontend/src/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "rd",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "rd",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/webapp-frontend/tsconfig.json b/webapp-frontend/tsconfig.json
new file mode 100644
index 0000000..457bef4
--- /dev/null
+++ b/webapp-frontend/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "downlevelIteration": true,
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "importHelpers": true,
+ "target": "es5",
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ }
+}
diff --git a/webapp-frontend/tslint.json b/webapp-frontend/tslint.json
new file mode 100644
index 0000000..a919351
--- /dev/null
+++ b/webapp-frontend/tslint.json
@@ -0,0 +1,131 @@
+{
+ "rulesDirectory": [
+ "codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "deprecation": {
+ "severity": "warn"
+ },
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "debug",
+ "info",
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-redundant-jsdoc": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "no-output-on-prefix": true,
+ "no-inputs-metadata-property": true,
+ "no-outputs-metadata-property": true,
+ "no-host-metadata-property": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-lifecycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}