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
98 changes: 98 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Release

on:
# Manual trigger
workflow_dispatch:

# # When a release is published
# release:
# types: [published]

# # Push to main excluding tags and workflow changes
# push:
# branches:
# - main
# tags-ignore:
# - '*.*'
# paths-ignore:
# - '**/*.md'

permissions:
actions: write
contents: write

jobs:
# call ci.yml for quality checks
call-ci:
name: Quality checks
uses: ./.github/workflows/ci.yml

# Build and push docker image
release:
name: Docker Image
needs: call-ci
runs-on: ubuntu-latest
env:
PLATFORM: linux/amd64,linux/arm64

steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
with:
access_token: ${{ github.token }}

- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set tags
id: set-tags
run: |
if [ -z "$TAG" ]; then
echo "TAG=-t openremote/service-ml-forecast:develop" >> $GITHUB_ENV
echo "dockerImage=openremote/service-ml-forecast:develop" >> $GITHUB_OUTPUT
else
echo "TAG=-t openremote/service-ml-forecast:latest -t openremote/service-ml-forecast:$TAG" >> $GITHUB_ENV
echo "dockerImage=openremote/service-ml-forecast:$TAG" >> $GITHUB_OUTPUT
fi
env:
TAG: ${{ github.event.release.tag_name }}

- name: Set up QEMU
uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480 # v1
with:
platforms: all

- name: Install buildx
id: buildx
uses: docker/setup-buildx-action@f211e3e9ded2d9377c8cadc4489a4e38014bc4c9 # v1
with:
version: latest
install: true

- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}

- name: Login to DockerHub
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7 # v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push images
run: |
docker build --build-arg GIT_COMMIT=${{ github.sha }} --push --platform $PLATFORM $TAG .

- name: Scan docker image
uses: anchore/scan-action@3343887d815d7b07465f6fdcd395bd66508d486a # v3
id: anchore-scan
with:
image: ${{ steps.set-tags.outputs.dockerImage }}
fail-build: false
severity-cutoff: critical

- name: Upload Anchore scan SARIF report
if: ${{ !cancelled() }}
uses: github/codeql-action/upload-sarif@e488e3c8239c26bf8e6704904a8cb59be658d450 # v3
with:
sarif_file: ${{ steps.anchore-scan.outputs.sarif }}

6 changes: 6 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ COPY frontend/ ./
# Define build arguments
ARG ML_SERVICE_URL
ARG ML_WEB_ROOT_PATH
ARG ML_KEYCLOAK_URL

RUN ML_SERVICE_URL=${ML_SERVICE_URL:-/services/ml-forecast} \
ML_WEB_ROOT_PATH=${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} \
ML_KEYCLOAK_URL=${ML_KEYCLOAK_URL:-/auth} \
npm run build:prod

# --- Python Build Phase -------------------------------------------------------
Expand Down Expand Up @@ -60,6 +62,10 @@ WORKDIR /app
# Explicitly declare the ARG for the build process
ARG ML_ENVIRONMENT

# Add git commit label must be specified at build time using --build-arg GIT_COMMIT=commit-hash
ARG GIT_COMMIT=unknown
LABEL git-commit=$GIT_COMMIT

ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/app/src \
Expand Down
1 change: 1 addition & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
context: ..
dockerfile: docker/Dockerfile
args:
ML_KEYCLOAK_URL: ${ML_KEYCLOAK_URL:-/auth} # OpenRemote Keycloak URL
ML_SERVICE_URL: ${ML_SERVICE_URL:-/services/ml-forecast} # Url to reach the back-end service, should be the same as ML_API_ROOT_PATH
ML_WEB_ROOT_PATH: ${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} # Public path for the front-end (e.g. when behind a reverse proxy)
container_name: service-ml-forecast
Expand Down
4 changes: 4 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Prevent index.html from being cached -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="icon" href="<%= templateRootPath %>/assets/images/logo.svg" />
<style>
/* Global styles */
Expand Down
23 changes: 15 additions & 8 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"type": "module",
"scripts": {
"serve": "cross-env rspack serve",
"build:dev": "cross-env rspack build --mode development",
"build:prod": "cross-env SERVICE_URL=${SERVICE_URL:-/services/ml-forecast} ML_WEB_ROOT_PATH=${ML_WEB_ROOT_PATH:-/services/ml-forecast/ui} rspack build --mode production",
"build:prod": "cross-env ML_KEYCLOAK_URL=${ML_KEYCLOAK_URL:-/auth} 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",
"format": "prettier . --write"
Expand All @@ -16,6 +15,7 @@
"@openremote/or-components": "^1.5.0",
"@openremote/or-mwc-components": "^1.5.0",
"@vaadin/router": "^2.0.0",
"keycloak-js": "^26.1.5",
"lit": "^3.2.1",
"lodash": "^4.17.21",
"raw-loader": "^4.0.2",
Expand Down
7 changes: 4 additions & 3 deletions frontend/rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const isProduction = process.env.NODE_ENV === 'production';
const serviceUrl = process.env.ML_SERVICE_URL || 'http://localhost:8000'; // Default to default dev backend
const serviceUrl = process.env.ML_SERVICE_URL || 'http://localhost:8000'; // Default to default service backend
const keycloakUrl = process.env.ML_KEYCLOAK_URL || 'http://localhost:8081/auth'; // Default to openremote keycloak address
const rootPath = process.env.ML_WEB_ROOT_PATH;

export default {
Expand All @@ -20,7 +21,6 @@ export default {
filename: `bundle.[contenthash].js`,
clean: true,
path: path.resolve(__dirname, 'dist'),
// prefix for the bundle, use root path or fallback to '/'
publicPath: rootPath ? rootPath : '/'
},
resolve: {
Expand Down Expand Up @@ -65,7 +65,8 @@ export default {
patterns: [{ from: 'assets', to: 'assets' }]
}),
new rspack.DefinePlugin({
'process.env.ML_SERVICE_URL': JSON.stringify(serviceUrl)
'process.env.ML_SERVICE_URL': JSON.stringify(serviceUrl),
'process.env.ML_KEYCLOAK_URL': JSON.stringify(keycloakUrl)
})
],
devServer: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
export const APP_OUTLET = document.querySelector('#outlet') as HTMLElement;
export const IS_DEVELOPMENT = process.env.NODE_ENV === 'development';
export const ML_SERVICE_URL = (process.env.ML_SERVICE_URL || '').replace(/\/$/, '');
export const IS_EMBEDDED = window.top !== window.self;
2 changes: 2 additions & 0 deletions frontend/src/common/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const BASE_THEME = {
};

/**
* Setup the OR icons
* Overrides the default createMdiIconSet with a function that uses the static fonts part of the build
* Setup the MDI-Icons for or-icon element
*/
export function setupORIcons() {
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,37 @@ export function getRootPath() {
}
return '';
}

export function isEmbedded(): boolean {
return window.top !== window.self;
}

export function setupConsoleLogging() {
// Override console.log to add a prefix
const originalConsoleLog = console.log;
console.log = (...args) => {
originalConsoleLog('[ml-forecast]', ...args);
};

// Override console.info to add a prefix
const originalConsoleInfo = console.info;
console.info = (...args) => {
originalConsoleInfo('[ml-forecast]', ...args);
};

// Override console.warn to add a prefix
const originalConsoleWarn = console.warn;
console.warn = (...args) => {
originalConsoleWarn('[ml-forecast]', ...args);
};

// Override console.error to add a prefix
const originalConsoleError = console.error;
console.error = (...args) => {
originalConsoleError('[ml-forecast]', ...args);
};
}

export const getRealmParam = () => {
return new URLSearchParams(window.location.search).get('realm');
};
20 changes: 16 additions & 4 deletions frontend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
//
// SPDX-License-Identifier: AGPL-3.0-or-later

import { AuthService } from './services/auth-service';
import { setupORIcons } from './common/theme';
import { setupRouter } from './router';
import { IS_EMBEDDED } from './common/constants';
import { setupConsoleLogging, getRealmParam } from './common/util';

// Component Imports
import '@openremote/or-mwc-components/or-mwc-input';
import '@openremote/or-components/or-panel';
Expand All @@ -24,16 +30,22 @@ import './components/configs-table';
import './components/loading-spinner';
import './components/breadcrumb-nav';
import './components/alert-message';
import { setupORIcons } from './common/theme';
import { setupRouter } from './router';

function init() {
// Override default log statements with service prefix
setupConsoleLogging();

async function init() {
console.info('Context:', IS_EMBEDDED ? 'iframe' : 'standalone');

const authenticated = await AuthService.init(getRealmParam() ?? 'master');
if (!authenticated) {
AuthService.login();
}
// Setup OR icons
setupORIcons();

// Setup the router
setupRouter();
}

// Entry point
init();
24 changes: 22 additions & 2 deletions frontend/src/pages/app-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

import { createContext, provide } from '@lit/context';
import { RouterLocation } from '@vaadin/router';
import { Router, RouterLocation } from '@vaadin/router';
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { setRealmTheme } from '../common/theme';
import { AuthService } from '../services/auth-service';

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

Expand All @@ -34,7 +35,26 @@ export class AppLayout extends LitElement {

// Called before the initial Vaadin Router location is entered ('/'), only called once since its a parent route
onBeforeEnter(location: RouterLocation) {
this.realm = location.params.realm as string;
if (!AuthService.authenticated) {
AuthService.login();
return;
}

const paramRealm = location.params.realm as string;
const authRealm = AuthService.realm;

// Param realm takes precedence over auth realm
if (!paramRealm) {
this.realm = authRealm;
} else {
this.realm = paramRealm;
}

// Navigate to given auth realm if no param realm is provided
if (!paramRealm) {
Router.go(`/${this.realm}`);
}

setRealmTheme(this.realm);
}

Expand Down
Loading