Skip to content

Commit

Permalink
Include (base) dashboard in the builtin-webserver (#593)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Hjelmare <[email protected]>
  • Loading branch information
marcelveldt and MartinHjelmare authored Feb 28, 2024
1 parent 7eeb288 commit 44679f2
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 25 deletions.
31 changes: 21 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: Publish releases
on:
release:
types: [published]
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: "18.x"

jobs:
build-and-publish-pypi:
Expand All @@ -28,10 +31,14 @@ jobs:
exit 1
fi
fi
- name: Set up Python 3.10
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/[email protected]
with:
python-version: "3.10"
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install build
run: >-
pip install build tomli tomli-w
Expand All @@ -48,7 +55,13 @@ jobs:
with open("pyproject.toml", "wb") as f:
tomli_w.dump(pyproject, f)
- name: Build
- name: Build dashboard
run: |
cd dashboard
script/setup
script/build
cd ..
- name: Build python package
run: >-
python3 -m build
- name: Publish release to PyPI
Expand All @@ -69,9 +82,9 @@ jobs:
- name: Log in to the GitHub container registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/[email protected]
- name: Version number for tags
Expand All @@ -95,8 +108,7 @@ jobs:
ghcr.io/${{ github.repository_owner }}/python-matter-server:${{ steps.tags.outputs.major }},
ghcr.io/${{ github.repository_owner }}/python-matter-server:stable
push: true
build-args:
"PYTHON_MATTER_SERVER=${{ needs.build-and-publish-pypi.outputs.version }}"
build-args: "PYTHON_MATTER_SERVER=${{ needs.build-and-publish-pypi.outputs.version }}"
- name: Build and Push pre-release
uses: docker/[email protected]
if: github.event.release.prerelease == true
Expand All @@ -108,5 +120,4 @@ jobs:
ghcr.io/${{ github.repository_owner }}/python-matter-server:${{ steps.tags.outputs.patch }},
ghcr.io/${{ github.repository_owner }}/python-matter-server:beta
push: true
build-args:
"PYTHON_MATTER_SERVER=${{ needs.build-and-publish-pypi.outputs.version }}"
build-args: "PYTHON_MATTER_SERVER=${{ needs.build-and-publish-pypi.outputs.version }}"
17 changes: 10 additions & 7 deletions dashboard/src/entrypoint/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { MatterClient } from "../client/client";
async function main() {
import("../pages/matter-dashboard-app");

// Turn httpX url into wsX url and append "/ws"
let url = "ws" + new URL("./ws", location.href).toString().substring(4);

// Inside Home Assistant ingress, we will not prompt for the URL
if (!location.pathname.endsWith("/ingress")) {
let url = "";
// Detect if we're running in the (production) webserver included in the matter server or not.
if (location.href.includes(":5580")) {
// production server running inside the matter server
// Turn httpX url into wsX url and append "/ws"
url = "ws" + new URL("./ws", location.href).toString().substring(4);
} else {
// development server, ask for url to matter server
let storageUrl = localStorage.getItem("matterURL");
if (!storageUrl) {
storageUrl = prompt(
"Enter Matter URL",
"ws://homeassistant.local:5580/ws"
"Enter Websocket URL to a running Matter Server",
"ws://localhost:5580/ws"
);
if (!storageUrl) {
alert("Unable to connect without URL");
Expand Down
13 changes: 9 additions & 4 deletions dashboard/src/pages/matter-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@ class MatterDashboard extends LitElement {

render() {
const nodes = this.nodeEntries(this.nodes);
const isProductionServer = location.href.includes(":5580")

return html`
<div class="header">
<div>Python Matter Server</div>
<div class="actions">
${isProductionServer
? ""
: html`
<md-icon-button @click=${this._disconnect}>
<ha-svg-icon .path=${mdiLogout}></ha-svg-icon>
</md-icon-button>
`}
</div>
</div>
<div class="container">
Expand All @@ -50,20 +55,20 @@ class MatterDashboard extends LitElement {
</md-list-item>
<md-divider></md-divider>
${nodes.map(([id, node]) => {
return html`
return html`
<md-list-item type="link" href=${`#node/${node.node_id}`}>
<span slot="start">${node.node_id}</span>
<div slot="headline">
${node.vendorName} ${node.productName}
${node.available
? ""
: html`<span class="status">OFFLINE</span>`}
? ""
: html`<span class="status">OFFLINE</span>`}
</div>
<div slot="supporting-text">${node.serialNumber}</div>
<ha-svg-icon slot="end" .path=${mdiChevronRight}></ha-svg-icon>
</md-list-item>
`;
})}
})}
</md-list>
</div>
<div class="footer">
Expand Down
31 changes: 30 additions & 1 deletion matter_server/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from __future__ import annotations

import asyncio
from functools import partial
import ipaddress
import logging
import os
from pathlib import Path
from typing import Any, Callable, Set, cast
import weakref

Expand All @@ -31,6 +34,9 @@
from .storage import StorageController
from .vendor_info import VendorInfo

DASHBOARD_DIR = Path(__file__).parent.joinpath("../../dashboard/dist/web/").resolve()
DASHBOARD_DIR_EXISTS = DASHBOARD_DIR.exists()


def mount_websocket(server: MatterServer, path: str) -> None:
"""Mount the websocket endpoint."""
Expand Down Expand Up @@ -106,7 +112,23 @@ async def start(self) -> None:
await self.device_controller.start()
await self.vendor_info.start()
mount_websocket(self, "/ws")
self.app.router.add_route("GET", "/", self._handle_info)
self.app.router.add_route("GET", "/info", self._handle_info)

# Host dashboard if the prebuilt files are detected
if DASHBOARD_DIR_EXISTS:
dashboard_dir = str(DASHBOARD_DIR)
self.logger.debug("Detected dashboard files on %s", dashboard_dir)
for abs_dir, _, files in os.walk(dashboard_dir):
rel_dir = abs_dir.replace(dashboard_dir, "")
for filename in files:
filepath = os.path.join(abs_dir, filename)
handler = partial(self._serve_static, filepath)
if rel_dir == "" and filename == "index.html":
route_path = "/"
else:
route_path = f"{rel_dir}/{filename}"
self.app.router.add_route("GET", route_path, handler)

self._runner = web.AppRunner(self.app, access_log=None)
await self._runner.setup()
self._http = MultiHostTCPSite(
Expand Down Expand Up @@ -233,3 +255,10 @@ async def _handle_info(self, request: web.Request) -> web.Response:
"""Handle info endpoint to serve basic server (version) info."""
# pylint: disable=unused-argument
return web.json_response(self.get_info(), dumps=json_dumps)

async def _serve_static(
self, file_path: str, _request: web.Request
) -> web.FileResponse:
"""Serve file response."""
headers = {"Cache-Control": "no-cache"}
return web.FileResponse(file_path, headers=headers)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ zip-safe = false
matter_server = ["py.typed"]

[tool.setuptools.packages.find]
include = ["matter_server*"]
include = ["matter_server*", "dashboard/dist/web*"]

[tool.mypy]
check_untyped_defs = true
Expand Down
4 changes: 2 additions & 2 deletions tests/server/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ async def test_server_start(
assert application.call_count == 1
application_instance = application.return_value
add_route = application_instance.router.add_route
assert add_route.call_count == 2
assert add_route.call_count >= 2
assert add_route.call_args_list[0][0][0] == "GET"
assert add_route.call_args_list[0][0][1] == "/ws"
assert add_route.call_args_list[1][0][0] == "GET"
assert add_route.call_args_list[1][0][1] == "/"
assert add_route.call_args_list[1][0][1] == "/info"
assert app_runner.call_count == 1
assert app_runner.return_value.setup.call_count == 1
assert multi_host_tcp_site.call_count == 1
Expand Down

0 comments on commit 44679f2

Please sign in to comment.