Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ARG ML_WEB_ROOT_PATH
ARG ML_OR_KEYCLOAK_URL
ARG ML_OR_URL

# Run the front-end bundle in production mode
RUN ML_SERVICE_URL=${ML_SERVICE_URL:-/services/ml-forecast} \
ML_WEB_ROOT_PATH=${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} \
ML_OR_KEYCLOAK_URL=${ML_OR_KEYCLOAK_URL:-/auth} \
Expand All @@ -31,24 +32,27 @@ RUN echo "Starting Python build phase..."
WORKDIR /app

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
PYTHONDONTWRITEBYTECODE=1

RUN echo "Installing Python build dependencies..."
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy project files
COPY pyproject.toml README.md ./
# Install uv
RUN pip install uv

# Copy the necessary project files
COPY pyproject.toml uv.lock README.md ./
COPY src/ ./src/
COPY scripts/ ./scripts/
COPY packages/ ./packages/

# Install project dependencies and clean up
# Install project dependencies using uv
RUN echo "Installing Python project dependencies..."
RUN pip install --no-cache-dir . \
RUN uv sync --no-cache-dir \
&& apt-get remove -y build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
Expand All @@ -67,27 +71,29 @@ ARG ML_ENVIRONMENT
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/app/src \
# Use the ARG, falling back to a default if not provided during build or runtime
ML_ENVIRONMENT=${ML_ENVIRONMENT:-production}

# Install runtime dependencies and clean up
# Install any runtime dependencies
RUN echo "Installing runtime dependencies..."
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy installed packages from builder
RUN echo "Copying Python packages from builder..."
COPY --from=builder /usr/local/lib/python3.13/site-packages/ /usr/local/lib/python3.13/site-packages/
COPY --from=builder /usr/local/bin/ /usr/local/bin/
# Install uv package manager
RUN pip install uv

# Copy application code
COPY pyproject.toml ./
COPY pyproject.toml uv.lock README.md ./
COPY src/ ./src/
COPY scripts/ ./scripts/
COPY packages/ ./packages/

# Install project dependencies using uv
RUN echo "Installing Python project dependencies..."
RUN uv sync --no-cache-dir

# Copy frontend build and clean up
# Copy frontend build artifacts
RUN echo "Copying frontend build artifacts..."
COPY --from=frontend-builder /app/frontend/dist/ ./deployment/web/dist/
RUN rm -rf /app/frontend
Expand All @@ -100,9 +106,9 @@ RUN mkdir -p ./deployment/data/models ./deployment/data/configs
EXPOSE 8000

# Add health check
HEALTHCHECK --interval=5s --timeout=5s --start-period=30s --retries=3 CMD curl --fail --silent http://localhost:8000/ui || exit 1
HEALTHCHECK --interval=10s --timeout=10s --start-period=30s --retries=3 CMD curl --fail --silent http://localhost:8000/ui || exit 1

RUN echo "Container setup complete! Starting application..."

# Run the application
CMD ["python", "-m", "service_ml_forecast.main"]
# Run the application using uv run to ensure virtual environment is activated
CMD ["uv", "run", "python", "-m", "service_ml_forecast.main"]
1 change: 0 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

/* Outlet */
#outlet {
padding: 20px;
min-width: fit-content;
}
</style>
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"serve": "cross-env rspack serve",
"build:dev": "cross-env ML_OR_KEYCLOAK_URL=${ML_OR_KEYCLOAK_URL:-http://localhost:8081/auth} ML_OR_URL=${ML_OR_URL:-http://localhost:8080} ML_SERVICE_URL=${ML_SERVICE_URL:-/services/ml-forecast} ML_WEB_ROOT_PATH=${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} rspack build --mode development",
"build:prod": "cross-env ML_OR_KEYCLOAK_URL=${ML_OR_KEYCLOAK_URL:-/auth} ML_OR_URL=${ML_OR_URL} ML_SERVICE_URL=${ML_SERVICE_URL:-/services/ml-forecast} ML_WEB_ROOT_PATH=${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} rspack build --mode production",
"build:analyze": "rspack build --mode production --analyze",
"lint": "eslint && prettier . --check",
Expand Down
2 changes: 1 addition & 1 deletion frontend/rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default {
port: 8001,
historyApiFallback: true,
hot: true,
watchFiles: ['/**/*'],
watchFiles: ['src/**/*', 'assets/**/*', 'index.html'],
headers: {
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
Pragma: 'no-cache',
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/pages/app-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@

import { createContext, provide } from '@lit/context';
import { PreventAndRedirectCommands, RouterLocation } from '@vaadin/router';
import { html, LitElement } from 'lit';
import { css, html, LitElement, unsafeCSS } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { setRealmTheme } from '../common/theme';
import { manager } from '@openremote/core';
import { ML_OR_URL } from '../common/constants';
import { IS_EMBEDDED, ML_OR_URL } from '../common/constants';

export const realmContext = createContext<string>(Symbol('realm'));

Expand All @@ -32,6 +32,17 @@ export class AppLayout extends LitElement {
@state()
realm = '';

static get styles() {
const padding = IS_EMBEDDED ? '0 20px' : '20px';

return css`
:host {
display: block;
padding: ${unsafeCSS(padding)};
}
`;
}

// Vaadin router lifecycle hook -- runs exactly once since this is the parent route
async onBeforeEnter(location: RouterLocation, commands: PreventAndRedirectCommands) {
const authRealm = manager.getRealm() ?? 'master';
Expand Down
20 changes: 20 additions & 0 deletions packages/openremote_client/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "openremote-client"
version = "0.1.0"
description = "A Python client for interacting with the OpenRemote API"
readme = "README.md"
requires-python = ">=3.13"

dependencies = ["httpx>=0.24.0", "pydantic>=2.0.0", "apscheduler>=3.11.0"]

[tool.setuptools]
package-dir = { "" = "src" }
[tool.setuptools.package-data]
"openremote_client" = ["py.typed"] # include type stubs

[tool.setuptools.packages.find]
where = ["src"]
29 changes: 29 additions & 0 deletions packages/openremote_client/src/openremote_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""OpenRemote Client Package."""

from openremote_client.client_roles import ClientRoles
from openremote_client.models import (
AssetDatapoint,
AssetDatapointPeriod,
AssetDatapointQuery,
BasicAsset,
BasicAttribute,
Realm,
ServiceInfo,
ServiceStatus,
)
from openremote_client.rest_client import OpenRemoteClient
from openremote_client.service_registrar import OpenRemoteServiceRegistrar

__all__ = [
"AssetDatapoint",
"AssetDatapointPeriod",
"AssetDatapointQuery",
"BasicAsset",
"BasicAttribute",
"ClientRoles",
"OpenRemoteClient",
"OpenRemoteServiceRegistrar",
"Realm",
"ServiceInfo",
"ServiceStatus",
]
25 changes: 25 additions & 0 deletions packages/openremote_client/src/openremote_client/client_roles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2025, OpenRemote Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later

"""
This module contains the client roles for the OpenRemote API.
"""


class ClientRoles:
READ_SERVICES_ROLE = "read:services"
WRITE_SERVICES_ROLE = "write:services"
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#
# SPDX-License-Identifier: AGPL-3.0-or-later

from enum import Enum
from typing import Any

from pydantic import BaseModel
Expand Down Expand Up @@ -81,3 +82,47 @@ class Realm(BaseModel):

name: str
displayName: str


class ServiceStatus(str, Enum):
"""The status of a registered service.

- AVAILABLE: The service is available and can be used
- UNAVAILABLE: The service is unavailable
"""

AVAILABLE = "AVAILABLE"
UNAVAILABLE = "UNAVAILABLE"


class ServiceInfo(BaseModel):
"""Holds comprehensive details about a service.

This object is used to register and deregister services.
"""

serviceId: str
"""The unique identifier of the service, e.g. 'energy-service'"""

instanceId: int | None = None
"""The unique instance identifier of the registered service,
either generated by the service or provided by the user."""

label: str
"""The label of the service, e.g. 'Energy Service'"""

icon: str | None = None
"""The icon of the service, e.g. 'puzzle', must be part of the Material Design Icons set"""

version: str | None = None
"""The version of the service, e.g. '1.0.0'"""

homepageUrl: str
"""The URL of the service's homepage which provides the user interface,
e.g. 'https://openremote.app/services/energy-service/ui'"""

status: ServiceStatus
"""The status of the service, e.g. 'AVAILABLE'"""

realm: str
"""The realm of the service, e.g. 'master'"""
Loading