diff --git a/Dockerfile b/Dockerfile index cd8925f..317d94a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,41 +2,49 @@ # build ui # ############ -FROM node:lts-alpine AS build-ui - -# some dir for our code -WORKDIR /app - -# install dependencies -COPY ui/package*.json ui/yarn*.lock ./ -RUN yarn --production=false - -# copy code -COPY ui . -RUN yarn build - - -############## -# webservice # -############## - -FROM antonapetrov/uvicorn-gunicorn:python3.9-alpine3.13 AS production - -RUN set -ex; \ - # prerequisites - apk add --no-cache \ - libmagic \ - ; +FROM node:lts AS build-ui # env setup -ENV \ +WORKDIR /usr/local/src/ovdashboard_ui + +# install ovdashboard_ui dependencies +COPY ui/package*.json ui/yarn*.lock ./ +RUN yarn install --production false + +# copy and build ovdashboard_ui +COPY ui ./ +RUN yarn build --dest /tmp/ovdashboard_ui/html + +########### +# web app # +########### + +FROM tiangolo/uvicorn-gunicorn:python3.12-slim AS production + +# add prepared ovdashboard_ui +COPY --from=build-ui /tmp/ovdashboard_ui /usr/local/share/ovdashboard_ui + +# env setup +WORKDIR /usr/local/src/ovdashboard_api +ENV \ PRODUCTION_MODE="true" \ - APP_MODULE="ovdashboard_api:app" + PORT="8000" \ + MODULE_NAME="ovdashboard_api.app" +EXPOSE 8000 -# install API -COPY api /usr/src/ovdashboard_api +COPY api ./ RUN set -ex; \ - pip3 --no-cache-dir install /usr/src/ovdashboard_api; + # install libs + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; apt-get install --yes --no-install-recommends \ + libmagic1 \ + ; rm -rf /var/lib/apt/lists/*; \ + \ + # remove example app + rm -rf /app; \ + \ + # install ovdashboard_api + python -m pip --no-cache-dir install ./ -# install UI -COPY --from=build-ui /app/dist /html \ No newline at end of file +# run as unprivileged user +USER nobody diff --git a/api/ovdashboard_api/core/caldav.py b/api/ovdashboard_api/core/caldav.py index 4f7b97a..128b9eb 100644 --- a/api/ovdashboard_api/core/caldav.py +++ b/api/ovdashboard_api/core/caldav.py @@ -79,7 +79,7 @@ class CalDAV: _logger.info(f"downloading {calendar_name!r} ...") dt_start = datetime.combine( - datetime.utcnow().date(), + datetime.now().date(), datetime.min.time(), ) dt_end = dt_start + timedelta(days=cfg.calendar.future_days) diff --git a/api/ovdashboard_api/core/calevent.py b/api/ovdashboard_api/core/calevent.py index 0823072..c22bbca 100644 --- a/api/ovdashboard_api/core/calevent.py +++ b/api/ovdashboard_api/core/calevent.py @@ -31,8 +31,8 @@ class CalEvent(BaseModel): summary: StrippedStr = "" description: StrippedStr = "" - dtstart: datetime = datetime.utcnow() - dtend: datetime = datetime.utcnow() + dtstart: datetime = datetime.now() + dtend: datetime = datetime.now() def __lt__(self, other: Self) -> bool: """ diff --git a/api/ovdashboard_api/core/settings.py b/api/ovdashboard_api/core/settings.py index 21c5af3..6cc11fa 100644 --- a/api/ovdashboard_api/core/settings.py +++ b/api/ovdashboard_api/core/settings.py @@ -25,7 +25,7 @@ class DAVSettings(BaseModel): username: str | None = None password: str | None = None - cache_ttl: int = 60 * 30 + cache_ttl: int = 60 * 10 cache_size: int = 1024 @property diff --git a/api/ovdashboard_api/routers/v1/misc.py b/api/ovdashboard_api/routers/v1/misc.py index 23dc2a3..8f5ea3b 100644 --- a/api/ovdashboard_api/routers/v1/misc.py +++ b/api/ovdashboard_api/routers/v1/misc.py @@ -31,8 +31,8 @@ async def get_lan_ip() -> str: family=AF_INET, type=SOCK_DGRAM, ) as s: - s.settimeout(0) try: + s.settimeout(0) s.connect((SETTINGS.ping_host, SETTINGS.ping_port)) IP = s.getsockname()[0] @@ -44,7 +44,7 @@ async def get_lan_ip() -> str: @router.get("/version") async def get_server_api_version() -> str: - return importlib.metadata.version("ovdashboard-api") + return importlib.metadata.version("ovdashboard_api") @router.get("/config/server") diff --git a/ui/.devcontainer/Dockerfile b/ui/.devcontainer/Dockerfile index b355ae2..9dcfd1e 100644 --- a/ui/.devcontainer/Dockerfile +++ b/ui/.devcontainer/Dockerfile @@ -1,11 +1,20 @@ # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster -ARG VARIANT=16-bullseye -FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} +ARG VARIANT=16-bookworm +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:1-${VARIANT} # [Optional] Uncomment this section to install additional OS packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # && apt-get -y install --no-install-recommends +RUN set -ex; \ + \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; apt-get install --yes --no-install-recommends \ + git-flow \ + git-lfs \ + ; rm -rf /var/lib/apt/lists/*; \ + su node -c "git lfs install" + # [Optional] Uncomment if you want to install an additional version of node using nvm # ARG EXTRA_NODE_VERSION=10 # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" diff --git a/ui/.devcontainer/devcontainer.json b/ui/.devcontainer/devcontainer.json index 25b2127..98c693c 100644 --- a/ui/.devcontainer/devcontainer.json +++ b/ui/.devcontainer/devcontainer.json @@ -1,28 +1,35 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node { - "name": "Node.js", + "name": "OVD UI", "build": { "dockerfile": "Dockerfile", + "context": "..", // Update 'VARIANT' to pick a Node version: 18, 16, 14. // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local arm64/Apple Silicon. "args": { - "VARIANT": "18-bullseye" + "VARIANT": "20-bookworm" } }, - // Set *default* container specific settings.json values on container create. - "settings": { - "terminal.integrated.defaultProfile.linux": "zsh" + "containerEnv": { + "TZ": "Europe/Berlin" }, // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh" + }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "dbaeumer.vscode-eslint", - "octref.vetur" + "esbenp.prettier-vscode", + "mhutchie.git-graph", + "Syler.sass-indented", + "Vue.volar" ] } }, @@ -30,7 +37,7 @@ // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "yarn install", - "postStartCommand": "yarn install", + "postStartCommand": "yarn install --production false", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "node" } \ No newline at end of file diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 5da6a7f..4238da9 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -1,18 +1,18 @@ module.exports = { root: true, env: { - node: true + node: true, }, - 'extends': [ - 'plugin:vue/essential', - 'eslint:recommended', - '@vue/typescript/recommended' + extends: [ + "plugin:vue/essential", + "eslint:recommended", + "@vue/typescript/recommended", ], parserOptions: { - ecmaVersion: 2020 + ecmaVersion: 2020, }, rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' - } -} + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + }, +}; diff --git a/ui/.vscode/settings.json b/ui/.vscode/settings.json index 8a9bbe6..c6c940c 100644 --- a/ui/.vscode/settings.json +++ b/ui/.vscode/settings.json @@ -1,8 +1,21 @@ { "editor.formatOnSave": true, + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, "editor.codeActionsOnSave": { "source.organizeImports": true }, "git.closeDiffOnOperation": true, - "editor.tabSize": 2 + "editor.tabSize": 2, + "sass.disableAutoIndent": true, + "sass.format.convert": false, + "sass.format.deleteWhitespace": true, + "prettier.trailingComma": "all", } \ No newline at end of file diff --git a/ui/babel.config.js b/ui/babel.config.js index e955840..162a3ea 100644 --- a/ui/babel.config.js +++ b/ui/babel.config.js @@ -1,5 +1,3 @@ module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} + presets: ["@vue/cli-plugin-babel/preset"], +}; diff --git a/ui/package.json b/ui/package.json index fca1e2f..84b0603 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,35 +7,34 @@ "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, - "dependencies": { - "axios": "^0.27.2", - "color": "^4.2.3", - "core-js": "^3.8.3", - "luxon": "^3.0.3", - "register-service-worker": "^1.7.2", - "vue": "^2.6.14", - "vue-class-component": "^7.2.3", - "vue-property-decorator": "^9.1.2", - "vuetify": "^2.6.0" - }, "devDependencies": { "@types/color": "^3.0.3", "@types/luxon": "^3.0.1", - "@typescript-eslint/eslint-plugin": "^5.4.0", - "@typescript-eslint/parser": "^5.4.0", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-pwa": "~5.0.0", "@vue/cli-plugin-typescript": "~5.0.0", "@vue/cli-service": "~5.0.0", - "@vue/eslint-config-typescript": "^9.1.0", - "eslint": "^7.32.0", - "eslint-plugin-vue": "^8.0.3", - "sass": "~1.32.0", - "sass-loader": "^10.0.0", - "typescript": "~4.5.5", + "@vue/eslint-config-typescript": "^12.0.0", + "axios": "^1.6.0", + "color": "^4.2.3", + "core-js": "^3.8.3", + "eslint": "^8.52.0", + "eslint-plugin-vue": "^9.18.0", + "luxon": "^3.0.3", + "prettier": "^3.0.3", + "register-service-worker": "^1.7.2", + "sass": "~1.69.5", + "sass-loader": "^13.3.2", + "typescript": "~5.2.2", + "vue": "^2.7.15", + "vue-class-component": "^7.2.3", "vue-cli-plugin-vuetify": "^2.5.5", + "vue-property-decorator": "^9.1.2", "vue-template-compiler": "^2.6.14", + "vuetify": "^2.7.1", "vuetify-loader": "^1.7.0" } } diff --git a/ui/public/index.html b/ui/public/index.html index bc51465..6ab24c2 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -1,17 +1,27 @@ - + - - - - + + + + <%= htmlWebpackPlugin.options.title %> - - + +
diff --git a/ui/src/components/Dashboard.vue b/ui/src/components/Dashboard.vue index 88b6683..95ae2d0 100644 --- a/ui/src/components/Dashboard.vue +++ b/ui/src/components/Dashboard.vue @@ -18,5 +18,4 @@ import { Component, Vue } from "vue-property-decorator"; export default class Dashboard extends Vue {} - \ No newline at end of file + diff --git a/ui/src/components/DashboardInfo.vue b/ui/src/components/DashboardInfo.vue index 28247c1..072a75b 100644 --- a/ui/src/components/DashboardInfo.vue +++ b/ui/src/components/DashboardInfo.vue @@ -18,10 +18,10 @@ import { Component, Vue } from "@/ovd-vue"; @Component export default class DashboardInfo extends Vue { - private server_host = "https://oekzident.de"; - private server_name = "OEKZident"; - private version = "0.0.1"; - private lan_ip = "0.0.0.0"; + public server_host = "https://oekzident.de"; + public server_name = "OEKZident"; + public version = "0.0.1"; + public lan_ip = "0.0.0.0"; public created(): void { super.created(); @@ -43,7 +43,7 @@ export default class DashboardInfo extends Vue { (data) => { this.server_host = data.host; this.server_name = data.name; - } + }, ); // Update Version @@ -57,4 +57,4 @@ export default class DashboardInfo extends Vue { }); } } - \ No newline at end of file + diff --git a/ui/src/components/ImageCarousel.vue b/ui/src/components/ImageCarousel.vue index dc5c3de..328f693 100644 --- a/ui/src/components/ImageCarousel.vue +++ b/ui/src/components/ImageCarousel.vue @@ -22,10 +22,10 @@ import { Component, Vue } from "@/ovd-vue"; @Component export default class ImageCarousel extends Vue { - private urls: string[] = require("@/assets/image_testdata.json"); - private height = 300; - private contain = false; - private speed = 10000; + public urls: string[] = require("@/assets/image_testdata.json"); + public height = 300; + public contain = false; + public speed = 10000; public created(): void { super.created(); @@ -39,7 +39,7 @@ export default class ImageCarousel extends Vue { // Update Images this.$ovdashboard.api_get_list("image/list", (names) => { this.urls = names.map((name: string) => - this.$ovdashboard.api_url(`image/get/${name}`) + this.$ovdashboard.api_url(`image/get/${name}`), ); }); @@ -71,4 +71,4 @@ export default class ImageCarousel extends Vue { } } } - \ No newline at end of file + diff --git a/ui/src/components/Message.vue b/ui/src/components/Message.vue index 0a0c745..a660b2e 100644 --- a/ui/src/components/Message.vue +++ b/ui/src/components/Message.vue @@ -7,7 +7,7 @@ import { Component, Vue } from "@/ovd-vue"; @Component export default class Message extends Vue { - private html = require("@/assets/message_testdata.json"); + public html = require("@/assets/message_testdata.json"); public created(): void { super.created(); @@ -21,7 +21,7 @@ export default class Message extends Vue { // Update Message this.$ovdashboard.api_get_string( "text/get/html/message", - (data) => (this.html = data) + (data) => (this.html = data), ); } } @@ -59,4 +59,4 @@ div:deep() { font-weight: bold; } } - \ No newline at end of file + diff --git a/ui/src/components/Model.ts b/ui/src/components/Model.ts index 53ca388..016e87d 100644 --- a/ui/src/components/Model.ts +++ b/ui/src/components/Model.ts @@ -5,7 +5,7 @@ export class Model { // source: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0?permalink_comment_id=2775538#gistcomment-2775538 let hash = 0; for (let i = 0; i < str.length; i++) - hash = Math.imul(31, hash) + str.charCodeAt(i) | 0; + hash = (Math.imul(31, hash) + str.charCodeAt(i)) | 0; return new Uint32Array([hash])[0].toString(36); } diff --git a/ui/src/components/TickerBar.vue b/ui/src/components/TickerBar.vue index 0d0e3bb..6538ce1 100644 --- a/ui/src/components/TickerBar.vue +++ b/ui/src/components/TickerBar.vue @@ -15,23 +15,20 @@ import Color from "color"; @Component export default class TickerBar extends Vue { - private content = "

changeme

"; + public content = "

changeme

"; - private color = "primary"; - - @Ref("content") - private readonly _content!: HTMLDivElement; + public color = "primary"; @Ref("marquee") private readonly _marquee!: HTMLSpanElement; - private get is_dark(): boolean { + public get is_dark(): boolean { return this.footer_color.isDark(); } private get footer_color(): Color { // try getting from vuetify theme - let color = this.$vuetify.theme.themes.light[this.color]; + const color = this.$vuetify.theme.themes.light[this.color]; if (typeof color === "string") { return Color(color); diff --git a/ui/src/components/calendar/Calendar.vue b/ui/src/components/calendar/Calendar.vue index e3142ae..3b87ff2 100644 --- a/ui/src/components/calendar/Calendar.vue +++ b/ui/src/components/calendar/Calendar.vue @@ -4,11 +4,11 @@ {{ title }} @@ -16,8 +16,8 @@ @@ -37,4 +37,4 @@ export default class Calendar extends Vue { .v-list .v-divider { border-color: rgba(0, 0, 0, 0.25); } - \ No newline at end of file + diff --git a/ui/src/components/calendar/CalendarCarousel.vue b/ui/src/components/calendar/CalendarCarousel.vue index 994b3e7..56e7807 100644 --- a/ui/src/components/calendar/CalendarCarousel.vue +++ b/ui/src/components/calendar/CalendarCarousel.vue @@ -29,7 +29,7 @@ export default class CalendarCarousel extends Vue { private interval?: number; private data: CalendarData[] = require("@/assets/calendar_testdata.json"); - private speed = 10000; + public speed = 10000; @Ref("main") private readonly _main?: Vue; @@ -57,7 +57,7 @@ export default class CalendarCarousel extends Vue { events: calendars[i], }); } - } + }, ); }); @@ -70,7 +70,7 @@ export default class CalendarCarousel extends Vue { "calendar/config", (data) => { this.speed = data.speed; - } + }, ); } @@ -98,8 +98,8 @@ export default class CalendarCarousel extends Vue { this.interval = setInterval(this.update_height, 10000); } - private get calendars(): CalendarModel[] { - let arr = []; + public get calendars(): CalendarModel[] { + const arr = []; for (const json_data of this.data) { arr.push(new CalendarModel(json_data)); @@ -131,4 +131,4 @@ export default class CalendarCarousel extends Vue { } } } - \ No newline at end of file + diff --git a/ui/src/components/calendar/CalendarModel.ts b/ui/src/components/calendar/CalendarModel.ts index 5fb6423..74d9b1f 100644 --- a/ui/src/components/calendar/CalendarModel.ts +++ b/ui/src/components/calendar/CalendarModel.ts @@ -13,11 +13,11 @@ export class CalendarModel extends Model { public constructor(json_data: CalendarData) { super(); - this.title = json_data.title + this.title = json_data.title; this.events = []; for (const event_data of json_data.events) { - this.events.push(new EventModel(event_data)) + this.events.push(new EventModel(event_data)); } } -} \ No newline at end of file +} diff --git a/ui/src/components/calendar/EventDate.vue b/ui/src/components/calendar/EventDate.vue index 6fcd588..044ee11 100644 --- a/ui/src/components/calendar/EventDate.vue +++ b/ui/src/components/calendar/EventDate.vue @@ -17,23 +17,23 @@ - \ No newline at end of file + diff --git a/ui/src/components/calendar/EventModel.ts b/ui/src/components/calendar/EventModel.ts index 2558fd3..6bf732b 100644 --- a/ui/src/components/calendar/EventModel.ts +++ b/ui/src/components/calendar/EventModel.ts @@ -19,13 +19,11 @@ export class EventModel extends Model { this.summary = json_data.summary; this.description = json_data.description; - this.start = DateTime - .fromISO(json_data.dtstart) - .setLocale(navigator.language); - const end = DateTime - .fromISO(json_data.dtend) - .setLocale(navigator.language); + this.start = DateTime.fromISO(json_data.dtstart).setLocale( + navigator.language, + ); + const end = DateTime.fromISO(json_data.dtend).setLocale(navigator.language); this.duration = end.diff(this.start); } -} \ No newline at end of file +} diff --git a/ui/src/components/title/Clock.vue b/ui/src/components/title/Clock.vue index 6c9670d..6c3d95a 100644 --- a/ui/src/components/title/Clock.vue +++ b/ui/src/components/title/Clock.vue @@ -3,12 +3,12 @@