This is an automated email from the ASF dual-hosted git repository.

shamrick pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new c9d5d8a1ae Tpv2 server fix (#7102)
c9d5d8a1ae is described below

commit c9d5d8a1ae4d7d2abfb9b521b321e21ebd232b22
Author: ocket8888 <[email protected]>
AuthorDate: Thu Oct 6 12:55:44 2022 -0600

    Tpv2 server fix (#7102)
    
    * Fix TPv2 server not running.
    
    Also fixes a long-standing issue where -k/--insecure passed on the
    command line is ignored.
    
    * Handle lack of permissions for HTTP-to-HTTPS redirect server more 
gracefully
    
    * Run TPv2 server for integration tests instead of ng serve-ing
    
    * Fix naked 'ng' without 'npx'
    
    * Fix missing path part in main script exec
    
    * Run server in the background instead of blocking
    
    * Fix direct loading of Tenants page broken in actual deployments
    
    * Update Chromedriver
---
 .../actions/tpv2-integration-tests/entrypoint.sh   |  3 +-
 .github/workflows/tpv2.yml                         |  5 ++
 experimental/traffic-portal/package-lock.json      | 77 +++++++++++++-----
 experimental/traffic-portal/package.json           |  2 +-
 experimental/traffic-portal/server.config.ts       | 93 ++++++++++++++--------
 experimental/traffic-portal/server.ts              | 67 +++++++++-------
 .../app/core/users/tenants/tenants.component.ts    | 12 +--
 7 files changed, 172 insertions(+), 87 deletions(-)

diff --git a/.github/actions/tpv2-integration-tests/entrypoint.sh 
b/.github/actions/tpv2-integration-tests/entrypoint.sh
index b7f03aa004..f474be950b 100755
--- a/.github/actions/tpv2-integration-tests/entrypoint.sh
+++ b/.github/actions/tpv2-integration-tests/entrypoint.sh
@@ -26,7 +26,7 @@ envsubst 
<../../.github/actions/tpv2-integration-tests/cdn.json >./cdn.conf
 ./traffic_ops_golang --cfg ./cdn.conf --dbcfg 
../../.github/actions/tpv2-integration-tests/database.json > out.log 2>&1 &
 
 cd "${GITHUB_WORKSPACE}/experimental/traffic-portal"
-npx ng serve &
+node ./dist/traffic-portal/server/main.js --port 4200 -k -t 
'https://localhost:6443/' &
 
 timeout 15m bash <<TMOUT
        while ! curl -k "http://localhost:4200/api/4.0/ping"; >/dev/null 2>&1; do
@@ -35,3 +35,4 @@ timeout 15m bash <<TMOUT
        done
 TMOUT
 timeout 15m npm run e2e:ci
+kill %%
diff --git a/.github/workflows/tpv2.yml b/.github/workflows/tpv2.yml
index c236d102eb..39b69d4c7c 100644
--- a/.github/workflows/tpv2.yml
+++ b/.github/workflows/tpv2.yml
@@ -218,6 +218,11 @@ jobs:
         run: |
           cd experimental/traffic-portal
           npm ci
+      - name: Build TPv2
+        run: |
+          cd experimental/traffic-portal
+          npx ng build
+          npx ng run traffic-portal:server
       - name: Run everything and test
         uses: ./.github/actions/tpv2-integration-tests
       - name: Upload Report
diff --git a/experimental/traffic-portal/package-lock.json 
b/experimental/traffic-portal/package-lock.json
index 86ae42b853..02137c5a46 100644
--- a/experimental/traffic-portal/package-lock.json
+++ b/experimental/traffic-portal/package-lock.json
@@ -55,7 +55,7 @@
         "@typescript-eslint/eslint-plugin": "^5.10.0",
         "@typescript-eslint/parser": "^5.10.0",
         "axios": "^0.27.2",
-        "chromedriver": "^104.0.0",
+        "chromedriver": "^106.0.1",
         "codelyzer": "^6.0.0",
         "eslint": "^8.2.0",
         "eslint-plugin-import": "^2.25.3",
@@ -4277,9 +4277,9 @@
       }
     },
     "node_modules/@testim/chrome-version": {
-      "version": "1.1.2",
-      "resolved": 
"https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.2.tgz";,
-      "integrity": 
"sha512-1c4ZOETSRpI0iBfIFUqU4KqwBAB2lHUAlBjZz/YqOHqwM9dTTzjV6Km0ZkiEiSCx/tLr1BtESIKyWWMww+RUqw==",
+      "version": "1.1.3",
+      "resolved": 
"https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.3.tgz";,
+      "integrity": 
"sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==",
       "dev": true
     },
     "node_modules/@tootallnate/once": {
@@ -6622,17 +6622,18 @@
       }
     },
     "node_modules/chromedriver": {
-      "version": "104.0.0",
-      "resolved": 
"https://registry.npmjs.org/chromedriver/-/chromedriver-104.0.0.tgz";,
-      "integrity": 
"sha512-zbHZutN2ATo19xA6nXwwLn+KueD/5w8ap5m4b6bCb8MIaRFnyDwMbFoy7oFAjlSMpCFL3KSaZRiWUwjj//N3yQ==",
+      "version": "106.0.1",
+      "resolved": 
"https://registry.npmjs.org/chromedriver/-/chromedriver-106.0.1.tgz";,
+      "integrity": 
"sha512-thaBvbDEPgGocSp4/SBIajQz3G7UQfUqCOHZBp9TVhRJv7c91eZrUGcjeJUaNF4p9CfSjCYNYzs4EVVryqmddA==",
       "dev": true,
       "hasInstallScript": true,
       "dependencies": {
-        "@testim/chrome-version": "^1.1.2",
+        "@testim/chrome-version": "^1.1.3",
         "axios": "^0.27.2",
-        "del": "^6.0.0",
+        "compare-versions": "^5.0.1",
+        "del": "^6.1.1",
         "extract-zip": "^2.0.1",
-        "https-proxy-agent": "^5.0.0",
+        "https-proxy-agent": "^5.0.1",
         "proxy-from-env": "^1.1.0",
         "tcp-port-used": "^1.0.1"
       },
@@ -6643,6 +6644,19 @@
         "node": ">=10"
       }
     },
+    "node_modules/chromedriver/node_modules/https-proxy-agent": {
+      "version": "5.0.1",
+      "resolved": 
"https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz";,
+      "integrity": 
"sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "dev": true,
+      "dependencies": {
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/ci-info": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz";,
@@ -6934,6 +6948,12 @@
       "integrity": 
"sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
       "dev": true
     },
+    "node_modules/compare-versions": {
+      "version": "5.0.1",
+      "resolved": 
"https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz";,
+      "integrity": 
"sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==",
+      "dev": true
+    },
     "node_modules/component-emitter": {
       "version": "1.3.0",
       "resolved": 
"https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz";,
@@ -21872,9 +21892,9 @@
       }
     },
     "@testim/chrome-version": {
-      "version": "1.1.2",
-      "resolved": 
"https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.2.tgz";,
-      "integrity": 
"sha512-1c4ZOETSRpI0iBfIFUqU4KqwBAB2lHUAlBjZz/YqOHqwM9dTTzjV6Km0ZkiEiSCx/tLr1BtESIKyWWMww+RUqw==",
+      "version": "1.1.3",
+      "resolved": 
"https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.3.tgz";,
+      "integrity": 
"sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==",
       "dev": true
     },
     "@tootallnate/once": {
@@ -23693,18 +23713,31 @@
       "dev": true
     },
     "chromedriver": {
-      "version": "104.0.0",
-      "resolved": 
"https://registry.npmjs.org/chromedriver/-/chromedriver-104.0.0.tgz";,
-      "integrity": 
"sha512-zbHZutN2ATo19xA6nXwwLn+KueD/5w8ap5m4b6bCb8MIaRFnyDwMbFoy7oFAjlSMpCFL3KSaZRiWUwjj//N3yQ==",
+      "version": "106.0.1",
+      "resolved": 
"https://registry.npmjs.org/chromedriver/-/chromedriver-106.0.1.tgz";,
+      "integrity": 
"sha512-thaBvbDEPgGocSp4/SBIajQz3G7UQfUqCOHZBp9TVhRJv7c91eZrUGcjeJUaNF4p9CfSjCYNYzs4EVVryqmddA==",
       "dev": true,
       "requires": {
-        "@testim/chrome-version": "^1.1.2",
+        "@testim/chrome-version": "^1.1.3",
         "axios": "^0.27.2",
-        "del": "^6.0.0",
+        "compare-versions": "^5.0.1",
+        "del": "^6.1.1",
         "extract-zip": "^2.0.1",
-        "https-proxy-agent": "^5.0.0",
+        "https-proxy-agent": "^5.0.1",
         "proxy-from-env": "^1.1.0",
         "tcp-port-used": "^1.0.1"
+      },
+      "dependencies": {
+        "https-proxy-agent": {
+          "version": "5.0.1",
+          "resolved": 
"https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz";,
+          "integrity": 
"sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+          "dev": true,
+          "requires": {
+            "agent-base": "6",
+            "debug": "4"
+          }
+        }
       }
     },
     "ci-info": {
@@ -23945,6 +23978,12 @@
       "integrity": 
"sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
       "dev": true
     },
+    "compare-versions": {
+      "version": "5.0.1",
+      "resolved": 
"https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz";,
+      "integrity": 
"sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==",
+      "dev": true
+    },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": 
"https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz";,
diff --git a/experimental/traffic-portal/package.json 
b/experimental/traffic-portal/package.json
index 81f23e1219..eb642b5526 100644
--- a/experimental/traffic-portal/package.json
+++ b/experimental/traffic-portal/package.json
@@ -95,7 +95,7 @@
     "@typescript-eslint/eslint-plugin": "^5.10.0",
     "@typescript-eslint/parser": "^5.10.0",
     "axios": "^0.27.2",
-    "chromedriver": "^104.0.0",
+    "chromedriver": "^106.0.1",
     "codelyzer": "^6.0.0",
     "eslint": "^8.2.0",
     "eslint-plugin-import": "^2.25.3",
diff --git a/experimental/traffic-portal/server.config.ts 
b/experimental/traffic-portal/server.config.ts
index 8da2dc581c..fa9edfacbf 100644
--- a/experimental/traffic-portal/server.config.ts
+++ b/experimental/traffic-portal/server.config.ts
@@ -44,7 +44,12 @@ export interface ServerVersion {
        arch?: string;
 }
 
-/** Converts the given version to a string. */
+/**
+ * Converts the given version to a string.
+ *
+ * @param v The server version to convert.
+ * @returns A string representation of `v`.
+ */
 export function versionToString(v: ServerVersion): string {
        let ret = `traffic-portal-${v.version}`;
 
@@ -70,8 +75,13 @@ export function versionToString(v: ServerVersion): string {
        return ret;
 }
 
+/**
+ * Checks if some unknown Javascript value is a valid {@link ServerVersion}.
+ *
+ * @param v The object to check.
+ * @returns Whether `v` is a valid {@link ServerVersion}.
+ */
 function isServerVersion(v: unknown): v is ServerVersion {
-       // tslint:disable completed-docs
        if (typeof(v) !== "object") {
                console.error("version does not represent a server version");
                return false;
@@ -85,30 +95,34 @@ function isServerVersion(v: unknown): v is ServerVersion {
                console.error("version missing required field 'version'");
                return false;
        }
-       if (typeof((v as {version: unknown; }).version) !== "string") {
+       if (typeof((v as {version: unknown}).version) !== "string") {
                return false;
        }
 
-       if (Object.prototype.hasOwnProperty.call(v, "commits") && (typeof((v as 
{commits: unknown; }).commits)) !== "string") {
-               console.error(`version property 'commits' has incorrect type; 
want: string, got: ${typeof((v as {commits: unknown; }).commits)}`);
+       if (Object.prototype.hasOwnProperty.call(v, "commits") && (typeof((v as 
{commits: unknown}).commits)) !== "string") {
+               console.error(`version property 'commits' has incorrect type; 
want: string, got: ${typeof((v as {commits: unknown}).commits)}`);
                return false;
        }
-       if (Object.prototype.hasOwnProperty.call(v, "hash") && (typeof((v as 
{hash: unknown; }).hash)) !== "string") {
-               console.error(`version property 'hash' has incorrect type; 
want: string, got: ${typeof((v as {hash: unknown; }).hash)}`);
+       if (Object.prototype.hasOwnProperty.call(v, "hash") && (typeof((v as 
{hash: unknown}).hash)) !== "string") {
+               console.error(`version property 'hash' has incorrect type; 
want: string, got: ${typeof((v as {hash: unknown}).hash)}`);
                return false;
        }
-       if (Object.prototype.hasOwnProperty.call(v, "elRelease") && (typeof((v 
as {elRelease: unknown; }).elRelease)) !== "string") {
-               console.error(`version property 'elRelease' has incorrect type; 
want: string, got: ${typeof((v as {elRelease: unknown; }).elRelease)}`);
+       if (Object.prototype.hasOwnProperty.call(v, "elRelease") && (typeof((v 
as {elRelease: unknown}).elRelease)) !== "string") {
+               console.error(
+                       `version property 'elRelease' has incorrect type; want: 
string, got: ${typeof (v as {elRelease: unknown}).elRelease}`
+               );
                return false;
        }
-       if (Object.prototype.hasOwnProperty.call(v, "arch") && (typeof((v as 
{arch: unknown; }).arch)) !== "string") {
-               console.error(`version property 'arch' has incorrect type; 
want: string, got: ${typeof((v as {arch: unknown; }).arch)}`);
+       if (Object.prototype.hasOwnProperty.call(v, "arch") && (typeof((v as 
{arch: unknown}).arch)) !== "string") {
+               console.error(`version property 'arch' has incorrect type; 
want: string, got: ${typeof((v as {arch: unknown}).arch)}`);
                return false;
        }
        return true;
-       // tslint:enable completed-docs
 }
 
+/**
+ * The base properties shared by all ServerConfigs.
+ */
 interface BaseConfig {
        /** Whether or not SSL certificate errors from Traffic Ops will be 
ignored. */
        insecure?: boolean;
@@ -122,6 +136,9 @@ interface BaseConfig {
        version: ServerVersion;
 }
 
+/**
+ * The properties a ServerConfig has if SSL is in use.
+ */
 interface ConfigWithSSL {
        /** The path to the SSL certificate Traffic Portal will use. */
        certPath: string;
@@ -131,6 +148,9 @@ interface ConfigWithSSL {
        useSSL: true;
 }
 
+/**
+ * The properties a ServerConfig has if SSL is not in use.
+ */
 interface ConfigWithoutSSL {
        /** Whether or not to serve HTTPS */
        useSSL?: false;
@@ -143,9 +163,11 @@ export type ServerConfig = BaseConfig & (ConfigWithSSL | 
ConfigWithoutSSL);
  * isConfig checks specifically the contents of configuration files
  * passed through JSON.parse, so it doesn't validate the 'version', since
  * that doesn't need to be in the configuration file.
+ *
+ * @param c The ostensibly configuration object to check.
+ * @returns Whether c is a valid server configuration.
  */
 function isConfig(c: unknown): c is ServerConfig {
-       // tslint:disable completed-docs
        if (typeof(c) !== "object") {
                throw new Error("configuration is not an object");
        }
@@ -154,53 +176,52 @@ function isConfig(c: unknown): c is ServerConfig {
        }
 
        if (Object.prototype.hasOwnProperty.call(c, "insecure")) {
-               if (typeof((c as {insecure: unknown; }).insecure) !== 
"boolean") {
+               if (typeof((c as {insecure: unknown}).insecure) !== "boolean") {
                        throw new Error("'insecure' must be a boolean");
                }
        } else {
-               (c as {insecure: boolean; }).insecure = false;
+               (c as {insecure: boolean}).insecure = false;
        }
        if (!Object.prototype.hasOwnProperty.call(c, "port")) {
                throw new Error("'port' is required");
        }
-       if (typeof((c as {port: unknown; }).port) !== "number") {
+       if (typeof((c as {port: unknown}).port) !== "number") {
                throw new Error("'port' must be a number");
        }
        if (!Object.prototype.hasOwnProperty.call(c, "trafficOps")) {
                throw new Error("'trafficOps' is required");
        }
-       if (typeof((c as {trafficOps: unknown; }).trafficOps) !== "string") {
+       if (typeof((c as {trafficOps: unknown}).trafficOps) !== "string") {
                throw new Error("'trafficOps' must be a string");
        }
 
        try {
-               (c as {trafficOps: URL; }).trafficOps = new URL((c as 
{trafficOps: string; }).trafficOps);
+               (c as {trafficOps: URL}).trafficOps = new URL((c as 
{trafficOps: string}).trafficOps);
        } catch (e) {
                throw new Error(`'trafficOps' is not a valid URL: ${e}`);
        }
 
        if (Object.prototype.hasOwnProperty.call(c, "useSSL")) {
-               if (typeof((c as {useSSL: unknown; }).useSSL) !== "boolean") {
+               if (typeof((c as {useSSL: unknown}).useSSL) !== "boolean") {
                        throw new Error("'useSSL' must be a boolean");
                }
-               if ((c as {useSSL: boolean; }).useSSL) {
+               if ((c as {useSSL: boolean}).useSSL) {
                        if (!Object.prototype.hasOwnProperty.call(c, 
"certPath")) {
                                throw new Error("'certPath' is required to use 
SSL");
                        }
-                       if (typeof((c as {certPath: unknown; }).certPath) !== 
"string") {
+                       if (typeof((c as {certPath: unknown}).certPath) !== 
"string") {
                                throw new Error("'certPath' must be a string");
                        }
                        if (!Object.prototype.hasOwnProperty.call(c, 
"keyPath")) {
                                throw new Error("'keyPath' is required to use 
SSL");
                        }
-                       if (typeof((c as {keyPath: unknown; }).keyPath) !== 
"string") {
+                       if (typeof((c as {keyPath: unknown}).keyPath) !== 
"string") {
                                throw new Error("'keyPath' must be a string");
                        }
                }
        }
 
        return true;
-       // tstlint:enable completed-docs
 }
 
 const defaultVersionFile = "/etc/traffic-portal/version.json";
@@ -212,6 +233,7 @@ const defaultVersionFile = 
"/etc/traffic-portal/version.json";
  * Defaults to /etc/traffic-portal/version.json. If this file doesn't exist,
  * the version may be deduced from the execution environment using git and
  * looking for the ATC VERSION file.
+ * @returns The parsed server version.
  */
 export function getVersion(path?: string): ServerVersion {
        if (!path) {
@@ -257,6 +279,7 @@ export function getVersion(path?: string): ServerVersion {
        return ver;
 }
 
+/** The type of command line arguments to Traffic Portal. */
 interface Args {
        trafficOps?: URL;
        insecure?: boolean;
@@ -268,6 +291,13 @@ interface Args {
 
 export const defaultConfigFile = "/etc/traffic-portal/config.js";
 
+/**
+ * Gets the configuration for the Traffic Portal server.
+ *
+ * @param args The arguments passed to Traffic Portal.
+ * @param ver The version to use for the server.
+ * @returns A full configuration for the server.
+ */
 export function getConfig(args: Args, ver: ServerVersion): ServerConfig {
        let cfg: ServerConfig = {
                insecure: false,
@@ -303,16 +333,15 @@ export function getConfig(args: Args, ver: 
ServerVersion): ServerConfig {
        if (args.trafficOps) {
                cfg.trafficOps = args.trafficOps;
        } else if (!readFromFile) {
-               if (Object.prototype.hasOwnProperty.call(process.env, 
"TO_URL")) {
-                       const envURL = (process.env as {TO_URL: string; 
}).TO_URL;
-                       try {
-                               cfg.trafficOps = new URL(envURL);
-                       } catch (e) {
-                               throw new Error(`invalid Traffic Ops URL from 
environment: ${envURL}`);
-                       }
-               } else {
+               const envURL = process.env.TO_URL;
+               if (!envURL) {
                        throw new Error("Traffic Ops URL must be specified");
                }
+               try {
+                       cfg.trafficOps = new URL(envURL);
+               } catch (e) {
+                       throw new Error(`invalid Traffic Ops URL from 
environment: ${envURL}`);
+               }
        }
 
        if (readFromFile && cfg.useSSL) {
@@ -341,6 +370,8 @@ export function getConfig(args: Args, ver: ServerVersion): 
ServerConfig {
                }
        }
 
+       cfg.insecure = args.insecure ?? cfg.insecure;
+
        if (cfg.useSSL) {
                if (!existsSync(cfg.certPath)) {
                        throw new Error(`no such certificate file: 
${cfg.certPath}`);
diff --git a/experimental/traffic-portal/server.ts 
b/experimental/traffic-portal/server.ts
index b79595478c..1f3d3ae42c 100644
--- a/experimental/traffic-portal/server.ts
+++ b/experimental/traffic-portal/server.ts
@@ -54,31 +54,22 @@ export function app(): express.Express {
                maxAge: "1y"
        }));
 
-       // All regular routes use the Universal engine
-       server.get("*", (req, res) => {
-               res.render(indexHtml, { providers: [{ provide: APP_BASE_HREF, 
useValue: req.baseUrl }], req });
-       });
-
-       server.use("/api/**", (req, res) => {
+       /**
+        * A handler for proxying the Traffic Ops API.
+        *
+        * @param req The client's request.
+        * @param res The server's response writer.
+        */
+       function toProxyHandler(req: express.Request, res: express.Response): 
void {
                console.log(`Making TO API request to \`${req.originalUrl}\``);
 
-               let origURL: URL;
-               try {
-                       origURL = new URL(req.originalUrl);
-               } catch (err) {
-                       console.error(`Failed to parse request URL 
${req.originalUrl} as a URL: ${err}`);
-                       res.statusCode = 502;
-                       res.setHeader("Content-Type", "application/json");
-                       res.write('{"alerts":[{"level":"error","text":"Traffic 
Ops is unreachable"}]}');
-                       return;
-               }
-
                const fwdRequest = {
-                       headers: req.headers,
-                       host:    config.trafficOps.hostname,
-                       method:  req.method,
-                       path:    origURL.pathname+origURL.search,
-                       port:    config.trafficOps.port,
+                       headers:            req.headers,
+                       host:               config.trafficOps.hostname,
+                       method:             req.method,
+                       path:               req.originalUrl,
+                       port:               config.trafficOps.port,
+                       rejectUnauthorized: !config.insecure,
                };
 
                try {
@@ -90,7 +81,14 @@ export function app(): express.Express {
                } catch (e) {
                        console.error("proxying request:", e);
                }
-               res.end();
+       }
+
+       server.use("api/**", toProxyHandler);
+       server.use("/api/**", toProxyHandler);
+
+       // All regular routes use the Universal engine
+       server.get("*", (req, res) => {
+               res.render(indexHtml, { providers: [{ provide: APP_BASE_HREF, 
useValue: req.baseUrl }], req });
        });
 
        server.enable("trust proxy");
@@ -126,7 +124,7 @@ function run(): number {
                }
        });
        parser.add_argument("-k", "--insecure", {
-               action: "storeTrue",
+               action: "store_true",
                help: "Skip Traffic Ops server certificate validation. This 
affects requests from Traffic Portal to Traffic Ops AND signature" +
                        " verification of any passed SSL keys/certificates"
        });
@@ -182,14 +180,14 @@ function run(): number {
                        {
                                cert,
                                key,
-                               rejectUnauthorized: !config.insecure
+                               rejectUnauthorized: !config.insecure,
                        },
                        server
                ).listen(config.port, ()=> {
                        console.log(`Node Express server listening on port 
${config.port}`);
                });
                try {
-                       createRedirectServer(
+                       const redirectServer = createRedirectServer(
                                (req, res) => {
                                        if (!req.url) {
                                                res.statusCode = 500;
@@ -198,10 +196,18 @@ function run(): number {
                                                return;
                                        }
                                        res.statusCode = 308;
-                                       res.setHeader("Location", 
req.url.replace(/^[hH][tT][tT][pP]:/, "https"));
+                                       res.setHeader("Location", 
req.url.replace(/^[hH][tT][tT][pP]:/, "https:"));
                                        res.end();
                                }
-                       ).listen(80);
+                       );
+                       redirectServer.listen(80);
+                       redirectServer.on("error", e => {
+                               console.error(`redirect server encountered 
error: ${e}`);
+                               if (Object.prototype.hasOwnProperty.call(e, 
"code") && (e as typeof e & {code: unknown}).code === "EACCES") {
+                                       console.warn("access to port 80 not 
allowed; closing redirect server");
+                                       redirectServer.close();
+                               }
+                       });
                } catch (e) {
                        console.warn("Failed to initialize HTTP-to-HTTPS 
redirect listener:", e);
                }
@@ -223,7 +229,10 @@ declare const __non_webpack_require__: NodeRequire;
 const mainModule = __non_webpack_require__.main;
 const moduleFilename = mainModule && mainModule.filename || "";
 if (moduleFilename === __filename || moduleFilename.includes("iisnode")) {
-       process.exit(run());
+       const code = run();
+       if (code) {
+               process.exit(code);
+       }
 }
 
 export * from "./src/main.server";
diff --git 
a/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.ts 
b/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.ts
index 9f8192310d..a2129d58fb 100644
--- 
a/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.ts
+++ 
b/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.ts
@@ -82,7 +82,7 @@ export class TenantsComponent implements OnInit, OnDestroy {
        ];
 
        public loading = true;
-       private subscription!: Subscription;
+       private readonly subscription: Subscription;
 
        constructor(
                private readonly userService: UserService,
@@ -90,6 +90,11 @@ export class TenantsComponent implements OnInit, OnDestroy {
                private readonly headerSvc: TpHeaderService
        ) {
                this.headerSvc.headerTitle.next("Tenant");
+               this.subscription = this.auth.userChanged.subscribe(
+                       () => {
+                               this.loadContextMenuItems();
+                       }
+               );
        }
 
        /**
@@ -131,11 +136,6 @@ export class TenantsComponent implements OnInit, OnDestroy 
{
        public async ngOnInit(): Promise<void> {
                this.tenants = await this.userService.getTenants();
                this.tenantMap = Object.fromEntries((this.tenants).map(t => 
[t.id, t]));
-               this.subscription = this.auth.userChanged.subscribe(
-                       () => {
-                               this.loadContextMenuItems();
-                       }
-               );
                this.loadContextMenuItems();
                this.loading = false;
        }

Reply via email to