diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index ee347c09c..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-env node */ -require("@rushstack/eslint-patch/modern-module-resolution"); - -module.exports = { - root: true, - extends: [ - "plugin:vue/vue3-essential", - "eslint:recommended", - "@vue/eslint-config-prettier", - ], - env: { - "vue/setup-compiler-macros": true, - }, - rules: { - "vue/multi-word-component-names": "off", - }, -}; diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 36e6b3061..d2ccc0316 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,6 @@ # These are supported funding model platforms -custom: ['https://www.buymeacoffee.com/bastien'] +# These are supported funding model platforms + +github: [bastienwirtz] +buy_me_a_coffee: bastien diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e0063d580..0bb193d3d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,10 +9,11 @@ Fixes # (issue) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Refactoring ## Checklist: - [ ] I've read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md) -- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers. -- [ ] I have made corresponding changes to the documentation (README.md). +- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers. +- [ ] I have made corresponding changes to the documentation (`README.md`). - [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 5968d1bd3..4b6750290 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -4,50 +4,45 @@ name: Dockerhub on: push: tags: [v*] - branches: [ main ] - jobs: dockerhub: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - - - name: Set tag name - run: | - if [[ ${{ github.ref_type }} == "tag" ]]; then - echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV - else - echo "IMAGE_TAG=latest" >> $GITHUB_ENV - fi - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: push: true tags: | - b4bz/homer:${{env.IMAGE_TAG}} - ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }} - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 + b4bz/homer:latest + b4bz/homer:${{ github.ref_name }} + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ github.ref_name }} + platforms: linux/amd64,linux/arm/v7,linux/arm/v6,linux/arm64 + build-args: | + VERSION_TAG=${{ github.ref_name }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 258e77e6d..773033afd 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -11,28 +11,24 @@ on: jobs: build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - + timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + name: pnpm setup + uses: pnpm/action-setup@v4 + - + name: Node.js setup + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} - cache: 'yarn' + node-version: 24 + cache: 'pnpm' - name: install dependencies - run: yarn install + run: pnpm install --frozen-lockfile - name: Check code style & potentential issues - run: yarn lint - + run: pnpm lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d554a5f98..a0e88d5c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,15 +9,25 @@ jobs: build: name: Upload Release Asset runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - + name: pnpm setup + uses: pnpm/action-setup@v4 + - + name: Node.js setup + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'pnpm' - name: Build project run: | - yarn install - yarn build + pnpm install --frozen-lockfile + pnpm build - name: Create artifact working-directory: "dist" @@ -25,7 +35,7 @@ jobs: - name: Create Release id: create_release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} generate_release_notes: true diff --git a/.gitignore b/.gitignore index ee51c8812..6eb9005be 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,8 @@ yarn-error.log* # App configuration config.yml -.drone.yml \ No newline at end of file +.drone.yml + +# Specific Agent file +CLAUDE.md +GEMINI.md diff --git a/.jsconfig.json b/.jsconfig.json new file mode 100644 index 000000000..7c82acb97 --- /dev/null +++ b/.jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] + } \ No newline at end of file diff --git a/.schema/config-schema.json b/.schema/config-schema.json new file mode 100644 index 000000000..f75166887 --- /dev/null +++ b/.schema/config-schema.json @@ -0,0 +1,359 @@ +{ + "$id": "https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json", + "$schema": "http://json-schema.org/draft-07/schema", + "description": "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md", + "examples": [], + "title": "Homer Dashboard configuration", + "type": "object", + "definitions": { + "Colors": { + "type": "object", + "additionalProperties": false, + "properties": { + "light": { + "$ref": "#/definitions/ColorSet" + }, + "dark": { + "$ref": "#/definitions/ColorSet" + } + }, + "title": "Colors" + }, + "ColorSet": { + "type": "object", + "additionalProperties": false, + "properties": { + "highlight-primary": { + "type": "string" + }, + "highlight-secondary": { + "type": "string" + }, + "highlight-hover": { + "type": "string" + }, + "background": { + "type": "string" + }, + "card-background": { + "type": "string" + }, + "text": { + "type": "string" + }, + "text-header": { + "type": "string" + }, + "text-title": { + "type": "string" + }, + "text-subtitle": { + "type": "string" + }, + "card-shadow": { + "type": "string" + }, + "link": { + "type": "string" + }, + "link-hover": { + "type": "string" + }, + "background-image": { + "type": "string" + } + } + }, + "Defaults": { + "type": "object", + "additionalProperties": false, + "properties": { + "layout": { + "enum": [ + "columns", + "list" + ], + "description": "Layout of the dashboard, either 'columns' or 'list'" + }, + "colorTheme": { + "enum": [ + "auto", + "light", + "dark" + ], + "description": "One of 'auto', 'light', or 'dark'" + } + }, + "title": "Defaults" + }, + "Hotkey": { + "type": "object", + "additionalProperties": false, + "properties": { + "search": { + "type": "string", + "description": "hotkey for search, e.g. Shift" + } + }, + "required": [ + "search" + ] + }, + "Link": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name as seen in the navbar" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon" + }, + "url": { + "type": "string", + "description": "Url of the link. When #filename is used, it is a link to another homer page, while 'filename' is the name of the config file" + }, + "target": { + "type": "string", + "description": "html tag target attribute like _blank for a new page" + } + }, + "required": [ + "url" + ], + "title": "Link" + }, + "Message": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "mapping": { + "$ref": "#/definitions/Mapping", + "description": "Mapping for the content loaded from the URL" + }, + "refreshInterval": { + "type": "integer", + "description": "The refresh interval in milliseconds for reloading the message url" + }, + "style": { + "type": "string", + "description": "See https://bulma.io/documentation/components/message/#colors for styling options" + }, + "title": { + "type": "string", + "description": "Title of the message box" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the message box" + }, + "content": { + "type": "string", + "description": "HTML content for the message box" + } + }, + "title": "Messagebox" + }, + "Mapping": { + "type": "object", + "additionalProperties": true, + "title": "Mapping" + }, + "Proxy": { + "type": "object", + "additionalProperties": false, + "properties": { + "useCredentials": { + "type": "boolean", + "description": "# send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level. " + }, + "headers": { + "$ref": "#/definitions/Headers", + "description": "send custom headers when fetching service specific data. Can also be set on a service level." + } + }, + "title": "Proxy" + }, + "Headers": { + "type": "object", + "additionalProperties": true, + "title": "Headers" + }, + "Service": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Service name" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the service" + }, + "logo": { + "type": "string", + "description": "A path to an image can also be provided. Note that icon take precedence if both icon and logo are set." + }, + "class": { + "type": "string", + "description": "Optional css class to add on the service group. Example 'highlight-purple'" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Item" + } + } + }, + "required": [ + "items" + ], + "title": "Service" + }, + "Item": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "logo": { + "type": "string", + "description": "Path to a logo. Alternatively a fa icon can be provided" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the item, alternative for logo" + }, + "subtitle": { + "type": "string" + }, + "tag": { + "type": "string", + "description": "Show tag" + }, + "keywords": { + "type": "string", + "description": "Optional keyword used for searching purpose" + }, + "url": { + "type": "string", + "description": "Url of this item" + }, + "target": { + "type": "string", + "description": "html tag target attribute like _blank for a new page" + }, + "tagstyle": { + "type": "string", + "description": "Styleclass for the tag" + }, + "type": { + "type": "string", + "description": "Optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`" + } + }, + "title": "Item" + } + }, + "properties": { + "externalConfig": { + "type": "string", + "description": "Use external configuration file. Using this will ignore remaining config in this file externalConfig: https://example.com/server-luci/config.yaml" + }, + "title": { + "type": "string", + "description": "Title of the dashboard" + }, + "subtitle": { + "type": "string", + "description": "Subtitle of the dashboard" + }, + "documentTitle": { + "type": "string", + "description": "Title of the document. When not filled, title (and subtitle will be used)" + }, + "logo": { + "type": "string", + "description": "Path to logo image" + }, + "icon": { + "type": "string", + "description": "Dashboard icon" + }, + "header": { + "type": "boolean", + "description": "Show header, default is true" + }, + "hotkey": { + "$ref": "#/definitions/Hotkey", + "description": "Define hotkeys, for example for search" + }, + "footer": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ], + "description": "footer Line content. HTML is supported. Set false if you want to hide it." + }, + "columns": { + "type": "string", + "description": "'auto' or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)", + "format": "integer" + }, + "connectivityCheck": { + "type": "boolean", + "description": "# whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example). You should set it to true when using an authentication proxy, it also reloads the page when a redirection is detected when checking connectivity." + }, + "proxy": { + "$ref": "#/definitions/Proxy", + "description": "Optional: Proxy / hosting option" + }, + "defaults": { + "$ref": "#/definitions/Defaults" + }, + "theme": { + "type": "string", + "description": "'default' or one of the themes available in 'src/assets/themes'" + }, + "stylesheet": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Will load custom CSS files. Especially useful for custom icon sets. Entries are paths to the stylesheets" + }, + "colors": { + "$ref": "#/definitions/Colors" + }, + "message": { + "$ref": "#/definitions/Message", + "description": "Messagebox" + }, + "links": { + "description": "Links in the navigation bar", + "type": "array", + "items": { + "$ref": "#/definitions/Link" + } + }, + "services": { + "description": "Services", + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..25d789b9c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,97 @@ +# AGENTS Instructions + +This file provides guidance to AI Agents when working with code in this repository. + +## Development Commands + +```bash +pnpm install # Install dependencies (PNPM enforced via packageManager) +pnpm dev # Start development server on http://localhost:3000 +pnpm mock # Start mock API server for testing service integrations +pnpm build # Build for production +pnpm preview # Preview production build +pnpm lint # Run ESLint with auto-fix +``` + +## Architecture Overview + +Homer is a static Vue.js 3 PWA dashboard that loads configuration from YAML files. The architecture is service-oriented with dynamic component loading. + +### Core Application Structure + +- **Entry Point**: `src/main.js` mounts the Vue app +- **Root Component**: `src/App.vue` handles layout, configuration loading, and routing +- **Configuration System**: YAML-based with runtime merging of defaults (`src/assets/defaults.yml`) and user config (`/assets/config.yml`) +- **Service Components**: 53 specialized integrations in `src/components/services/` that extend a Generic component pattern + +### Service Integration Pattern + +All service components follow this architecture: + +- Extend `Generic.vue` using Vue slots (`