#!/usr/bin/python3
import os
import argparse

from common.utils import (
    exit_if_no_permission,
    exit_if_stopped,
    is_cluster_locked,
    is_ha_enabled,
    get_dqlite_info,
    wait_for_ready,
    is_cluster_ready,
    get_available_addons,
    get_current_arch,
    get_addon_by_name,
    kubectl_get,
    kubectl_get_clusterroles,
)


def is_enabled(addon, item):
    if addon in item:
        return True
    else:
        filepath = os.path.expandvars(addon)
        return os.path.isfile(filepath)

    return False


def print_short(isReady, enabled_addons, disabled_addons):
    if isReady:
        print("microk8s is running")
        print("addons:")
        if enabled_addons and len(enabled_addons) > 0:
            for enabled in enabled_addons:
                print("{}: enabled".format(enabled["name"]))
        if disabled_addons and len(disabled_addons) > 0:
            for disabled in disabled_addons:
                print("{}: disabled".format(disabled["name"]))
    else:
        print("microk8s is not running. Use microk8s inspect for a deeper inspection.")


def print_pretty(isReady, enabled_addons, disabled_addons):
    console_formatter = "{:>3} {:<20} # {}"
    if isReady:
        print("microk8s is running")
        if not is_ha_enabled():
            print("high-availability: no")
        else:
            info = get_dqlite_info()
            if ha_cluster_formed(info):
                print("high-availability: yes")
            else:
                print("high-availability: no")

            masters = "none"
            standby = "none"
            for node in info:
                if node[1] == "voter":
                    if masters == "none":
                        masters = "{}".format(node[0])
                    else:
                        masters = "{} {}".format(masters, node[0])
                if node[1] == "standby":
                    if standby == "none":
                        standby = "{}".format(node[0])
                    else:
                        standby = "{} {}".format(standby, node[0])

            print("{:>2}{} {}".format("", "datastore master nodes:", masters))
            print("{:>2}{} {}".format("", "datastore standby nodes:", standby))

        print("addons:")
        if enabled_addons and len(enabled_addons) > 0:
            print("{:>2}{}".format("", "enabled:"))
            for enabled in enabled_addons:
                print(console_formatter.format("", enabled["name"], enabled["description"]))
        if disabled_addons and len(disabled_addons) > 0:
            print("{:>2}{}".format("", "disabled:"))
            for disabled in disabled_addons:
                print(console_formatter.format("", disabled["name"], disabled["description"]))
    else:
        print("microk8s is not running. Use microk8s inspect for a deeper inspection.")


def print_short_yaml(isReady, enabled_addons, disabled_addons):
    print("microk8s:")
    print("{:>2}{} {}".format("", "running:", isReady))

    if isReady:
        print("addons:")
        for enabled in enabled_addons:
            print("  {}: enabled".format(enabled["name"]))

        for disabled in disabled_addons:
            print("  {}: disabled".format(disabled["name"]))
    else:
        print(
            "{:>2}{} {}".format(
                "",
                "message:",
                "microk8s is not running. Use microk8s inspect for a deeper inspection.",
            )
        )


def print_yaml(isReady, enabled_addons, disabled_addons):
    print("microk8s:")
    print("{:>2}{} {}".format("", "running:", isReady))

    print("{:>2}".format("high-availability:"))
    ha_enabled = is_ha_enabled()
    print("{:>2}{} {}".format("", "enabled:", ha_enabled))
    if ha_enabled:
        info = get_dqlite_info()
        print("{:>2}{}".format("", "nodes:"))
        for node in info:
            print("{:>6}address: {:<1}".format("- ", node[0]))
            print("{:>6}role: {:<1}".format("", node[1]))

    if isReady:
        print("{:>2}".format("addons:"))
        for enabled in enabled_addons:
            print("{:>4}name: {:<1}".format("- ", enabled["name"]))
            print("{:>4}description: {:<1}".format("", enabled["description"]))
            print("{:>4}version: {:<1}".format("", enabled["version"]))
            print("{:>4}status: enabled".format(""))

        for disabled in disabled_addons:
            print("{:>4}name: {:<1}".format("- ", disabled["name"]))
            print("{:>4}description: {:<1}".format("", disabled["description"]))
            print("{:>4}version: {:<1}".format("", disabled["version"]))
            print("{:>4}status: disabled".format(""))
    else:
        print(
            "{:>2}{} {}".format(
                "",
                "message:",
                "microk8s is not running. Use microk8s inspect for a deeper inspection.",
            )
        )


def print_addon_status(enabled):
    if len(enabled) > 0:
        print("enabled")
    else:
        print("disabled")


def get_status(available_addons, isReady):
    enabled = []
    disabled = []
    if isReady:
        kube_output = kubectl_get("all")
        cluster_output = kubectl_get_clusterroles()
        kube_output = kube_output + cluster_output
        for addon in available_addons:
            found = False
            for row in kube_output.split("\n"):
                if is_enabled(addon["check_status"], row):
                    enabled.append(addon)
                    found = True
                    break
            if not found:
                disabled.append(addon)

    return enabled, disabled


def ha_cluster_formed(info):
    voters = 0
    for node in info:
        if node[1] == "voter":
            voters += 1
    ha_formed = False
    if voters > 2:
        ha_formed = True
    return ha_formed


if __name__ == "__main__":
    exit_if_no_permission()
    exit_if_stopped()
    is_cluster_locked()

    # initiate the parser with a description
    parser = argparse.ArgumentParser(
        description="Microk8s cluster status check.", prog="microk8s status"
    )
    parser.add_argument(
        "--format",
        help="print cluster and addon status, output can be in yaml, pretty or short",
        default="pretty",
        choices={"pretty", "yaml", "short"},
    )
    parser.add_argument(
        "-w", "--wait-ready", action="store_true", help="wait until the cluster is in ready state"
    )
    parser.add_argument(
        "-t",
        "--timeout",
        help="specify a timeout in seconds when waiting for the cluster to be ready.",
        type=int,
        default=0,
    )
    parser.add_argument("-a", "--addon", help="check the status of an addon.", default="all")
    parser.add_argument(
        "--yaml", action="store_true", help="DEPRECATED, use '--format yaml' instead"
    )

    # read arguments from the command line
    args = parser.parse_args()

    wait_ready = args.wait_ready
    timeout = args.timeout
    yaml_short = args.yaml

    if wait_ready:
        isReady = wait_for_ready(timeout)
    else:
        isReady = is_cluster_ready()

    available_addons = get_available_addons(get_current_arch())

    if args.addon != "all":
        available_addons = get_addon_by_name(available_addons, args.addon)

    enabled, disabled = get_status(available_addons, isReady)

    if args.addon != "all":
        print_addon_status(enabled)
    else:
        if args.format == "yaml":
            print_yaml(isReady, enabled, disabled)
        elif args.format == "short":
            print_short(isReady, enabled, disabled)
        else:
            if yaml_short:
                print_short_yaml(isReady, enabled, disabled)
            else:
                print_pretty(isReady, enabled, disabled)

scripts/wrappers/status.py
#!/usr/bin/python3
import os
import argparse

from common.utils import (
    exit_if_no_permission,
    exit_if_stopped,
    is_cluster_locked,
    is_ha_enabled,
    get_dqlite_info,
    wait_for_ready,
    is_cluster_ready,
    get_available_addons,
    get_current_arch,
    get_addon_by_name,
    kubectl_get,
    kubectl_get_clusterroles,
)


def is_enabled(addon, item):
    if addon in item:
        return True
    else:
        filepath = os.path.expandvars(addon)
        return os.path.isfile(filepath)

    return False


def print_short(isReady, enabled_addons, disabled_addons):
    if isReady:
        print("microk8s is running")
        print("addons:")
        if enabled_addons and len(enabled_addons) > 0:
            for enabled in enabled_addons:
                print("{}: enabled".format(enabled["name"]))
        if disabled_addons and len(disabled_addons) > 0:
            for disabled in disabled_addons:
                print("{}: disabled".format(disabled["name"]))
    else:
        print("microk8s is not running. Use microk8s inspect for a deeper inspection.")


def print_pretty(isReady, enabled_addons, disabled_addons):
    console_formatter = "{:>3} {:<20} # {}"
    if isReady:
        print("microk8s is running")
        if not is_ha_enabled():
            print("high-availability: no")
        else:
            info = get_dqlite_info()
            if ha_cluster_formed(info):
                print("high-availability: yes")
            else:
                print("high-availability: no")

            masters = "none"
            standby = "none"
            for node in info:
                if node[1] == "voter":
                    if masters == "none":
                        masters = "{}".format(node[0])
                    else:
                        masters = "{} {}".format(masters, node[0])
                if node[1] == "standby":
                    if standby == "none":
                        standby = "{}".format(node[0])
                    else:
                        standby = "{} {}".format(standby, node[0])

            print("{:>2}{} {}".format("", "datastore master nodes:", masters))
            print("{:>2}{} {}".format("", "datastore standby nodes:", standby))

        print("addons:")
        if enabled_addons and len(enabled_addons) > 0:
            print("{:>2}{}".format("", "enabled:"))
            for enabled in enabled_addons:
                print(console_formatter.format("", enabled["name"], enabled["description"]))
        if disabled_addons and len(disabled_addons) > 0:
            print("{:>2}{}".format("", "disabled:"))
            for disabled in disabled_addons:
                print(console_formatter.format("", disabled["name"], disabled["description"]))
    else:
        print("microk8s is not running. Use microk8s inspect for a deeper inspection.")


def print_short_yaml(isReady, enabled_addons, disabled_addons):
    print("microk8s:")
    print("{:>2}{} {}".format("", "running:", isReady))

    if isReady:
        print("addons:")
        for enabled in enabled_addons:
            print("  {}: enabled".format(enabled["name"]))

        for disabled in disabled_addons:
            print("  {}: disabled".format(disabled["name"]))
    else:
        print(
            "{:>2}{} {}".format(
                "",
                "message:",
                "microk8s is not running. Use microk8s inspect for a deeper inspection.",
            )
        )


def print_yaml(isReady, enabled_addons, disabled_addons):
    print("microk8s:")
    print("{:>2}{} {}".format("", "running:", isReady))

    print("{:>2}".format("high-availability:"))
    ha_enabled = is_ha_enabled()
    print("{:>2}{} {}".format("", "enabled:", ha_enabled))
    if ha_enabled:
        info = get_dqlite_info()
        print("{:>2}{}".format("", "nodes:"))
        for node in info:
            print("{:>6}address: {:<1}".format("- ", node[0]))
            print("{:>6}role: {:<1}".format("", node[1]))

    if isReady:
        print("{:>2}".format("addons:"))
        for enabled in enabled_addons:
            print("{:>4}name: {:<1}".format("- ", enabled["name"]))
            print("{:>4}description: {:<1}".format("", enabled["description"]))
            print("{:>4}version: {:<1}".format("", enabled["version"]))
            print("{:>4}status: enabled".format(""))

        for disabled in disabled_addons:
            print("{:>4}name: {:<1}".format("- ", disabled["name"]))
            print("{:>4}description: {:<1}".format("", disabled["description"]))
            print("{:>4}version: {:<1}".format("", disabled["version"]))
            print("{:>4}status: disabled".format(""))
    else:
        print(
            "{:>2}{} {}".format(
                "",
                "message:",
                "microk8s is not running. Use microk8s inspect for a deeper inspection.",
            )
        )


def print_addon_status(enabled):
    if len(enabled) > 0:
        print("enabled")
    else:
        print("disabled")


def get_status(available_addons, isReady):
    enabled = []
    disabled = []
    if isReady:
        # 'all' does not include ingress
        kube_output = kubectl_get("all,ingress")
        cluster_output = kubectl_get_clusterroles()
        kube_output = kube_output + cluster_output
        for addon in available_addons:
            found = False
            for row in kube_output.split("\n"):
                if is_enabled(addon["check_status"], row):
                    enabled.append(addon)
                    found = True
                    break
            if not found:
                disabled.append(addon)

    return enabled, disabled


def ha_cluster_formed(info):
    voters = 0
    for node in info:
        if node[1] == "voter":
            voters += 1
    ha_formed = False
    if voters > 2:
        ha_formed = True
    return ha_formed


if __name__ == "__main__":
    exit_if_no_permission()
    exit_if_stopped()
    is_cluster_locked()

    # initiate the parser with a description
    parser = argparse.ArgumentParser(
        description="Microk8s cluster status check.", prog="microk8s status"
    )
    parser.add_argument(
        "--format",
        help="print cluster and addon status, output can be in yaml, pretty or short",
        default="pretty",
        choices={"pretty", "yaml", "short"},
    )
    parser.add_argument(
        "-w", "--wait-ready", action="store_true", help="wait until the cluster is in ready state"
    )
    parser.add_argument(
        "-t",
        "--timeout",
        help="specify a timeout in seconds when waiting for the cluster to be ready.",
        type=int,
        default=0,
    )
    parser.add_argument("-a", "--addon", help="check the status of an addon.", default="all")
    parser.add_argument(
        "--yaml", action="store_true", help="DEPRECATED, use '--format yaml' instead"
    )

    # read arguments from the command line
    args = parser.parse_args()

    wait_ready = args.wait_ready
    timeout = args.timeout
    yaml_short = args.yaml

    if wait_ready:
        isReady = wait_for_ready(timeout)
    else:
        isReady = is_cluster_ready()

    available_addons = get_available_addons(get_current_arch())

    if args.addon != "all":
        available_addons = get_addon_by_name(available_addons, args.addon)

    enabled, disabled = get_status(available_addons, isReady)

    if args.addon != "all":
        print_addon_status(enabled)
    else:
        if args.format == "yaml":
            print_yaml(isReady, enabled, disabled)
        elif args.format == "short":
            print_short(isReady, enabled, disabled)
        else:
            if yaml_short:
                print_short_yaml(isReady, enabled, disabled)
            else:
                print_pretty(isReady, enabled, disabled)

scripts/wrappers/status.py
diff --git a/microk8s-resources/actions/disable.dashboard-ingress.sh b/microk8s-resources/actions/disable.dashboard-ingress.sh
new file mode 100755
index 0000000000..67b9fec61f
--- /dev/null
+++ b/microk8s-resources/actions/disable.dashboard-ingress.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+import os
+import subprocess
+
+import click
+
+@click.command()
+def dashboard_ingress():
+    click.echo("Disabling Ingress for Kubernetes Dashboard")
+
+    env = os.environ.copy()
+    env["PATH"] += ":%s" % os.environ["SNAP"]  
+
+    resources = [
+        "secret/kubernetes-dashboard-basic-auth",
+        "ingress.networking.k8s.io/kubernetes-dashboard-ingress"
+    ] 
+
+    for resource in resources:
+        click.echo(f"Destroying {resource}...")
+        try:
+            subprocess.check_call(
+                ["microk8s-kubectl.wrapper", "delete", "-n", "kube-system", resource],
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                env=env,
+            )
+        except subprocess.CalledProcessError:
+            pass
+
+    click.echo("Destruction complete.")
+    click.echo("Ingress for Kubernetes Dashboard is disabled")
+
+if __name__ == "__main__":
+    dashboard_ingress(prog_name="microk8s disable dashboard-ingress")
diff --git a/microk8s-resources/actions/enable.dashboard-ingress.sh b/microk8s-resources/actions/enable.dashboard-ingress.sh
new file mode 100755
index 0000000000..7b3ef5437d
--- /dev/null
+++ b/microk8s-resources/actions/enable.dashboard-ingress.sh
@@ -0,0 +1,197 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+import subprocess
+import crypt
+import random
+import json
+import string
+import tempfile
+from base64 import b64encode
+
+from ipaddress import ip_address
+import click
+
+
+def run(*args, die=True, debug=False, stdout=True):
+    # Add wrappers to $PATH
+    env = os.environ.copy()
+    env["PATH"] += ":%s" % os.environ["SNAP"]
+
+    if debug and stdout:
+        print("\033[;1;32m+ %s\033[;0;0m" % " ".join(args))
+
+    result = subprocess.run(
+        args,
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        env=env,
+    )
+
+    try:
+        result.check_returncode()
+    except subprocess.CalledProcessError as err:
+        if die:
+            print("Ingress for Kubernetes Dashboard could not be enabled:")
+            if result.stderr:
+                print(result.stderr.decode("utf-8"))
+            print(err)
+            sys.exit(1)
+        else:
+            raise
+
+    result_stdout = result.stdout.decode("utf-8")
+
+    if debug and stdout:
+        print(result_stdout)
+        if result.stderr:
+            print(result.stderr.decode("utf-8"))
+
+    return result_stdout
+
+
+def htpasswd(pw, salt_length=32) -> str:
+    """Generates an htpasswd hash from provided password pw."""
+    return crypt.crypt(pw, get_random_pass(salt_length))
+
+
+def valid_ip(ip) -> bool:
+    """Validates whether the provided IP is formatted correctly."""
+    try:
+        _ = ip_address(ip)
+        return True
+    except ValueError:
+        return False
+
+
+def enable_addons(debug=False, addons=("ingress", "dashboard")):
+    for addon in addons:
+        print("Enabling %s..." % addon)
+        run("microk8s-enable.wrapper", addon, debug=debug)
+
+
+def get_random_pass(length=32) -> str:
+    """Generates a random password."""
+    return "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))
+
+
+@click.command()
+@click.option(
+    "--hostname", "-n",
+    default="kubernetes-dashboard.127.0.0.1.nip.io",
+    help="Sets the virtual hostname assigned to Kubernetes Dashboard."
+)
+@click.option(
+    "--allow", "-a",
+    multiple=True,
+    default=['127.0.0.1/8', '169.254.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'],
+    help="Allow a subnet to access the Kubernetes Dashboard."
+)
+@click.option(
+    "--auth",
+    is_flag=True,
+    help="If true, enables HTTP basic authentication."
+)
+@click.option(
+    "--auth-user",
+    default="admin",
+    help="The username for HTTP basic authentication"
+)
+@click.password_option(
+    envvar="DASHBOARD_INGRESS_AUTH_PASSWORD",
+    default=get_random_pass,
+    prompt=False,
+    help="The password for HTTP basic authentication."
+)
+def dashboard_ingress(hostname, allow, auth, auth_user, password):
+    click.echo("Enabling Ingress for Kubernetes Dashboard")
+    enable_addons(addons=("ingress", "dashboard"))
+
+    click.echo("Applying manifest")
+    manifest = {
+        "apiVersion": "networking.k8s.io/v1",
+        "kind": "Ingress",
+        "metadata": {
+            "name": "kubernetes-dashboard-ingress",
+            "namespace": "kube-system",
+            "annotations": {
+                "kubernetes.io/ingress.class": "public",
+
+                # restrict to private network scopes
+                "nginx.ingress.kubernetes.io/whitelist-source-range": ",".join(allow),
+
+                "nginx.ingress.kubernetes.io/force-ssl-redirect": "true",
+                "nginx.ingress.kubernetes.io/ssl-passthrough": "true",
+                "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS"
+            }
+        },
+        "spec": {
+            "rules": [{
+                "host": hostname,
+                "http": {
+                    "paths": [{
+                        "path": "/",
+                        "pathType": "Prefix",
+                        "backend": {
+                            "service": {
+                                "name": "kubernetes-dashboard",
+                                "port": {"number": 443}
+                            }
+                        }
+                    }]
+                }
+
+            }]
+        }
+    }
+
+    if auth:
+        click.echo("Configuring authentication")
+        encoded_auth = b64encode(f"{auth_user}:{htpasswd(password)}".encode("utf-8")).decode("utf-8")
+
+        with tempfile.NamedTemporaryFile(mode="w+") as f:
+            json.dump({
+                "apiVersion": "v1",
+                "kind": "Secret",
+                "metadata": {
+                    "name": "kubernetes-dashboard-basic-auth",
+                    "namespace": "kube-system"
+                },
+                "data": {"auth": encoded_auth},
+                "type": "Opaque"
+            }, f)
+            f.flush()
+
+            run(
+                "microk8s-kubectl.wrapper",
+                "apply",
+                "-f",
+                f.name,
+                die=False,
+                stdout=False
+            )
+
+        manifest["metadata"]["annotations"].update({
+            "nginx.ingress.kubernetes.io/auth-type": "basic",
+            "nginx.ingress.kubernetes.io/auth-secret": "kubernetes-dashboard-basic-auth",
+            "nginx.ingress.kubernetes.io/auth-realm": "Authentication Required - Kubernetes Dashboard"
+        })
+
+    with tempfile.NamedTemporaryFile(mode="w+") as f:
+        json.dump(manifest, f)
+        f.flush()
+
+        run("microk8s-kubectl.wrapper", "apply", "-f", f.name)
+
+    click.echo("Ingress for Kubernetes Dashboard is enabled.")
+    click.echo(f"\nDashboard will be available at https://{hostname}")
+
+    if auth:
+        click.echo("HTTP basic authentication login:")
+        click.echo(f"Username: {auth_user}")
+        click.echo(f"Password: {password}")
+
+if __name__ == "__main__":
+    dashboard_ingress(prog_name="microk8s enable dashboard-ingress")
diff --git a/microk8s-resources/wrappers/addon-lists.yaml b/microk8s-resources/wrappers/addon-lists.yaml
index a7573504f3..eb34937dc8 100644
--- a/microk8s-resources/wrappers/addon-lists.yaml
+++ b/microk8s-resources/wrappers/addon-lists.yaml
@@ -28,6 +28,14 @@ microk8s-addons:
         - amd64
         - s390x
 
+    - name: "dashboard-ingress"
+      description: "Ingress definition for Kubernetes dashboard"
+      version: "1.0.0"
+      check_status: "ingress.networking.k8s.io/kubernetes-dashboard-ingress"
+      supported_architectures:
+        - arm64
+        - amd64
+
     - name: "ingress"
       description: "Ingress controller for external access"
       version: "1.0.0-alpha.2"
diff --git a/scripts/wrappers/status.py b/scripts/wrappers/status.py
index 83cd7d7eb4..dc62c97244 100755
--- a/scripts/wrappers/status.py
+++ b/scripts/wrappers/status.py
@@ -154,7 +154,8 @@ def get_status(available_addons, isReady):
     enabled = []
     disabled = []
     if isReady:
-        kube_output = kubectl_get("all")
+        # 'all' does not include ingress
+        kube_output = kubectl_get("all,ingress")
         cluster_output = kubectl_get_clusterroles()
         kube_output = kube_output + cluster_output
         for addon in available_addons:
diff --git a/tests/test-addons.py b/tests/test-addons.py
index b5d4f603fc..bccf60caa5 100644
--- a/tests/test-addons.py
+++ b/tests/test-addons.py
@@ -6,6 +6,7 @@
 
 from validators import (
     validate_dns_dashboard,
+    validate_dashboard_ingress,
     validate_storage,
     validate_ingress,
     validate_ambassador,
@@ -126,6 +127,12 @@ def test_basic(self):
         microk8s_enable("dashboard")
         print("Validating dashboard")
         validate_dns_dashboard()
+        print("Enabling dashboard-ingress")
+        microk8s_enable("dashboard-ingress")
+        print("Validating dashboard-ingress")
+        validate_dashboard_ingress()
+        print("Disabling dashboard-ingress")
+        microk8s_disable("dashboard-ingress")
         print("Enabling storage")
         microk8s_enable("storage")
         print("Validating storage")
diff --git a/tests/validators.py b/tests/validators.py
index 21fd222dff..67e0258e35 100644
--- a/tests/validators.py
+++ b/tests/validators.py
@@ -42,6 +42,29 @@ def validate_dns_dashboard():
     assert attempt > 0
 
 
+def validate_dashboard_ingress():
+    """
+    Validate the ingress for dashboard addon by trying to access the kubernetes dashboard
+    using ingress ports. The dashboard will return HTTP 200 and HTML indicating that it is
+    up and running.
+    """
+    service_ok = False
+    attempt = 50
+    while attempt >= 0:
+        try:
+            resp = requests.get(
+                "https://kubernetes-dashboard.127.0.0.1.nip.io/#/login", verify=False
+            )
+            if resp.status_code == 200 and "Kubernetes Dashboard" in resp.content.decode("utf-8"):
+                service_ok = True
+                break
+        except requests.RequestException:
+            time.sleep(5)
+            attempt -= 1
+
+    assert service_ok
+
+
 def validate_storage():
     """
     Validate storage by creating a PVC.

[dashboard] should be exposed via ingress
When running microk8s on the server, rather than doing port forwarding it should be possible to access the dashboard via ingress (similar to kubeflow dashboard)
