From 3c25e018d2169e982cf5a292dd20cbee2a117336 Mon Sep 17 00:00:00 2001 From: Parth Yadav Date: Mon, 24 Aug 2020 12:36:19 +0530 Subject: Init Resource Modelling Tool Tool for resource planning of VNFs. Signed-off-by: Parth Yadav Change-Id: I4729d665707162132b16765b3ba2d7a9d914d339 --- sdv/docker/sdvmodel/Dockerfile | 15 + .../sdvmodel/resource-estimation/requirements.txt | 2 + sdv/docker/sdvmodel/resource-estimation/server | 340 +++++++++++++++++++++ .../resource-estimation/template/report.html | 75 +++++ sdv/docker/sdvmodel/website/actions.js | 39 +++ .../sdvmodel/website/assets/plus-circle-solid.svg | 59 ++++ sdv/docker/sdvmodel/website/assets/server.svg | 123 ++++++++ .../sdvmodel/website/assets/trash-alt-regular.svg | 59 ++++ sdv/docker/sdvmodel/website/assets/vnf.svg | 83 +++++ sdv/docker/sdvmodel/website/controller.js | 34 +++ sdv/docker/sdvmodel/website/index.html | 124 ++++++++ sdv/docker/sdvmodel/website/mergeDeep.js | 46 +++ sdv/docker/sdvmodel/website/readFromHTML.js | 72 +++++ sdv/docker/sdvmodel/website/style/array.css | 55 ++++ sdv/docker/sdvmodel/website/style/index.css | 198 ++++++++++++ sdv/docker/sdvmodel/website/style/report.css | 90 ++++++ 16 files changed, 1414 insertions(+) create mode 100644 sdv/docker/sdvmodel/Dockerfile create mode 100644 sdv/docker/sdvmodel/resource-estimation/requirements.txt create mode 100755 sdv/docker/sdvmodel/resource-estimation/server create mode 100644 sdv/docker/sdvmodel/resource-estimation/template/report.html create mode 100644 sdv/docker/sdvmodel/website/actions.js create mode 100644 sdv/docker/sdvmodel/website/assets/plus-circle-solid.svg create mode 100644 sdv/docker/sdvmodel/website/assets/server.svg create mode 100644 sdv/docker/sdvmodel/website/assets/trash-alt-regular.svg create mode 100644 sdv/docker/sdvmodel/website/assets/vnf.svg create mode 100644 sdv/docker/sdvmodel/website/controller.js create mode 100644 sdv/docker/sdvmodel/website/index.html create mode 100644 sdv/docker/sdvmodel/website/mergeDeep.js create mode 100644 sdv/docker/sdvmodel/website/readFromHTML.js create mode 100644 sdv/docker/sdvmodel/website/style/array.css create mode 100644 sdv/docker/sdvmodel/website/style/index.css create mode 100644 sdv/docker/sdvmodel/website/style/report.css (limited to 'sdv') diff --git a/sdv/docker/sdvmodel/Dockerfile b/sdv/docker/sdvmodel/Dockerfile new file mode 100644 index 0000000..a71575e --- /dev/null +++ b/sdv/docker/sdvmodel/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.8-slim-buster + +MAINTAINER Parth Yadav + +WORKDIR /server/ + +COPY resource-estimation/requirements.txt /server/requirements.txt +RUN pip install -r requirements.txt + +COPY website/ /website/ +COPY resource-estimation/ /server/ + +RUN rm requirements.txt + +CMD [ "python", "/server/server" ] diff --git a/sdv/docker/sdvmodel/resource-estimation/requirements.txt b/sdv/docker/sdvmodel/resource-estimation/requirements.txt new file mode 100644 index 0000000..8fd63d9 --- /dev/null +++ b/sdv/docker/sdvmodel/resource-estimation/requirements.txt @@ -0,0 +1,2 @@ +tornado == 6.0.4 +Jinja2 == 2.11.2 diff --git a/sdv/docker/sdvmodel/resource-estimation/server b/sdv/docker/sdvmodel/resource-estimation/server new file mode 100755 index 0000000..bae9781 --- /dev/null +++ b/sdv/docker/sdvmodel/resource-estimation/server @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 + +# Copyright 2020 Spirent Communications, University Of Delhi. +# +# 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. + + +""" +Server +""" + + +import logging +import os +import sys +import copy +import json +from tornado.web import Application +from tornado.ioloop import IOLoop +import tornado.concurrent +import tornado.httpserver +import tornado.ioloop +import tornado.options +import tornado.web +import tornado.log +import jinja2 + +# SOF: 11124247: Massey101 and Corey Klein +class StreamToLogger(): + """ + file-like stream object that redirects writes to a logger instance. + """ + def __init__(self, logger, log_level=logging.INFO): + self.logger = logger + self.log_level = log_level + self.linebuf = '' + + def write(self, buf): + """ + write logs + """ + temp_linebuf = self.linebuf + buf + self.linebuf = '' + for line in temp_linebuf.splitlines(True): + # From the io.TextIOWrapper docs: + # On output, if newline is None, any '\n' characters written + # are translated to the system default line separator. + # By default sys.stdout.write() expects '\n' newlines and then + # translates them so this is still cross platform. + if line[-1] == '\n': + self.logger.log(self.log_level, line.rstrip()) + else: + self.linebuf += line + + def flush(self): + """ + flush the buffer + """ + if self.linebuf != '': + self.logger.log(self.log_level, self.linebuf.rstrip()) + self.linebuf = '' + + + +class Server(): + """ + Server + """ + # pylint: disable=too-many-instance-attributes + + def __init__(self, hw_profile): + self.vcpus = hw_profile['vcpus'] + self.numas = hw_profile['numas'] + self.numa_vcpu_map = [] + self.sriov_support = False + self.hosted_vnfs = [] + for count in range(int(self.numas)): + self.numa_vcpu_map.append(hw_profile['numa' + +str(count) + +'_cpus_4vnfs']) + self.create_numa_sriov_map(hw_profile['nics']) + self.zone = 'default' + self.cpu_isolation = hw_profile['cpu_isol_set'] + self.available_cpu_map = self.numa_vcpu_map + + def create_numa_sriov_map(self, nics): + """ + Search for all sriov and nonsriov numas + """ + self.sriov_numas = [] + self.nonsriov_numas = [] + for nic in nics: + if nic['type'] == 'sriov': + self.sriov_support = True + if nic['numa'] not in self.sriov_numas: + self.sriov_numas.append(int(nic['numa'])) + else: + if nic['numa'] not in self.sriov_numas: + self.nonsriov_numas.append(int(nic['numa'])) + + def dump_profile(self): + """ + Print Server Profile + """ + print("The number of vCPUs: %s" %self.vcpus) + print("Number of NUMA nodes on this server: %s" %self.numas) + print("vCPUs available for the application in each NUMA: %s" %self.numa_vcpu_map) + print("SRIOV Support? %s" %self.sriov_support) + print("The Zone this server belongs to: %s" %self.zone) + print("vCPUs Isolated: %s" %self.cpu_isolation) + print("Numa to which SRIOV Nics belogs to: %s" %str(self.sriov_numas)) + +class Deployment(): + """ + Model deployment + """ + def __init__(self, rack_count, hw_profile): + self.server_list = [] + self.total_servers = 0 + self.hw_profile = hw_profile + self.rack_count = rack_count + self.server_zones = {} + + def create_deployment(self, vnf_profiles): + """ + Understand zones. + """ + zones = [] + for vnf in vnf_profiles: + if vnf['availability_zone'] not in zones: + zones.append(vnf['availability_zone']) + # print(zones) + for zone in zones: + for vnf in vnf_profiles: + if zone == vnf['availability_zone']: + for count in range(int(vnf['num_of_vnfs'])): + self.deploy(vnf, count) + self.server_zones[zone] = copy.deepcopy(self.server_list) + self.total_servers += len(self.server_list) + self.server_list.clear() + + def deploy(self, vnf, suffix): + """ + Understand deployment + """ + # pylint: disable=too-many-branches + + deploy = False + # If no servers, just do the deployment there and apped it. + if len(self.server_list) == 0: + server = Server(self.hw_profile) + for cnt in range(len(server.available_cpu_map)): + if int(server.available_cpu_map[cnt]) >= int(vnf['vcpus']): + if not ((vnf['sriov_support'] == 'yes' and cnt not in server.sriov_numas) or\ + (vnf['sriov_support'] == 'no' and cnt not in server.nonsriov_numas)): + server.available_cpu_map[cnt] = str( + int(server.available_cpu_map[cnt]) - int(vnf['vcpus'])) + deploy = True + server.hosted_vnfs.append({'vnf':vnf['profile_name'] +\ + str(suffix), 'numa': cnt}) + self.server_list.append(server) + return + if not deploy: + print("The existing hardware profile is not Suitable") + sys.exit() + # Servers already exist. Check if any eserver can accommodate the vnf: + for server in self.server_list: + # Check if SRIOV support is required for VNF and server supports + # Check if cpus are available in any of the numas + for cnt in range(len(server.available_cpu_map)): + if int(server.available_cpu_map[cnt]) >= int(vnf['vcpus']): + if not ((vnf['sriov_support'] == 'yes' and cnt not in server.sriov_numas) or\ + (vnf['sriov_support'] == 'no' and cnt not in server.nonsriov_numas)): + server.available_cpu_map[cnt] = str(int(server.available_cpu_map[cnt]) + - int(vnf['vcpus'])) + deploy = True + server.hosted_vnfs.append({'vnf':vnf['profile_name'] +\ + str(suffix), 'numa': cnt}) + return + # We need to create new server, do deployment there and append it the list + if not deploy: + server = Server(self.hw_profile) + for cnt in range(len(server.available_cpu_map)): + if int(server.available_cpu_map[cnt]) >= int(vnf['vcpus']): + if not ((vnf['sriov_support'] == 'yes' and cnt not in server.sriov_numas) or\ + (vnf['sriov_support'] == 'no' and cnt not in server.nonsriov_numas)): + server.available_cpu_map[cnt] = str( + int(server.available_cpu_map[cnt]) - int(vnf['vcpus'])) + deploy = True + server.hosted_vnfs.append({'vnf':vnf['profile_name'] +\ + str(suffix), 'numa': cnt}) + self.server_list.append(server) + return + if not deploy: + print("The existing hardware profile is not Suitable") + sys.exit() + + def display_deployment(self): + """ + Print Deployment Report + """ + print("Number of servers used %d" % self.total_servers) + print("------------------------------------------------") + count = 0 + for zone, server_list in self.server_zones.items(): + print("SERVERS IN AVAILABILITY ZONE: %s" %(zone)) + print("------------------------------------------------") + for server in server_list: + print("Server ID: " + str(count)) + for vnf in server.hosted_vnfs: + print("VNF: " + vnf['vnf'] + " NUMA: " + str(vnf['numa'])) + count = count + 1 + print("------------------------------------------------") + + def get_deployment(self): + """ + Returns servers and zones + """ + return self.total_servers, self.server_zones + + +# pylint: disable=W0223 + +class Estimate(tornado.web.RequestHandler): + """ + Resource estimator + """ + # def set_default_headers(self): + # self.set_header('Content-Type', 'application/json') + + def post(self): + """ + Server Resource Modelling Report + """ + model = {} + config = self.get_argument('config', None) + data = json.loads(config) + + vnf_profiles = (data['vnf_profiles']) + hw_profile = (data['hardware_profile']) + model['vnf_profiles'] = vnf_profiles + print("--------- Resource Modelling Report ------------") + print("------------------------------------------------") + print("The VNFs:") + for profile in vnf_profiles: + print(profile['profile_name']) + print("------------------------------------------------") + print("The Compute-Server Profile:") + server = Server(hw_profile) + server.dump_profile() + model['server'] = hw_profile + print("------------------------------------------------") + deployment = Deployment(2, hw_profile) + deployment.create_deployment(vnf_profiles) + deployment.display_deployment() + count, placement = deployment.get_deployment() + model['deployment_count'] = count + model['deployment'] = placement + loader = jinja2.FileSystemLoader(searchpath="template/") + jenv = jinja2.Environment(loader=loader) + template = jenv.get_template('report.html') + htmlout = template.render(model=model) + + self.finish(htmlout) + + +class HomeHandler(tornado.web.RequestHandler): + """ + Handler for '/' endpoint + """ + def get(self): + """ + Server Home Page + """ + self.render('/website/index.html') + + + +def server_main_block(): + """ + Main Function + """ + + app = Application([('/validate', Estimate), + ('/', HomeHandler), + ('/(.*)', tornado.web.StaticFileHandler, {'path' : '/website'})]) + + # Cli Config + tornado.options.define("port", default=80, help="run on the given port", type=int) + tornado.options.parse_command_line() + + + # Server Config + http_server = tornado.httpserver.HTTPServer(app) + http_server.listen(tornado.options.options.port) + + est_file = "/tmp/estimate.txt" + if os.path.exists(est_file): + os.remove(est_file) + + # Logging + logging.basicConfig( + level=logging.DEBUG, + format='%(message)s', + filename=est_file, + filemode='a' + ) + + stdout_logger = logging.getLogger('STDOUT') + sys.stdout = StreamToLogger(stdout_logger, logging.INFO) + + stderr_logger = logging.getLogger('STDERR') + sys.stderr = StreamToLogger(stderr_logger, logging.ERROR) + + tornado.log.enable_pretty_logging() + + # Tornado's event loop handles it from here + print("# Servering.... \n [Ctrl + C] to quit") + + try: + tornado.ioloop.IOLoop.instance().start() + except KeyboardInterrupt: + tornado.ioloop.IOLoop.instance().stop() + + # start + IOLoop.instance().start() + + +if __name__ == "__main__": + server_main_block() diff --git a/sdv/docker/sdvmodel/resource-estimation/template/report.html b/sdv/docker/sdvmodel/resource-estimation/template/report.html new file mode 100644 index 0000000..b53ea9f --- /dev/null +++ b/sdv/docker/sdvmodel/resource-estimation/template/report.html @@ -0,0 +1,75 @@ + + + + Resource Modelling Report + + + + + + + + + + +
+ +

Resource Modelling Report

+
+ +

The VNFs Considered for Modelling:

+ +
+ {% for profile in model['vnf_profiles'] %} +
+ {{ profile['profile_name'] }} + +
+ {% endfor %} +
+
+ +

The Compute-Node Server Profile:

+ + The number of vCPUs: {{ model['server']['vcpus'] }} +
Number of NUMA nodes on this server: {{ model['server']['numas']}} +
vCPUs available for the application in each NUMA: +
SRIOV Support?: {{model['sriov_support']}} +
vCPUs Isolated: {{ model['server']['cpu_isol_set'] }} +
Number of Servers Used: {{ model['deployment_count'] }} +
+ +
+ + {% for zone, server_list in model['deployment'].items() %} +

Servers in Availability zone: {{ zone }}

+ + {% for server in server_list %} +
Server ID: {{ loop.index }}
+
+ {% for vnf in server.hosted_vnfs %} +
+ {{ vnf['vnf'] }} (numa:{{ vnf['numa'] }}) + +
+ {% endfor %} +
+
+ {% endfor %} + +
+ {% endfor %} + +
+ +
+ + + + + + + + + + diff --git a/sdv/docker/sdvmodel/website/actions.js b/sdv/docker/sdvmodel/website/actions.js new file mode 100644 index 0000000..aeb0e91 --- /dev/null +++ b/sdv/docker/sdvmodel/website/actions.js @@ -0,0 +1,39 @@ +/* Copyright 2020 University Of Delhi. +* +* 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. +*/ + + +// expand-arrow button +function toggleClass(element, classname){ + element.classList.toggle(classname) +} + +// Add button +function duplicate(button){ + if (button.previousElementSibling.hasAttribute('name') && + button.previousElementSibling.getAttribute('name') != null) + { + newdiv = button.previousElementSibling.cloneNode(true); + if (!newdiv.lastElementChild.classList.contains('del-button')){ + del ='
' + newdiv.innerHTML += del; + } + button.parentNode.insertBefore(newdiv, button) + } +} + +// Delete Button +function remove(button){ + button.parentNode.parentNode.removeChild(button.parentNode); +} \ No newline at end of file diff --git a/sdv/docker/sdvmodel/website/assets/plus-circle-solid.svg b/sdv/docker/sdvmodel/website/assets/plus-circle-solid.svg new file mode 100644 index 0000000..39a0f8e --- /dev/null +++ b/sdv/docker/sdvmodel/website/assets/plus-circle-solid.svg @@ -0,0 +1,59 @@ + + diff --git a/sdv/docker/sdvmodel/website/assets/server.svg b/sdv/docker/sdvmodel/website/assets/server.svg new file mode 100644 index 0000000..547cdaf --- /dev/null +++ b/sdv/docker/sdvmodel/website/assets/server.svg @@ -0,0 +1,123 @@ + + diff --git a/sdv/docker/sdvmodel/website/assets/trash-alt-regular.svg b/sdv/docker/sdvmodel/website/assets/trash-alt-regular.svg new file mode 100644 index 0000000..fbce77b --- /dev/null +++ b/sdv/docker/sdvmodel/website/assets/trash-alt-regular.svg @@ -0,0 +1,59 @@ + + diff --git a/sdv/docker/sdvmodel/website/assets/vnf.svg b/sdv/docker/sdvmodel/website/assets/vnf.svg new file mode 100644 index 0000000..7bd67e4 --- /dev/null +++ b/sdv/docker/sdvmodel/website/assets/vnf.svg @@ -0,0 +1,83 @@ + + diff --git a/sdv/docker/sdvmodel/website/controller.js b/sdv/docker/sdvmodel/website/controller.js new file mode 100644 index 0000000..5b71352 --- /dev/null +++ b/sdv/docker/sdvmodel/website/controller.js @@ -0,0 +1,34 @@ +/* Copyright 2020 University Of Delhi. +* +* 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. +*/ + +function getModel(){ + + config = {} + + // Get current values from form + for(category of document.getElementsByClassName('resmodData')) + config = mergeDeep(config, objectifyDiv(category)); + + requestModel(config); +} + +function requestModel(config){ + + form = document.getElementById('validate'); + form.elements['config'].value = JSON.stringify(config); + form.submit(); +} + + diff --git a/sdv/docker/sdvmodel/website/index.html b/sdv/docker/sdvmodel/website/index.html new file mode 100644 index 0000000..b08481a --- /dev/null +++ b/sdv/docker/sdvmodel/website/index.html @@ -0,0 +1,124 @@ + + + + Resource Modelling + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + VNFs + +
+ +
+
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+ + Compute Hardware + +
+
+
+ +
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+
+
+ +
+ + + +
+ +
+
+
+
+
+
+
+ +
+ + +
+ +
+ + + +
+ + diff --git a/sdv/docker/sdvmodel/website/mergeDeep.js b/sdv/docker/sdvmodel/website/mergeDeep.js new file mode 100644 index 0000000..970983a --- /dev/null +++ b/sdv/docker/sdvmodel/website/mergeDeep.js @@ -0,0 +1,46 @@ +/* Copyright 2020 University Of Delhi. +* +* 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. +*/ + + +/** +* Performs a deep merge of objects and returns new object. Does not modify +* objects (immutable) and merges arrays via concatenation. +* +* @param {...object} objects - Objects to merge +* @returns {object} New object with merged key/values +*/ +function mergeDeep(...objects) { + const isObject = obj => obj && typeof obj === 'object'; + + return objects.reduce((prev, obj) => { + Object.keys(obj).forEach(key => { + const pVal = prev[key]; + const oVal = obj[key]; + + if (Array.isArray(pVal) && Array.isArray(oVal)) { + prev[key] = pVal.concat(...oVal); + } + else if (isObject(pVal) && isObject(oVal)) { + prev[key] = mergeDeep(pVal, oVal); + } + else { + prev[key] = oVal; + } + }); + + return prev; + }, {}); +} + diff --git a/sdv/docker/sdvmodel/website/readFromHTML.js b/sdv/docker/sdvmodel/website/readFromHTML.js new file mode 100644 index 0000000..f14e089 --- /dev/null +++ b/sdv/docker/sdvmodel/website/readFromHTML.js @@ -0,0 +1,72 @@ +/* Copyright 2020 University Of Delhi. +* +* 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. +*/ + + +/** +* Reads HTML contents into javascript object +* +* +* @param {element} element to read, can be input or div element +* @returns {object} New object with values read +*/ +function objectifyDiv(element){ + var obj = {}; + var el = element.childNodes; + for(var i in el){ + + if(el[i] instanceof HTMLInputElement && el[i].hasAttribute('name')) { + + if(el[i].type == 'text') + obj = mergeDeep(obj, objectify(el[i].name, el[i].value)); + + } + if(el[i] instanceof HTMLSelectElement && el[i].hasAttribute('name')){ + obj = mergeDeep(obj, objectify(el[i].name, el[i].value)); + } + if(el[i] instanceof HTMLDivElement){ + + if(el[i].classList.contains('arr')){ + var key = el[i].getAttribute('name'); + var value = objectifyDiv(el[i]); + if(obj[key] == undefined) + obj[key] =[]; + obj[key].push(value[key]); + } + else + obj = mergeDeep(obj, objectifyDiv(el[i])); + + } + } + + if(element.hasAttribute('name')){ + var newobj = {}; + newobj[element.getAttribute('name')] = obj; + return newobj; + } + return obj; +} + + + +function objectify(key, value){ + var obj = {}; + var keys = key.split('.'); + for(var i = keys.length-1; i >= 0; i--){ + obj[keys[i]] = value; + value = obj; + obj = {}; + } + return value; +} diff --git a/sdv/docker/sdvmodel/website/style/array.css b/sdv/docker/sdvmodel/website/style/array.css new file mode 100644 index 0000000..45a53fc --- /dev/null +++ b/sdv/docker/sdvmodel/website/style/array.css @@ -0,0 +1,55 @@ + +@keyframes popup { + 0%{ + transform: scale(0.5); + } + 90%{ + transform: scale(1.1); + } + 100%{ + transform: scale(1.0); + } +} + +.arr{ + border-radius: 6px; + border-style: dashed; + border-color: #c9c9c9; + border-width: 2px; + display: inline-block; + padding-right: 3em; + margin-right: 15px; + margin-bottom: 5px; + position: relative; + animation: popup 0.2s ease; +} + + +.add-button{ + background-image: url("../assets/plus-circle-solid.svg"); + opacity: 0.7; + width: 2em; + height: 2em; + display: inline-block; + left: 0; +} +.add-button:hover{ + opacity: 1 +} + + + +.del-button{ + background-image: url("../assets/trash-alt-regular.svg"); + background-repeat: no-repeat; + opacity: 0.7; + width: 2em; + height: 2em; + position: absolute; + right: 0.5em; + bottom: 0.5em; +} +.del-button:hover{ + opacity: 1 +} + diff --git a/sdv/docker/sdvmodel/website/style/index.css b/sdv/docker/sdvmodel/website/style/index.css new file mode 100644 index 0000000..882c31a --- /dev/null +++ b/sdv/docker/sdvmodel/website/style/index.css @@ -0,0 +1,198 @@ + +html,body +{ + padding: 0px; + margin: 0px; + font-family: 'Ubuntu', sans-serif; + color: #333333; + background-color: white; + scroll-behavior: smooth; +} + + +#site{ + margin: 8%; + margin-top: 0px; + padding-bottom: 90px; +} + +.add-new{ + border: solid #c9c9c9; + border-width: 1px; + color: #f5f5f5; + opacity: 0.8; + font-size: 1.1em; + padding: 15px; + padding-top: 3px; + padding-bottom: 3px; + margin: 30px; + cursor: pointer; + display: inline-block; + text-align: center; + vertical-align: middle; + background-color: #1fad4e; +} +.add-new:hover{ + opacity: 1; + text-shadow: #f5f5f5 0px 0px 1px; +} + +.add-new img{ + width: 1em; + height: 1em; + margin-left: 5px; + vertical-align: middle; + filter: brightness(0) invert(1); + box-shadow: white 0px 0px 1px; +} + +fieldset { + border-radius: 6px; + border-color: #c9c9c9; + border-width: 1px; + margin-top: 45px; + margin-bottom: 45px; + background-color: #f5f5f5; +} + + + +fieldset legend{ + font-weight: bold; + padding: 1em; + text-shadow: #bbb 0px 0px 2px; +} + +.collapse-arrow::after{ + content: "▲"; + margin: 1em; + color: #61b9ff; + text-shadow: #61b9ff 0px 0px 4px; + cursor: pointer; + font-size: 1.2em; +} + +.expand-arrow::after { + content: "▼"; +} + +@keyframes expand { + 0%{ + transform: scale(0.5); + } + 90%{ + transform: scale(1.1); + } + 100%{ + transform: scale(1.0); + } +} + +/* when expand-arrow hide all siblings */ +.expand-arrow ~ *{ + display:none; +} + + + + +fieldset label{ + padding: 1em; + padding-left: 2em; + text-shadow: #f0f0f0 0px 0px 2px; + width: 230px; + display: inline-block; +} + + +fieldset input{ + border: solid #c9c9c9; + border-radius: 6px; + border-width: 1px; + padding: 5px; + padding-left: 15px; + color: #333333; + width: 150px; +} + +fieldset input:hover{ + background-color: #e8e8e8; +} + +button{ + border: solid #c9c9c9; + border-width: 1px; + height: 45px; + width: 170px; + color: #f5f5f5; + opacity: 0.8; + font-size: 1.1em; + margin: 30px; + cursor: pointer; +} +button:hover{ + opacity: 1; + text-shadow: #f5f5f5 0px 0px 1px; +} + +#save-changes{ + background-color: #50affa; + float: right; +} + +#save-changes::after{ + content: ""; + clear: both; + display: inline; +} + +#delete{ + background-color: #fa3c3c; +} + +#reload{ + background-color: #1fad4e; +} + +button img{ + width: 1em; + height: 1em; + margin-right: 10px; + margin-left: 10px; + filter: brightness(0) invert(1); +} + + + + +#save-changes.changed{ + background-color: #deae00; + +} + + +select{ + background-color: white; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + border: solid #c9c9c9; + border-radius: 6px; + border-width: 1px; + padding: 3px; + padding-left: 15px; + color: #333333; + min-width: 150px; + cursor: pointer; +} + +select:hover{ + background-color: #e8e8e8; +} + + +.select::after{ + content: "▼"; + color: transparent; + text-shadow: #61b9ff -1.5em 0px 0px; +} diff --git a/sdv/docker/sdvmodel/website/style/report.css b/sdv/docker/sdvmodel/website/style/report.css new file mode 100644 index 0000000..bc259fb --- /dev/null +++ b/sdv/docker/sdvmodel/website/style/report.css @@ -0,0 +1,90 @@ + + +.report{ + text-align: left; + margin: 8%; + margin-top: 3%; + padding: 50px; + + background-color: #f5f5f5; + border-radius: 6px; + border-color: #c9c9c9; + border-width: 1px; + border-style: solid; + text-shadow: #CCC 0px 0px 2px; + word-spacing: 0.1em; + line-height: 1.5em; +} + +hr{ + width: 60%; +} + + +button{ + background-color: #50affa; + float: right; +} + + +.holder{ + display: flex; + flex-wrap: wrap; + width: 98%; +} + + +.tab{ + background-color: #f0f0f0; + border-radius: 8px; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + border-color: #757474; + border-width: 2px; + border-style: solid; + display: inline-block; + padding: 3px; + padding-left: 6px; + padding-right: 10px; +} + +.server{ + background-image: url("/assets/server.svg"); + background-repeat: repeat; + background-size: auto 100%; + + background-color: #e3e3e3; + border-color: #757474; + border-width: 8px; + border-style: double; +} + + + +.vnf{ + min-width: 80px; + margin: 10px; + + font-size: 0.9em; + padding: 5px; + + + border-radius: 8px; + border-color: #3dbbf5; + border-width: 3px; + border-style: solid; + + background-color: #e6f4fa; +} + +.vnf::after{ + clear: both; + content: ''; + display: inline-block; +} + +.vnf > img{ + width: 35px; + margin-left: 10px; + float: right +} -- cgit 1.2.3-korg