mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
Add a warning when the version does not match the latest release (#442)
Some checks failed
Docker publish rootless / build-rootless (push) Waiting to run
Docker publish / build (push) Waiting to run
Update Currencies / update-currencies (push) Waiting to run
Docker publish ARM / build (push) Has been cancelled
Docker publish rootless ARM / build-rootless (push) Has been cancelled
Dockerhub publish / build (push) Has been cancelled
Some checks failed
Docker publish rootless / build-rootless (push) Waiting to run
Docker publish / build (push) Waiting to run
Update Currencies / update-currencies (push) Waiting to run
Docker publish ARM / build (push) Has been cancelled
Docker publish rootless ARM / build-rootless (push) Has been cancelled
Dockerhub publish / build (push) Has been cancelled
This commit is contained in:
@@ -23,8 +23,9 @@ func NewTask(name string, interval time.Duration, fn func(context.Context)) *Bac
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tsk *BackgroundTask) Start(ctx context.Context) error {
|
func (tsk *BackgroundTask) Start(ctx context.Context) error {
|
||||||
timer := time.NewTimer(tsk.Interval)
|
tsk.Fn(ctx)
|
||||||
|
|
||||||
|
timer := time.NewTimer(tsk.Interval)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|||||||
@@ -84,13 +84,14 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
APISummary struct {
|
APISummary struct {
|
||||||
Healthy bool `json:"health"`
|
Healthy bool `json:"health"`
|
||||||
Versions []string `json:"versions"`
|
Versions []string `json:"versions"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Build Build `json:"build"`
|
Build Build `json:"build"`
|
||||||
Demo bool `json:"demo"`
|
Latest services.Latest `json:"latest"`
|
||||||
AllowRegistration bool `json:"allowRegistration"`
|
Demo bool `json:"demo"`
|
||||||
|
AllowRegistration bool `json:"allowRegistration"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,6 +124,7 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
|
|||||||
Title: "Homebox",
|
Title: "Homebox",
|
||||||
Message: "Track, Manage, and Organize your Things",
|
Message: "Track, Manage, and Organize your Things",
|
||||||
Build: build,
|
Build: build,
|
||||||
|
Latest: ctrl.svc.BackgroundService.GetLatestVersion(),
|
||||||
Demo: ctrl.isDemo,
|
Demo: ctrl.isDemo,
|
||||||
AllowRegistration: ctrl.allowRegistration,
|
AllowRegistration: ctrl.allowRegistration,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -254,6 +254,18 @@ func run(cfg *config.Config) error {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
if cfg.Options.GithubReleaseCheck {
|
||||||
|
runner.AddPlugin(NewTask("get-latest-github-release", time.Hour, func(ctx context.Context) {
|
||||||
|
log.Debug().Msg("running get latest github release")
|
||||||
|
err := app.services.BackgroundService.GetLatestGithubRelease(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("failed to get latest github release")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Debug.Enabled {
|
if cfg.Debug.Enabled {
|
||||||
runner.AddFunc("debug", func(ctx context.Context) error {
|
runner.AddFunc("debug", func(ctx context.Context) error {
|
||||||
debugserver := http.Server{
|
debugserver := http.Server{
|
||||||
|
|||||||
@@ -2950,6 +2950,17 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"services.Latest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services.UserRegistration": {
|
"services.UserRegistration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2982,6 +2993,9 @@ const docTemplate = `{
|
|||||||
"health": {
|
"health": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"latest": {
|
||||||
|
"$ref": "#/definitions/services.Latest"
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2943,6 +2943,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"services.Latest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services.UserRegistration": {
|
"services.UserRegistration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2975,6 +2986,9 @@
|
|||||||
"health": {
|
"health": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"latest": {
|
||||||
|
"$ref": "#/definitions/services.Latest"
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -654,6 +654,13 @@ definitions:
|
|||||||
value:
|
value:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
services.Latest:
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
services.UserRegistration:
|
services.UserRegistration:
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
@@ -675,6 +682,8 @@ definitions:
|
|||||||
type: boolean
|
type: boolean
|
||||||
health:
|
health:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
latest:
|
||||||
|
$ref: '#/definitions/services.Latest'
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
title:
|
title:
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
|
|||||||
repo: repos,
|
repo: repos,
|
||||||
autoIncrementAssetID: options.autoIncrementAssetID,
|
autoIncrementAssetID: options.autoIncrementAssetID,
|
||||||
},
|
},
|
||||||
BackgroundService: &BackgroundService{repos},
|
BackgroundService: &BackgroundService{repos, Latest{}},
|
||||||
Currencies: currencies.NewCurrencyService(options.currencies),
|
Currencies: currencies.NewCurrencyService(options.currencies),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,8 +14,13 @@ import (
|
|||||||
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
|
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Latest struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
}
|
||||||
type BackgroundService struct {
|
type BackgroundService struct {
|
||||||
repos *repo.AllRepos
|
repos *repo.AllRepos
|
||||||
|
latest Latest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
|
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
|
||||||
@@ -79,3 +87,52 @@ func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *BackgroundService) GetLatestGithubRelease(ctx context.Context) error {
|
||||||
|
url := "https://api.github.com/repos/sysadminsmedia/homebox/releases/latest"
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create latest version request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "Homebox-Version-Checker")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to make latest version request: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error closing latest version response body: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("latest version unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignoring fields that are not relevant
|
||||||
|
type Release struct {
|
||||||
|
ReleaseVersion string `json:"tag_name"`
|
||||||
|
PublishedAt time.Time `json:"published_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var release Release
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode latest version response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.latest = Latest{
|
||||||
|
Version: release.ReleaseVersion,
|
||||||
|
Date: release.PublishedAt.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *BackgroundService) GetLatestVersion() (Latest) {
|
||||||
|
return svc.latest
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ type Options struct {
|
|||||||
AllowRegistration bool `yaml:"disable_registration" conf:"default:true"`
|
AllowRegistration bool `yaml:"disable_registration" conf:"default:true"`
|
||||||
AutoIncrementAssetID bool `yaml:"auto_increment_asset_id" conf:"default:true"`
|
AutoIncrementAssetID bool `yaml:"auto_increment_asset_id" conf:"default:true"`
|
||||||
CurrencyConfig string `yaml:"currencies"`
|
CurrencyConfig string `yaml:"currencies"`
|
||||||
|
GithubReleaseCheck bool `yaml:"check_github_release" conf:"default:true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DebugConf struct {
|
type DebugConf struct {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
| HBOX_MAILER_FROM | | email from address to use |
|
| HBOX_MAILER_FROM | | email from address to use |
|
||||||
| HBOX_SWAGGER_HOST | 7745 | swagger host to use, if not set swagger will be disabled |
|
| HBOX_SWAGGER_HOST | 7745 | swagger host to use, if not set swagger will be disabled |
|
||||||
| HBOX_SWAGGER_SCHEMA | `http` | swagger schema to use, can be one of: `http`, `https` |
|
| HBOX_SWAGGER_SCHEMA | `http` | swagger schema to use, can be one of: `http`, `https` |
|
||||||
|
| HBOX_OPTIONS_CHECK_GITHUB_RELEASE | true | check for new github releases |
|
||||||
|
|
||||||
::: tip "CLI Arguments"
|
::: tip "CLI Arguments"
|
||||||
If you're deploying without docker you can use command line arguments to configure the application. Run `homebox --help` for more information.
|
If you're deploying without docker you can use command line arguments to configure the application. Run `homebox --help` for more information.
|
||||||
@@ -55,6 +55,7 @@ OPTIONS
|
|||||||
--options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION <bool> (default: true)
|
--options-allow-registration/$HBOX_OPTIONS_ALLOW_REGISTRATION <bool> (default: true)
|
||||||
--options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID <bool> (default: true)
|
--options-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID <bool> (default: true)
|
||||||
--options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG <string>
|
--options-currency-config/$HBOX_OPTIONS_CURRENCY_CONFIG <string>
|
||||||
|
--options-check-github-release/$HBOX_OPTIONS_CHECK_GITHUB_RELEASE <bool> (default: true)
|
||||||
--help/-h display this help message
|
--help/-h display this help message
|
||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|||||||
41
frontend/components/App/OutdatedModal.vue
Normal file
41
frontend/components/App/OutdatedModal.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<BaseModal v-model="modal">
|
||||||
|
<template #title>🎉 {{ $t("components.app.outdated.new_version_available") }} 🎉</template>
|
||||||
|
<div class="p-4">
|
||||||
|
<p>{{ $t("components.app.outdated.current_version") }}: {{ current }}</p>
|
||||||
|
<p>{{ $t("components.app.outdated.latest_version") }}: {{ latest }}</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/sysadminsmedia/homebox/releases" target="_blank" rel="noopener" class="link">
|
||||||
|
{{ $t("components.app.outdated.new_version_available_link") }}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-warning" @click="hide">
|
||||||
|
{{ $t("components.app.outdated.dismiss") }}
|
||||||
|
</button>
|
||||||
|
</BaseModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
current: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
latest: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const modal = useVModel(props, "modelValue");
|
||||||
|
|
||||||
|
const hide = () => {
|
||||||
|
modal.value = false;
|
||||||
|
localStorage.setItem("latestVersion", props.latest);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
up the tree
|
up the tree
|
||||||
-->
|
-->
|
||||||
<ModalConfirm />
|
<ModalConfirm />
|
||||||
|
<AppOutdatedModal v-model="modals.outdated" :current="current ?? ''" :latest="latest ?? ''" />
|
||||||
<ItemCreateModal v-model="modals.item" />
|
<ItemCreateModal v-model="modals.item" />
|
||||||
<LabelCreateModal v-model="modals.label" />
|
<LabelCreateModal v-model="modals.label" />
|
||||||
<LocationCreateModal v-model="modals.location" />
|
<LocationCreateModal v-model="modals.location" />
|
||||||
@@ -100,10 +101,9 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { lt } from "semver";
|
||||||
import { useLabelStore } from "~~/stores/labels";
|
import { useLabelStore } from "~~/stores/labels";
|
||||||
import { useLocationStore } from "~~/stores/locations";
|
import { useLocationStore } from "~~/stores/locations";
|
||||||
import MdiMenu from "~icons/mdi/menu";
|
|
||||||
import MdiPlus from "~icons/mdi/plus";
|
|
||||||
|
|
||||||
import MdiHome from "~icons/mdi/home";
|
import MdiHome from "~icons/mdi/home";
|
||||||
import MdiFileTree from "~icons/mdi/file-tree";
|
import MdiFileTree from "~icons/mdi/file-tree";
|
||||||
@@ -111,6 +111,8 @@
|
|||||||
import MdiAccount from "~icons/mdi/account";
|
import MdiAccount from "~icons/mdi/account";
|
||||||
import MdiCog from "~icons/mdi/cog";
|
import MdiCog from "~icons/mdi/cog";
|
||||||
import MdiWrench from "~icons/mdi/wrench";
|
import MdiWrench from "~icons/mdi/wrench";
|
||||||
|
import MdiMenu from "~icons/mdi/menu";
|
||||||
|
import MdiPlus from "~icons/mdi/plus";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const username = computed(() => authCtx.user?.name || "User");
|
const username = computed(() => authCtx.user?.name || "User");
|
||||||
@@ -124,6 +126,15 @@
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const latest = computed(() => status.value?.latest.version);
|
||||||
|
const current = computed(() => status.value?.build.version);
|
||||||
|
|
||||||
|
const isDev = computed(() => import.meta.dev || !current.value?.includes("."));
|
||||||
|
const isOutdated = computed(() => current.value && latest.value && lt(current.value, latest.value));
|
||||||
|
const hasHiddenLatest = computed(() => localStorage.getItem("latestVersion") === latest.value);
|
||||||
|
|
||||||
|
const displayOutdatedWarning = computed(() => !isDev && !hasHiddenLatest.value && isOutdated.value);
|
||||||
|
|
||||||
// Preload currency format
|
// Preload currency format
|
||||||
useFormatCurrency();
|
useFormatCurrency();
|
||||||
const modals = reactive({
|
const modals = reactive({
|
||||||
@@ -131,6 +142,14 @@
|
|||||||
location: false,
|
location: false,
|
||||||
label: false,
|
label: false,
|
||||||
import: false,
|
import: false,
|
||||||
|
outdated: displayOutdatedWarning.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(displayOutdatedWarning, () => {
|
||||||
|
console.log("displayOutdatedWarning", displayOutdatedWarning.value);
|
||||||
|
if (displayOutdatedWarning.value) {
|
||||||
|
modals.outdated = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const dropdown = [
|
const dropdown = [
|
||||||
|
|||||||
@@ -392,6 +392,11 @@ export interface ValueOverTimeEntry {
|
|||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Latest {
|
||||||
|
date: Date | string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserRegistration {
|
export interface UserRegistration {
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -404,6 +409,7 @@ export interface APISummary {
|
|||||||
build: Build;
|
build: Build;
|
||||||
demo: boolean;
|
demo: boolean;
|
||||||
health: boolean;
|
health: boolean;
|
||||||
|
latest: Latest;
|
||||||
message: string;
|
message: string;
|
||||||
title: string;
|
title: string;
|
||||||
versions: string[];
|
versions: string[];
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
|
"description": "Import a CSV file containing your items, labels, and locations. See documentation for more information on the \nrequired format.",
|
||||||
"title": "Import CSV File",
|
"title": "Import CSV File",
|
||||||
"upload": "Upload"
|
"upload": "Upload"
|
||||||
|
},
|
||||||
|
"outdated": {
|
||||||
|
"current_version": "Current Version",
|
||||||
|
"latest_version": "Latest Version",
|
||||||
|
"new_version_available": "New Version Available",
|
||||||
|
"new_version_available_link": "Click here to view the release notes",
|
||||||
|
"dismiss": "Dismiss"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global": {
|
"global": {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"pinia": "^2.2.8",
|
"pinia": "^2.2.8",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
|
"semver": "^7.6.3",
|
||||||
"tailwindcss": "^3.4.15",
|
"tailwindcss": "^3.4.15",
|
||||||
"vue": "3.4.8",
|
"vue": "3.4.8",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
|
|||||||
3
frontend/pnpm-lock.yaml
generated
3
frontend/pnpm-lock.yaml
generated
@@ -68,6 +68,9 @@ importers:
|
|||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.49
|
specifier: ^8.4.49
|
||||||
version: 8.4.49
|
version: 8.4.49
|
||||||
|
semver:
|
||||||
|
specifier: ^7.6.3
|
||||||
|
version: 7.6.3
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.15
|
specifier: ^3.4.15
|
||||||
version: 3.4.15
|
version: 3.4.15
|
||||||
|
|||||||
Reference in New Issue
Block a user