diff --git a/labapp/app/app.py b/labapp/app/app.py index da2a8b5..752b22b 100644 --- a/labapp/app/app.py +++ b/labapp/app/app.py @@ -3,12 +3,15 @@ """ import os import re +import json +import requests +import urllib from flask import Flask, render_template, jsonify, request, redirect, make_response, flash, url_for from flask_caching import Cache -import requests import markdown from ce import get_ce_info, get_ce_state from fetch import get_runner_session, cloudapp_fetch, cloudapp_req_headers, cloudapp_res_headers +from score import score_get_results, score_build_table app = Flask(__name__) app.config['ce_info'] = None @@ -42,7 +45,7 @@ def validate_eph_ns(input_name): pattern = r'^[a-zA-Z]+-[a-zA-Z]+$' return bool(re.match(pattern, input_name)) -def eph_ns() -> str: +def get_eph_ns() -> str: """check if ephemeral namespace is set""" this_eph_ns = request.cookies.get('eph_ns', None) return this_eph_ns @@ -85,7 +88,7 @@ def arch(): @app.route('/setup', methods=['GET', 'POST']) def setup(): """setup page""" - ns = eph_ns() + ns = get_eph_ns() if request.method == 'POST': action = request.form['action'] if action == 'save': @@ -116,10 +119,10 @@ def ce_state(): data = get_ce_state(app.config['ce_info']) return data -@app.route('/lb') +@app.route('/loadbalancing') def lb(): """lb page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/lb.md") return render_template('exercise_standard.html', title="MCN Practical: LB", @@ -130,7 +133,7 @@ def lb(): @app.route('/route') def path(): """routing page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/route.md") return render_template('exercise_standard.html', title="MCN Practical: HTTP Routing", @@ -142,7 +145,7 @@ def path(): @app.route('/manipulation') def header(): """manipulation page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/manipulation.md") return render_template('exercise_standard.html', title="MCN Practical: Manipulation", @@ -153,7 +156,7 @@ def header(): @app.route('/portability') def port(): """portability page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/portability.md") return render_template('exercise_standard.html', title="MCN Practical: Portability", @@ -164,7 +167,7 @@ def port(): @app.route('/vnet') def vnet(): """vnet page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/reference.md") return render_template('coming-soon.html', title="MCN Practical: Reference", @@ -175,7 +178,7 @@ def vnet(): @app.route('/netpolicy') def netp(): """netpolicy page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/reference.md") return render_template('coming-soon.html', title="MCN Practical: Reference", @@ -183,10 +186,10 @@ def netp(): ns=ns ) -@app.route('/ref') +@app.route('/reference') def ref(): """reference page""" - ns = eph_ns() + ns = get_eph_ns() html = render_md("markdown/reference.md") return render_template('coming-soon.html', title="MCN Practical: Reference", @@ -197,13 +200,37 @@ def ref(): @app.route('/score') def score(): """scoreboard page""" - ns = eph_ns() - html = render_md("markdown/score.md") - return render_template('coming-soon.html', - title="MCN Practical: Scoreboard", - content=html, - ns=ns - ) + ns = get_eph_ns() + score_cookie = request.cookies.get('score', '%7B%7D') + print(score_cookie) + try: + decoded_cookie = urllib.parse.unquote(score_cookie) + enc_score = json.loads(decoded_cookie) + this_score = {urllib.parse.unquote(k): v for k, v in enc_score.items()} + except json.JSONDecodeError: + this_score = {} + try: + p_score = score_get_results(this_score) + over_table = score_build_table(p_score, 'overview', 'Overview') + lb_table = score_build_table(p_score, 'lb', 'Load Balancing') + route_table = score_build_table(p_score, 'route', 'Routing') + manip_table = score_build_table(p_score, 'manip', 'Manipulation') + port_table = score_build_table(p_score, 'port', 'Portability') + except LabException as e: + print(f"Couldn't build score table: {e}") + response = make_response(render_template('score.html', + title="MCN Practical: Scoreboard", + over_table=over_table, + lb_table=lb_table, + route_table=route_table, + manip_table=manip_table, + port_table=port_table, + ns=ns + )) + response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = '0' + return response @app.route('/_test1') def ex_test(): @@ -234,7 +261,7 @@ def ex_test2(): def lb_aws(): """Azure LB test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") url = f"https://{ns}.{app.config['base_url']}" @@ -249,7 +276,7 @@ def lb_aws(): def lb_azure(): """Azure LB test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") url = f"https://{ns}.{app.config['base_url']}" @@ -269,7 +296,7 @@ def lb_azure(): def route1(): """First Route Test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") base_url = app.config['base_url'] @@ -291,7 +318,7 @@ def route1(): def route2(): """First Route Test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") base_url = app.config['base_url'] @@ -315,7 +342,7 @@ def route2(): def manip1(): """First Manip Test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") base_url = app.config['base_url'] @@ -331,7 +358,7 @@ def manip1(): def manip2(): """Second Manip Test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") base_url = app.config['base_url'] @@ -348,7 +375,7 @@ def manip2(): def manip3(): """Third Manip Test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") base_url = app.config['base_url'] @@ -372,7 +399,7 @@ def manip3(): def port1(): """Friend test""" try: - ns = eph_ns() + ns = get_eph_ns() if not ns: raise LabException("Ephemeral NS not set") url = f"https://{ns}.{app.config['base_url']}/" diff --git a/labapp/app/markdown/lb.md b/labapp/app/markdown/lb.md index 96c0d4e..3ca0a3e 100644 --- a/labapp/app/markdown/lb.md +++ b/labapp/app/markdown/lb.md @@ -61,7 +61,7 @@ Host: eph-ns.mcn-lab.f5demos.com
@@ -152,7 +152,7 @@ Host: eph-ns.mcn-lab.f5demos.com
diff --git a/labapp/app/markdown/manipulation.md b/labapp/app/markdown/manipulation.md index 3c3d09e..f87135e 100644 --- a/labapp/app/markdown/manipulation.md +++ b/labapp/app/markdown/manipulation.md @@ -50,7 +50,7 @@ Host: eph-ns.mcn-lab.f5demos.com
@@ -117,7 +117,7 @@ Host: eph-ns.mcn-lab.f5demos.com
@@ -166,7 +166,7 @@ Host: eph-ns.mcn-lab.f5demos.com
diff --git a/labapp/app/markdown/overview.md b/labapp/app/markdown/overview.md index af81bae..2c1e801 100644 --- a/labapp/app/markdown/overview.md +++ b/labapp/app/markdown/overview.md @@ -100,7 +100,7 @@ GET https://foo.mcn-lab.f5demos.com/ HTTP/1.1
@@ -125,7 +125,7 @@ GET https://bar.mcn-lab.f5demos.com/ HTTP/1.1
diff --git a/labapp/app/markdown/portability.md b/labapp/app/markdown/portability.md index 5bf0541..fd0df22 100644 --- a/labapp/app/markdown/portability.md +++ b/labapp/app/markdown/portability.md @@ -46,7 +46,7 @@ Host: eph-ns.mcn-lab.f5demos.com
@@ -108,7 +108,7 @@ Host: friend-eph-ns.mcn-lab.f5demos.com diff --git a/labapp/app/markdown/route.md b/labapp/app/markdown/route.md index 65545c9..5988e98 100644 --- a/labapp/app/markdown/route.md +++ b/labapp/app/markdown/route.md @@ -63,7 +63,7 @@ Host: eph-ns.mcn-lab.f5demos.com
@@ -117,7 +117,7 @@ X-MCN-lab: azure
diff --git a/labapp/app/score.py b/labapp/app/score.py new file mode 100644 index 0000000..af8ffcc --- /dev/null +++ b/labapp/app/score.py @@ -0,0 +1,127 @@ +""" +Build Scoreboards +""" +import copy + +score_schema = { + "/_test1": { + "name": "Foo Test Example", + "key": "overview", + "href": "/overview", + "result": "none", + }, + "/_test2": { + "name": "Bar Test Example", + "key": "overview", + "href": "/overview", + "result": "none" + }, + "/_lb1": { + "name": "AWS Cloud App", + "key": "lb", + "href": "/loadbalancing", + "result": "none" + }, + "/_lb2": { + "name": "Azure Cloud App", + "key": "lb", + "href": "/loadbalancing", + "result": "none" + }, + "/_route1": { + "name": "Path Routing", + "key": "route", + "href": "/route", + "result": "none" + }, + "/_route2": { + "name": "Header Routing", + "key": "route", + "href": "/route", + "result": "none" + }, + "/_manip1:": { + "name": "Path Rewrite", + "key": "manip", + "href": "/manipulation", + "result": "none" + }, + "/_manip2": { + "name": "Request Headers", + "key": "manip", + "href": "/manipulation", + "result": "none" + }, + "/_manip3": { + "name": "Response Headers", + "key": "manip", + "href": "/manipulation", + "result": "none" + }, + "/_port1": { + "name": "Advertise Policy", + "key": "port", + "href": "/portability", + "result": "none" + }, + "/_port2": { + "name": "Find a Friend", + "key": "port", + "href": "/portability", + "result": "none" + } +} + +result_map = { + "fail": '', + "pass": '', + "none": '' +} + +def score_get_results(cookie_results): + """ + build score result from cookie + """ + this_score = copy.deepcopy(score_schema) + for test_path, result in cookie_results.items(): + if test_path in score_schema: + this_score[test_path]['result'] = result + print(this_score) + print(score_schema) + return this_score + +def score_sort(scores, key): + """sort score by key""" + scores = {k: v for k, v in scores.items() if v['key'] == key} + return scores + +def score_build_table(scores, section, name): + """build table section""" + section_scores = score_sort(scores, section) + rows_html = "" + for key, score in section_scores.items(): + print(score['result']) + r_icon = result_map[score['result']] + section_html = f""" + + {score['name']} + {r_icon} + + """ + rows_html += section_html + html = f""" + + + + + + + + {rows_html} + +
{name}
+ """ + return html + + + diff --git a/labapp/app/static/custom.css b/labapp/app/static/custom.css index 6d11d83..f92034d 100644 --- a/labapp/app/static/custom.css +++ b/labapp/app/static/custom.css @@ -43,6 +43,10 @@ main { padding: 50px; } +.main-body { + padding: 50px; +} + .err { display: flex; justify-content: center; /* Centers horizontally */ diff --git a/labapp/app/static/helpers.js b/labapp/app/static/helpers.js index e39aac5..18e50b4 100644 --- a/labapp/app/static/helpers.js +++ b/labapp/app/static/helpers.js @@ -19,46 +19,70 @@ function getCookie(name) { return null; } -async function makeHttpRequest(buttonId, requestUrl, resultDivId) { - const button = document.getElementById(buttonId); - const resultDiv = document.getElementById(resultDivId); - button.disabled = true; - try { - const response = await axios.get(requestUrl); - if (response.data.status === 'success') { - const prettyJson = JSON.stringify(response.data.data, null, 4); - resultDiv.innerHTML = `
Request Succeeded:
${prettyJson}
`; - } else { - const errJson = JSON.stringify(response.data.error, null, 4); - resultDiv.innerHTML = `
Request Failed:
${errJson}
`; - } - } catch (error) { - resultDiv.innerHTML = `
Error: ${error.message}
`; - } finally { - button.disabled = false; - resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); +async function testHttpRequest(buttonId, requestUrl, resultDivId) { + const button = document.getElementById(buttonId); + const resultDiv = document.getElementById(resultDivId); + button.disabled = true; + try { + const response = await axios.get(requestUrl); + if (response.data.status === 'success') { + const prettyJson = JSON.stringify(response.data.data, null, 4); + resultDiv.innerHTML = `
Request Succeeded:
${prettyJson}
`; + updateScoreCookie(requestUrl, 'pass'); + } else { + const errJson = JSON.stringify(response.data.error, null, 4); + resultDiv.innerHTML = `
Request Failed:
${errJson}
`; + updateScoreCookie(requestUrl, 'fail'); + } + } catch (error) { + resultDiv.innerHTML = `
Error: ${error.message}
`; + updateScoreCookie(requestUrl, 'fail'); + } finally { + button.disabled = false; + resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } } -} -async function makePostRequest(buttonId, requestUrl, resultDivId, inputDataId) { + async function testPostRequest(buttonId, requestUrl, resultDivId, inputDataId) { const button = document.getElementById(buttonId); const resultDiv = document.getElementById(resultDivId); const inputData = document.getElementById(inputDataId).value; button.disabled = true; - try { const response = await axios.post(requestUrl, { userInput: inputData }); if (response.data.status === 'success') { const prettyJson = JSON.stringify(response.data.data, null, 4); - resultDiv.innerHTML = `
Request Succeeded:
${prettyJson}
`; + resultDiv.innerHTML = `
Request Succeeded:
${prettyJson}
`; + updateScoreCookie(requestUrl, 'pass'); } else { const errJson = JSON.stringify(response.data.error, null, 4); - resultDiv.innerHTML = `
Request Failed:
${errJson}
`; + resultDiv.innerHTML = `
Request Failed:
${errJson}
`; + updateScoreCookie(requestUrl, 'fail'); } } catch (error) { resultDiv.innerHTML = `
Error: ${error.message}
`; + updateScoreCookie(requestUrl, 'fail'); } finally { button.disabled = false; resultDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } -} + } + + function updateScoreCookie(requestUrl, status) { + // Get the current cookie, decode it, and parse it as JSON + const currentCookie = decodeURIComponent(getScoreCookie('score') || '%7B%7D'); // Ensure the default value is an encoded empty JSON object + let progress = JSON.parse(currentCookie); + progress[encodeURIComponent(requestUrl)] = status; + document.cookie = `score=${encodeURIComponent(JSON.stringify(progress))}; path=/; expires=${new Date(new Date().getTime() + 86400e3).toUTCString()};`; + } + + function getScoreCookie(name) { + let cookieArray = document.cookie.split(';'); + for(let i = 0; i < cookieArray.length; i++) { + let cookiePair = cookieArray[i].split('='); + if(name == cookiePair[0].trim()) { + return cookiePair[1]; + } + } + return null; + } diff --git a/labapp/app/static/table.css b/labapp/app/static/table.css new file mode 100644 index 0000000..c05fec5 --- /dev/null +++ b/labapp/app/static/table.css @@ -0,0 +1,60 @@ +/* General table styling */ +.score-table { + background: #34495E; /* Dark blue background for contrast */ + color: rgb(85, 155, 221); /* Light blue text for readability */ + border-radius: .9em; /* Rounded corners for aesthetics */ + overflow: hidden; /* Keeps the inner elements contained within the border radius */ + margin: 1em 0; /* Spacing above and below each table */ +} + +/* Ensures table cells have appropriate padding and text alignment */ +.score-table th, .score-table td { + text-align: left; /* Align text to the left */ + display: block; /* Stack cells vertically on small screens */ + padding: .4em .7em; /* Padding for spacing within cells */ +} + +.score-table th { + background: #34495E; /* Dark blue background for contrast */ + color: white; +} + +.score-table td { + background: rgb(230, 234, 253); + color: black; +} + +/* Specific styles for pass/fail scenarios */ +.score-pass { + color: rgb(25, 135, 84); /* Green for pass */ +} + +.score-fail { + color: rgb(203, 68, 74); /* Red for fail */ +} + +/* Responsive settings and adjustments for table display */ +@media (min-width: 768px) { /* Adjusting for medium-sized devices and up */ + .score-table { + min-width: 300px; /* Minimum width for tables on larger screens */ + width: 100%; /* Full width to utilize available space */ + } + .score-table th, .score-table td { + display: table-cell; /* Normal table cell display on larger screens */ + } +} + +/* Responsive container to allow horizontal scrolling on small devices */ +.table-responsive { + width: 100%; /* Full width */ + overflow-x: auto; /* Enable horizontal scrolling when necessary */ +} + +/* Adjust first and last cell padding for aesthetics */ +.score-table th:first-child, .score-table td:first-child { + padding-left: .75em; /* Remove left padding for clean alignment */ +} + +.score-table th:last-child, .score-table td:last-child { + padding-right: 0; /* Remove right padding for clean alignment */ +} diff --git a/labapp/app/static/table2.css b/labapp/app/static/table2.css new file mode 100644 index 0000000..7a129c1 --- /dev/null +++ b/labapp/app/static/table2.css @@ -0,0 +1,73 @@ +/* General Styles for Score Tables */ +.score-table { + background: #34495E; + border-radius: .8em; + overflow: hidden; + margin: 1em 0; + color: rgb(85, 155, 221); /* General text color, adjust as needed */ + tr, th { + border-color: lighten(#34495E, 10%); + } +} + +.score-table th, .score-table td { + text-align: left; + margin: .5em 1em; + padding: .25em .5em; + display: block; /* Default to block to stack on small screens */ +} + +/* Responsive Table Styles */ +.table-responsive { + min-width: 100%; /* Ensure the responsive container is full width on smaller screens */ +} + +/* Styling for 'th' and 'td' under media query control */ +@media (min-width: 480px) { + .score-table th, .score-table td { + display: table-cell; + } + .score-table th { + display: table-cell; /* Ensure table headers are visible and formatted correctly at wider widths */ + padding: 1em; + } + .score-table td { + padding: 1em !important; /* Important to override Bootstrap defaults if necessary */ + background: rgb(230, 234, 253); /* Light blue background for contrast */ + } + .score-table td:before { + display: none; /* Hide the 'before' content when not needed */ + } +} + +/* Specific Styles for Pass/Fail */ +.score-pass { + color: rgb(25, 135, 84); /* Green for pass */ +} + +.score-fail { + color: rgb(203, 68, 74); /* Red for fail */ +} + +/* Adjustments for First and Last Child Padding */ +.score-table th:first-child, .score-table td:first-child { + padding-left: 0; +} + +.score-table th:last-child, .score-table td:last-child { + padding-right: 0; +} + +/* Responsive adjustments for smaller screens */ +@media (max-width: 479px) { + .score-table th, .score-table td { + padding: 1em; /* Larger padding for readability on small screens */ + } + .score-table td:before { + /* Content for responsive labels */ + content: attr(data-label); + font-weight: bold; + width: 6.5em; + display: inline-block; + } +} diff --git a/labapp/app/templates/base.html b/labapp/app/templates/base.html index 3cf5c68..9446736 100644 --- a/labapp/app/templates/base.html +++ b/labapp/app/templates/base.html @@ -48,7 +48,7 @@
- - {% endif %} - - diff --git a/labapp/app/templates/score.html b/labapp/app/templates/score.html new file mode 100644 index 0000000..387cd53 --- /dev/null +++ b/labapp/app/templates/score.html @@ -0,0 +1,50 @@ + +{% extends "base.html" %} + +{% block title %}{{ title }}{% endblock %} + +{% block content %} + +
+

Scoreboard

+ + +
+
+
+
+ {{ lb_table|safe }} +
+
+
+
+ {{ route_table|safe }} +
+
+
+
+
+
+ {{ manip_table|safe }} +
+
+
+
+ {{ port_table|safe }} +
+
+
+
+ + +
+ +
+ +{% endblock %} +