mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +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 {
|
||||
tsk.Fn(ctx)
|
||||
|
||||
timer := time.NewTimer(tsk.Interval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -84,13 +84,14 @@ type (
|
||||
}
|
||||
|
||||
APISummary struct {
|
||||
Healthy bool `json:"health"`
|
||||
Versions []string `json:"versions"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Build Build `json:"build"`
|
||||
Demo bool `json:"demo"`
|
||||
AllowRegistration bool `json:"allowRegistration"`
|
||||
Healthy bool `json:"health"`
|
||||
Versions []string `json:"versions"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
Build Build `json:"build"`
|
||||
Latest services.Latest `json:"latest"`
|
||||
Demo bool `json:"demo"`
|
||||
AllowRegistration bool `json:"allowRegistration"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -123,6 +124,7 @@ func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) errchain.Hand
|
||||
Title: "Homebox",
|
||||
Message: "Track, Manage, and Organize your Things",
|
||||
Build: build,
|
||||
Latest: ctrl.svc.BackgroundService.GetLatestVersion(),
|
||||
Demo: ctrl.isDemo,
|
||||
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 {
|
||||
runner.AddFunc("debug", func(ctx context.Context) error {
|
||||
debugserver := http.Server{
|
||||
|
||||
@@ -2950,6 +2950,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"services.Latest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services.UserRegistration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2982,6 +2993,9 @@ const docTemplate = `{
|
||||
"health": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"latest": {
|
||||
"$ref": "#/definitions/services.Latest"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -2943,6 +2943,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"services.Latest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services.UserRegistration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2975,6 +2986,9 @@
|
||||
"health": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"latest": {
|
||||
"$ref": "#/definitions/services.Latest"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -654,6 +654,13 @@ definitions:
|
||||
value:
|
||||
type: number
|
||||
type: object
|
||||
services.Latest:
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
services.UserRegistration:
|
||||
properties:
|
||||
email:
|
||||
@@ -675,6 +682,8 @@ definitions:
|
||||
type: boolean
|
||||
health:
|
||||
type: boolean
|
||||
latest:
|
||||
$ref: '#/definitions/services.Latest'
|
||||
message:
|
||||
type: string
|
||||
title:
|
||||
|
||||
@@ -61,7 +61,7 @@ func New(repos *repo.AllRepos, opts ...OptionsFunc) *AllServices {
|
||||
repo: repos,
|
||||
autoIncrementAssetID: options.autoIncrementAssetID,
|
||||
},
|
||||
BackgroundService: &BackgroundService{repos},
|
||||
BackgroundService: &BackgroundService{repos, Latest{}},
|
||||
Currencies: currencies.NewCurrencyService(options.currencies),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,8 +14,13 @@ import (
|
||||
"github.com/sysadminsmedia/homebox/backend/internal/data/types"
|
||||
)
|
||||
|
||||
type Latest struct {
|
||||
Version string `json:"version"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
type BackgroundService struct {
|
||||
repos *repo.AllRepos
|
||||
latest Latest
|
||||
}
|
||||
|
||||
func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
|
||||
@@ -79,3 +87,52 @@ func (svc *BackgroundService) SendNotifiersToday(ctx context.Context) error {
|
||||
|
||||
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"`
|
||||
AutoIncrementAssetID bool `yaml:"auto_increment_asset_id" conf:"default:true"`
|
||||
CurrencyConfig string `yaml:"currencies"`
|
||||
GithubReleaseCheck bool `yaml:"check_github_release" conf:"default:true"`
|
||||
}
|
||||
|
||||
type DebugConf struct {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
| 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_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"
|
||||
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-auto-increment-asset-id/$HBOX_OPTIONS_AUTO_INCREMENT_ASSET_ID <bool> (default: true)
|
||||
--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
|
||||
```
|
||||
:::
|
||||
|
||||
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
|
||||
-->
|
||||
<ModalConfirm />
|
||||
<AppOutdatedModal v-model="modals.outdated" :current="current ?? ''" :latest="latest ?? ''" />
|
||||
<ItemCreateModal v-model="modals.item" />
|
||||
<LabelCreateModal v-model="modals.label" />
|
||||
<LocationCreateModal v-model="modals.location" />
|
||||
@@ -100,10 +101,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { lt } from "semver";
|
||||
import { useLabelStore } from "~~/stores/labels";
|
||||
import { useLocationStore } from "~~/stores/locations";
|
||||
import MdiMenu from "~icons/mdi/menu";
|
||||
import MdiPlus from "~icons/mdi/plus";
|
||||
|
||||
import MdiHome from "~icons/mdi/home";
|
||||
import MdiFileTree from "~icons/mdi/file-tree";
|
||||
@@ -111,6 +111,8 @@
|
||||
import MdiAccount from "~icons/mdi/account";
|
||||
import MdiCog from "~icons/mdi/cog";
|
||||
import MdiWrench from "~icons/mdi/wrench";
|
||||
import MdiMenu from "~icons/mdi/menu";
|
||||
import MdiPlus from "~icons/mdi/plus";
|
||||
|
||||
const { t } = useI18n();
|
||||
const username = computed(() => authCtx.user?.name || "User");
|
||||
@@ -124,6 +126,15 @@
|
||||
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
|
||||
useFormatCurrency();
|
||||
const modals = reactive({
|
||||
@@ -131,6 +142,14 @@
|
||||
location: false,
|
||||
label: false,
|
||||
import: false,
|
||||
outdated: displayOutdatedWarning.value,
|
||||
});
|
||||
|
||||
watch(displayOutdatedWarning, () => {
|
||||
console.log("displayOutdatedWarning", displayOutdatedWarning.value);
|
||||
if (displayOutdatedWarning.value) {
|
||||
modals.outdated = true;
|
||||
}
|
||||
});
|
||||
|
||||
const dropdown = [
|
||||
|
||||
@@ -392,6 +392,11 @@ export interface ValueOverTimeEntry {
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface Latest {
|
||||
date: Date | string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface UserRegistration {
|
||||
email: string;
|
||||
name: string;
|
||||
@@ -404,6 +409,7 @@ export interface APISummary {
|
||||
build: Build;
|
||||
demo: boolean;
|
||||
health: boolean;
|
||||
latest: Latest;
|
||||
message: string;
|
||||
title: 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.",
|
||||
"title": "Import CSV File",
|
||||
"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": {
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"markdown-it": "^14.1.0",
|
||||
"pinia": "^2.2.8",
|
||||
"postcss": "^8.4.49",
|
||||
"semver": "^7.6.3",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"vue": "3.4.8",
|
||||
"vue-router": "^4.5.0"
|
||||
|
||||
3
frontend/pnpm-lock.yaml
generated
3
frontend/pnpm-lock.yaml
generated
@@ -68,6 +68,9 @@ importers:
|
||||
postcss:
|
||||
specifier: ^8.4.49
|
||||
version: 8.4.49
|
||||
semver:
|
||||
specifier: ^7.6.3
|
||||
version: 7.6.3
|
||||
tailwindcss:
|
||||
specifier: ^3.4.15
|
||||
version: 3.4.15
|
||||
|
||||
Reference in New Issue
Block a user