Skip to content
Closed
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ in `/frontend`

## License

The OpenRemote Machine Learning Forecast Service is distributed under [AGPL-3.0-or-later](LICENSE.txt).
The OpenRemote ML Forecast Service is distributed under [AGPL-3.0-or-later](LICENSE.txt).

```
Copyright 2025, OpenRemote Inc.
Expand Down
35 changes: 29 additions & 6 deletions frontend/src/components/breadcrumb-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,31 @@
align-items: center;
gap: 8px;
margin-bottom: 16px;
width: fit-content;
width: 100%;
justify-content: space-between;
}

.breadcrumb-container {
display: flex;
align-items: center;
gap: 8px;
}

.realm-badge {
background-color: var(--or-app-color4);
color: white;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: auto;
min-width: 60px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}

a {
Expand All @@ -52,7 +76,7 @@
gap: 4px;
--or-icon-width: 16px;
--or-icon-height: 16px;
max-width: 200px;
max-width: 300px;
}

a:hover {
Expand All @@ -62,7 +86,7 @@
span[aria-current='page'] {
color: rgba(0, 0, 0, 0.87);
font-weight: 500;
max-width: 200px;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Expand Down Expand Up @@ -93,15 +117,13 @@

willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has('realm') && this.realm) {
// Trigger location change event
const location: Partial<RouterLocation> = {
pathname: `${this.rootPath}/${this.realm}/configs`,
params: {
realm: this.realm
}
};

// Update the breadcrumbs and title
this.updateBreadcrumbs(location as RouterLocation);
}
}
Expand All @@ -111,7 +133,6 @@
*/
protected readonly handleLocationChange = (event: CustomEvent<{ location: RouterLocation }>) => {
const location = event.detail.location;
// Update the breadcrumbs and title
this.updateBreadcrumbs(location);
};

Expand Down Expand Up @@ -204,6 +225,8 @@
const truncatedLabel = this.truncateText(part.label);
const iconTemplate = part.icon ? html`<or-icon icon="${part.icon}"></or-icon>` : html``;

const icon = part.icon ? html`<or-icon icon=${part.icon}></or-icon>` : html``;

Check failure on line 228 in frontend/src/components/breadcrumb-nav.ts

View workflow job for this annotation

GitHub Actions / frontend-quality-checks

'icon' is assigned a value but never used

return html`
${showSeparator ? html`<span aria-hidden="true">&gt;</span>` : html``}
${readonly
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/pages/app-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ export const realmContext = createContext<string>(Symbol('realm'));

@customElement('app-layout')
export class AppLayout extends LitElement {
static get styles() {
const padding = IS_EMBEDDED ? '0 20px' : '20px';

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

// Provide the realm to all child elements
@provide({ context: realmContext })
@state()
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/pages/pages-config-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ export class PageConfigList extends LitElement {
--or-icon-fill: var(--or-app-color3);
}
.title {
font-size: 18px;
font-weight: bold;
font-size: 14px;
font-weight: bolder;
display: flex;
flex-direction: row;
align-items: center;
color: var(--or-app-color3);
text-transform: uppercase;
line-height: 1em;
flex: 0 0 auto;
letter-spacing: 0.025em;
}
`;
}
Expand Down Expand Up @@ -166,8 +170,8 @@ export class PageConfigList extends LitElement {
<or-panel heading="">
<div class="config-header">
<div class="title-container">
<or-icon icon="chart-bell-curve"></or-icon>
<span class="title">Forecast Configurations</span>
<or-icon icon="chart-line"></or-icon>
<span class="title">Forecast configurations</span>
</div>

<or-mwc-input
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/services/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// TODO: Automatically generate the Models via OpenAPI spec -- See https://github.com/openremote/service-ml-forecast/issues/31

/**
* Enum representing the different types of machine learning models available.
* Enum representing the different types of models available.
* Corresponds to service_ml_forecast.models.model_type.ModelTypeEnum
*/
export enum ModelTypeEnum {
Expand Down Expand Up @@ -69,7 +69,7 @@ export interface RegressorFeature {
}

/**
* Base configuration common to all ML models.
* Base configuration common to all models.
*/
interface BaseModelConfig {
/**
Expand All @@ -90,7 +90,7 @@ interface BaseModelConfig {
*/
enabled?: boolean; // Optional as it has a default
/**
* Which machine learning model to use.
* Which model to use.
*/
type: ModelTypeEnum;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

HEARTBEAT_JOB_ID = "service:heartbeat"
JOB_GRACE_PERIOD = 60
HEARTBEAT_INTERVAL = 30 # 30 seconds between heartbeats
HEARTBEAT_INTERVAL = 30
Comment thread
dominiquekleeven marked this conversation as resolved.


class OpenRemoteServiceRegistrar:
Expand Down
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dependencies = [
"pyjwt>=2.10.1",
"aiocache>=0.12.3",
"openremote-client",
"scikit-learn>=1.7.2",
]


Expand Down Expand Up @@ -79,7 +80,7 @@ select = [
"PL", # pylint
"RUF", # ruff-specific rules
]
ignore = ["PLR0913", "B008", "C901", "PLR0912"]
ignore = ["PLR0913", "B008", "C901", "PLR0912", "PLC0415"]
fixable = ["ALL"]

[tool.ruff.format]
Expand Down Expand Up @@ -115,6 +116,10 @@ combine-as-imports = true
testpaths = ["tests"]
python_files = "test_*.py"
filterwarnings = ["ignore:.*"]
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(asctime)s [%(levelname)8s] %(name)s: %(message)s"
log_cli_date_format = "%Y-%m-%d %H:%M:%S"

[project.scripts]
service_ml_forecast = "service_ml_forecast.main:app"
Expand All @@ -132,5 +137,3 @@ build-frontend-dev = "scripts.tools:build_frontend_dev"
lint-packages = "scripts.tools:lint_packages"
test-packages = "scripts.tools:test_packages"
build-packages = "scripts.tools:build_packages"


4 changes: 2 additions & 2 deletions scripts/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,10 @@ def build_frontend_dev() -> None:
def _copy_frontend_dist() -> None:
"""Copy the frontend dist to the deployment/web directory."""
DEPLOYMENT_WEB_DIR.mkdir(parents=True, exist_ok=True)

if DEPLOYMENT_WEB_DIR.exists():
shutil.rmtree(DEPLOYMENT_WEB_DIR)

shutil.copytree(FRONTEND_DIR / "dist", DEPLOYMENT_WEB_DIR / "dist")

print(f"Frontend dist copied to {DEPLOYMENT_WEB_DIR}")
30 changes: 15 additions & 15 deletions src/service_ml_forecast/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,28 @@ class AppEnvironment(BaseSettings):

All settings can be overridden via environment variables.

The environment variables are prefixed with "ML_" to avoid conflicts with other services.
The environment variables are prefixed with "ML_" to avoid conflicts
"""

# Application Settings
ML_LOG_LEVEL: str = "INFO" # log level to use
ML_ENVIRONMENT: str = "development" # environment to run the service in
ML_VERIFY_SSL: bool = True # whether to verify SSL certificates when making requests

# FastAPI Settings
ML_API_PUBLISH_DOCS: bool = True # whether to make the openapi docs available
ML_API_ROOT_PATH: str = (
"/services/ml-forecast" # when running behind a reverse proxy, the root path of the fastapi app
)
# FastAPI settings
ML_API_PUBLISH_DOCS: bool = True
ML_API_ROOT_PATH: str = "/services/ml-forecast" # reverse proxy root path for the service
ML_API_MIDDLEWARE_KEYCLOAK: bool = True # whether to enable keycloak middleware

# Uvicorn Settings
ML_WEBSERVER_HOST: str = "0.0.0.0" # host to bind the web server (uvicorn) to
ML_WEBSERVER_PORT: int = 8000 # port to bind the web server (uvicorn) to
# Uvicorn webserver settings
ML_WEBSERVER_PORT: int = 8000
ML_WEBSERVER_ORIGINS: list[str] = [
"http://localhost:8000",
"http://localhost:8001",
] # origins to allow
]

# Application settings
ML_LOG_LEVEL: str = "INFO"
ML_ENVIRONMENT: str = "development"
ML_VERIFY_SSL: bool = True # verify SSL certificates when making HTTP requests

# Service registration settings
ML_SERVICE_HOSTNAME: str = f"http://localhost:{ML_WEBSERVER_PORT}"

# OpenRemote Settings
ML_OR_URL: str = "http://localhost:8080" # OpenRemote Manager URL
Expand Down
2 changes: 1 addition & 1 deletion src/service_ml_forecast/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
},
},
"loggers": {
# Service ML Forecast logs
# Service logs
"": {
"handlers": ["default"],
"level": ENV.ML_LOG_LEVEL,
Expand Down
2 changes: 1 addition & 1 deletion src/service_ml_forecast/middlewares/keycloak/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async def _verify_jwt_token(token: str, valid_issuers: list[str]) -> dict[str, A
if unverified_header.get("alg") != JWT_ALGORITHM_RS256:
raise jwt.exceptions.InvalidTokenError("Invalid token algorithm: Expected RS256")

# Decode the token without verifying (we don't have the public key yet)
# Decode the token without verifying (necessary so we can extract the issuer for retrieving the JWKS)
unverified_payload = jwt.decode(token, options={"verify_signature": False, "verify_aud": False})

# Extract required claims
Expand Down
Loading
Loading