mirror of
https://github.com/eduardolat/pgbackweb.git
synced 2026-01-26 06:29:03 -06:00
Refactor javascript following Locality of Behaviour (LoB) principle
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
export const githubRepoInfo = {
|
||||
name: 'githubRepoInfo',
|
||||
fn: () => ({
|
||||
stars: '',
|
||||
latestRelease: '',
|
||||
|
||||
async init () {
|
||||
const stars = await this.getStars()
|
||||
if (stars !== null) {
|
||||
this.stars = stars
|
||||
}
|
||||
|
||||
const latestRelease = await this.getLatestRelease()
|
||||
if (latestRelease !== null) {
|
||||
this.latestRelease = latestRelease
|
||||
}
|
||||
},
|
||||
async getStars () {
|
||||
const cacheKey = 'pbw_gh_stars'
|
||||
const cachedData = this.getCachedData(cacheKey)
|
||||
if (cachedData !== null) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
const url = 'https://api.github.com/repos/eduardolat/pgbackweb'
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
const data = await response.json()
|
||||
this.cacheData(cacheKey, data.stargazers_count)
|
||||
return data.stargazers_count
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async getLatestRelease () {
|
||||
const cacheKey = 'pbw_gh_last_release'
|
||||
const cachedData = this.getCachedData(cacheKey)
|
||||
if (cachedData !== null) {
|
||||
return cachedData
|
||||
}
|
||||
|
||||
const url = 'https://api.github.com/repos/eduardolat/pgbackweb/releases/latest'
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
const data = await response.json()
|
||||
this.cacheData(cacheKey, data.name)
|
||||
return data.name
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
getCachedData (key) {
|
||||
const cachedJSON = localStorage.getItem(key)
|
||||
if (!cachedJSON) {
|
||||
return null
|
||||
}
|
||||
const cached = JSON.parse(cachedJSON)
|
||||
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
|
||||
return cached.value
|
||||
}
|
||||
return null
|
||||
},
|
||||
cacheData (key, value) {
|
||||
const data = JSON.stringify({
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
localStorage.setItem(key, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,14 +2,10 @@ import { initThemeHelper } from './init-theme-helper.js'
|
||||
import { initSweetAlert2 } from './init-sweetalert2.js'
|
||||
import { initNotyf } from './init-notyf.js'
|
||||
import { initHTMX } from './init-htmx.js'
|
||||
import { initAlpineComponents } from './init-alpine-components.js'
|
||||
import { initHelpers } from './init-helpers.js'
|
||||
import { initDashboardAsideScroll } from './dashboard-aside-scroll.js'
|
||||
|
||||
initThemeHelper()
|
||||
initSweetAlert2()
|
||||
initNotyf()
|
||||
initHTMX()
|
||||
initAlpineComponents()
|
||||
initHelpers()
|
||||
initDashboardAsideScroll()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
export function initDashboardAsideScroll () {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const el = document.getElementById('dashboard-aside')
|
||||
const key = 'dashboard-aside-scroll-position'
|
||||
|
||||
if (!el) return
|
||||
|
||||
const saveScrollPosition = window.debounce(
|
||||
() => {
|
||||
const scrollPosition = el.scrollTop
|
||||
localStorage.setItem(key, scrollPosition)
|
||||
},
|
||||
200
|
||||
)
|
||||
el.addEventListener('scroll', saveScrollPosition)
|
||||
|
||||
const scrollPosition = localStorage.getItem(key)
|
||||
if (scrollPosition) {
|
||||
el.scrollTop = parseInt(scrollPosition, 10)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { changeThemeButton } from './alpine-components/change-theme-button.js'
|
||||
import { githubRepoInfo } from './alpine-components/github-repo-info.js'
|
||||
import { dashboardAsideItem } from './alpine-components/dashboard-aside-item.js'
|
||||
import { genericSlider } from './alpine-components/generic-slider.js'
|
||||
import { optionsDropdown } from './alpine-components/options-dropdown.js'
|
||||
|
||||
export function initAlpineComponents () {
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data(changeThemeButton.name, changeThemeButton.fn)
|
||||
Alpine.data(githubRepoInfo.name, githubRepoInfo.fn)
|
||||
Alpine.data(dashboardAsideItem.name, dashboardAsideItem.fn)
|
||||
Alpine.data(genericSlider.name, genericSlider.fn)
|
||||
Alpine.data(optionsDropdown.name, optionsDropdown.fn)
|
||||
})
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type ChangeThemeButtonParams struct {
|
||||
|
||||
func ChangeThemeButton(params ChangeThemeButtonParams) gomponents.Node {
|
||||
return html.Div(
|
||||
alpine.XData("changeThemeButton"),
|
||||
alpine.XData("alpineChangeThemeButton()"),
|
||||
alpine.XCloak(),
|
||||
|
||||
components.Classes{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const changeThemeButton = {
|
||||
name: 'changeThemeButton',
|
||||
fn: () => ({
|
||||
window.alpineChangeThemeButton = function () {
|
||||
return {
|
||||
theme: '',
|
||||
|
||||
loadTheme () {
|
||||
@@ -16,5 +15,5 @@ export const changeThemeButton = {
|
||||
init () {
|
||||
this.loadTheme()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func OptionsDropdown(children ...gomponents.Node) gomponents.Node {
|
||||
return html.Div(
|
||||
html.Class("inline-block"),
|
||||
alpine.XData("options_dropdown"),
|
||||
alpine.XData("alpineOptionsDropdown()"),
|
||||
alpine.XOn("mouseenter", "open()"),
|
||||
alpine.XOn("mouseleave", "close()"),
|
||||
html.Button(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const optionsDropdown = {
|
||||
name: 'options_dropdown',
|
||||
fn: () => ({
|
||||
window.alpineOptionsDropdown = function () {
|
||||
return {
|
||||
isOpen: false,
|
||||
buttonEl: null,
|
||||
contentEl: null,
|
||||
@@ -43,5 +42,5 @@ export const optionsDropdown = {
|
||||
this.contentEl.style.top = `${buttonRect.top - contentHeight}px`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func StarOnGithub(size size) gomponents.Node {
|
||||
return html.A(
|
||||
alpine.XData("githubRepoInfo"),
|
||||
alpine.XData("alpineStarOnGithub()"),
|
||||
alpine.XCloak(),
|
||||
components.Classes{
|
||||
"btn btn-neutral": true,
|
||||
|
||||
42
internal/view/web/component/star_on_github.inc.js
Normal file
42
internal/view/web/component/star_on_github.inc.js
Normal file
@@ -0,0 +1,42 @@
|
||||
window.alpineStarOnGithub = function () {
|
||||
return {
|
||||
stars: null,
|
||||
|
||||
async init () {
|
||||
const stars = await this.getStars()
|
||||
if (stars !== null) {
|
||||
this.stars = stars
|
||||
}
|
||||
},
|
||||
|
||||
async getStars () {
|
||||
const cacheKey = 'pbw-gh-stars'
|
||||
|
||||
const cachedJSON = localStorage.getItem(cacheKey)
|
||||
if (cachedJSON) {
|
||||
const cached = JSON.parse(cachedJSON)
|
||||
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
|
||||
return cached.value
|
||||
}
|
||||
}
|
||||
|
||||
const url = 'https://api.github.com/repos/eduardolat/pgbackweb'
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
const data = await response.json()
|
||||
const value = data.stargazers_count
|
||||
const dataToCache = JSON.stringify({
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
localStorage.setItem(cacheKey, dataToCache)
|
||||
return value
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,9 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
|
||||
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/component"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
|
||||
"github.com/google/uuid"
|
||||
@@ -201,111 +199,8 @@ func indexPage(
|
||||
BgColors: []string{blueColor, greenColor, redColor},
|
||||
}),
|
||||
),
|
||||
html.Div(
|
||||
alpine.XData("genericSlider(4)"),
|
||||
alpine.XCloak(),
|
||||
html.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
|
||||
component.H2Text("How to use PG Back Web"),
|
||||
component.CardBox(component.CardBoxParams{
|
||||
Class: "mt-4 space-y-4 max-w-[600px]",
|
||||
Children: []gomponents.Node{
|
||||
html.Div(
|
||||
html.Class("flex justify-center"),
|
||||
html.Ul(
|
||||
html.Class("steps"),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 1 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create database"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 2 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create destination"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 3 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create backup"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 4 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Wait for executions"),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 1"),
|
||||
component.H3Text("Create database"),
|
||||
component.PText(`
|
||||
To create a database, click on the "Databases" menu item on the
|
||||
left sidebar. Then click on the "Create database" button. Fill
|
||||
in the form and click on the "Save" button. You can create as
|
||||
many databases as you want to backup.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 2"),
|
||||
component.H3Text("Create S3 destination (optional)"),
|
||||
component.PText(`
|
||||
To create a destination, click on the "Destinations" menu item on
|
||||
the left sidebar. Then click on the "Create destination" button.
|
||||
Fill in the form and click on the "Save" button. You can create
|
||||
as many destinations as you want to store the backups. If you
|
||||
don't want to use S3 destinations and store the backups locally,
|
||||
you can skip this step.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 3"),
|
||||
component.H3Text("Create backup"),
|
||||
component.PText(`
|
||||
To create a backup you need to have at least one database and one
|
||||
destination. Click on the "Backups" menu item on the left sidebar.
|
||||
Then click on the "Create backup" button. Fill in the form and
|
||||
click on the "Save" button. You can create as many backups as you
|
||||
want including any combination of databases and destinations.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 4"),
|
||||
component.H3Text("Wait for executions"),
|
||||
component.PText(`
|
||||
When your backup is created and active, the system will start
|
||||
creating executions based on the schedule you defined. You can
|
||||
also create executions manually by clicking the "Run backup now"
|
||||
button on the backups list. You can see the executions on the
|
||||
"Executions" menu item on the left sidebar and then click on the
|
||||
"Show details" button to see the details, logs, and download or
|
||||
delete the backup file.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
html.Class("mt-4 flex justify-between"),
|
||||
html.Button(
|
||||
alpine.XBind("disabled", "!hasPrevSlide"),
|
||||
alpine.XOn("click", "prevSlide"),
|
||||
html.Class("btn btn-neutral btn-ghost"),
|
||||
lucide.ChevronLeft(),
|
||||
component.SpanText("Previous"),
|
||||
),
|
||||
html.Button(
|
||||
alpine.XBind("disabled", "!hasNextSlide"),
|
||||
alpine.XOn("click", "nextSlide"),
|
||||
html.Class("btn btn-neutral btn-ghost"),
|
||||
component.SpanText("Next"),
|
||||
lucide.ChevronRight(),
|
||||
),
|
||||
),
|
||||
},
|
||||
}),
|
||||
),
|
||||
indexHowTo(),
|
||||
}
|
||||
|
||||
return layout.Dashboard(reqCtx, layout.DashboardParams{
|
||||
|
||||
119
internal/view/web/dashboard/summary/index_how_to.go
Normal file
119
internal/view/web/dashboard/summary/index_how_to.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/component"
|
||||
"github.com/maragudk/gomponents"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
func indexHowTo() gomponents.Node {
|
||||
return html.Div(
|
||||
alpine.XData("alpineSummaryHowToSlider()"),
|
||||
alpine.XCloak(),
|
||||
html.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
|
||||
|
||||
component.H2Text("How to use PG Back Web"),
|
||||
|
||||
component.CardBox(component.CardBoxParams{
|
||||
Class: "mt-4 space-y-4 max-w-[600px]",
|
||||
Children: []gomponents.Node{
|
||||
html.Div(
|
||||
html.Class("flex justify-center"),
|
||||
html.Ul(
|
||||
html.Class("steps"),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 1 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create database"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 2 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create destination"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 3 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Create backup"),
|
||||
),
|
||||
html.Li(
|
||||
html.Class("step"),
|
||||
alpine.XBind("class", "currentSlide >= 4 ? 'step-primary' : ''"),
|
||||
gomponents.Text("Wait for executions"),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 1"),
|
||||
component.H3Text("Create database"),
|
||||
component.PText(`
|
||||
To create a database, click on the "Databases" menu item on the
|
||||
left sidebar. Then click on the "Create database" button. Fill
|
||||
in the form and click on the "Save" button. You can create as
|
||||
many databases as you want to backup.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 2"),
|
||||
component.H3Text("Create S3 destination (optional)"),
|
||||
component.PText(`
|
||||
To create a destination, click on the "Destinations" menu item on
|
||||
the left sidebar. Then click on the "Create destination" button.
|
||||
Fill in the form and click on the "Save" button. You can create
|
||||
as many destinations as you want to store the backups. If you
|
||||
don't want to use S3 destinations and store the backups locally,
|
||||
you can skip this step.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 3"),
|
||||
component.H3Text("Create backup"),
|
||||
component.PText(`
|
||||
To create a backup you need to have at least one database and one
|
||||
destination. Click on the "Backups" menu item on the left sidebar.
|
||||
Then click on the "Create backup" button. Fill in the form and
|
||||
click on the "Save" button. You can create as many backups as you
|
||||
want including any combination of databases and destinations.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
alpine.XShow("currentSlide === 4"),
|
||||
component.H3Text("Wait for executions"),
|
||||
component.PText(`
|
||||
When your backup is created and active, the system will start
|
||||
creating executions based on the schedule you defined. You can
|
||||
also create executions manually by clicking the "Run backup now"
|
||||
button on the backups list. You can see the executions on the
|
||||
"Executions" menu item on the left sidebar and then click on the
|
||||
"Show details" button to see the details, logs, and download or
|
||||
delete the backup file.
|
||||
`),
|
||||
),
|
||||
|
||||
html.Div(
|
||||
html.Class("mt-4 flex justify-between"),
|
||||
html.Button(
|
||||
alpine.XBind("disabled", "!hasPrevSlide"),
|
||||
alpine.XOn("click", "prevSlide"),
|
||||
html.Class("btn btn-neutral btn-ghost"),
|
||||
lucide.ChevronLeft(),
|
||||
component.SpanText("Previous"),
|
||||
),
|
||||
html.Button(
|
||||
alpine.XBind("disabled", "!hasNextSlide"),
|
||||
alpine.XOn("click", "nextSlide"),
|
||||
html.Class("btn btn-neutral btn-ghost"),
|
||||
component.SpanText("Next"),
|
||||
lucide.ChevronRight(),
|
||||
),
|
||||
),
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
export const genericSlider = {
|
||||
name: 'genericSlider',
|
||||
fn: (slidesQty = 0) => ({
|
||||
currentSlide: slidesQty > 0 ? 1 : 0,
|
||||
window.alpineSummaryHowToSlider = function () {
|
||||
return {
|
||||
slidesQty: 4,
|
||||
currentSlide: 1,
|
||||
|
||||
get hasNextSlide () {
|
||||
return this.currentSlide < slidesQty
|
||||
return this.currentSlide < this.slidesQty
|
||||
},
|
||||
|
||||
get hasPrevSlide () {
|
||||
return this.currentSlide > 1
|
||||
},
|
||||
|
||||
nextSlide () {
|
||||
if (this.hasNextSlide) this.currentSlide++
|
||||
},
|
||||
|
||||
prevSlide () {
|
||||
if (this.hasPrevSlide) this.currentSlide--
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func dashboardAsideItem(
|
||||
text, link string, strict bool,
|
||||
) gomponents.Node {
|
||||
return html.A(
|
||||
alpine.XData(fmt.Sprintf("dashboardAsideItem('%s', %t)", link, strict)),
|
||||
alpine.XData(fmt.Sprintf("alpineDashboardAsideItem('%s', %t)", link, strict)),
|
||||
html.Class("block flex flex-col items-center justify-center group"),
|
||||
html.Href(link),
|
||||
html.Button(
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
export const dashboardAsideItem = {
|
||||
name: 'dashboardAsideItem',
|
||||
fn: (link = '', strict = false) => ({
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const el = document.getElementById('dashboard-aside')
|
||||
const key = 'dashboard-aside-scroll-position'
|
||||
|
||||
if (!el) return
|
||||
|
||||
const saveScrollPosition = window.debounce(
|
||||
() => {
|
||||
const scrollPosition = el.scrollTop
|
||||
localStorage.setItem(key, scrollPosition)
|
||||
},
|
||||
200
|
||||
)
|
||||
el.addEventListener('scroll', saveScrollPosition)
|
||||
|
||||
const scrollPosition = localStorage.getItem(key)
|
||||
if (scrollPosition) {
|
||||
el.scrollTop = parseInt(scrollPosition, 10)
|
||||
}
|
||||
})
|
||||
|
||||
window.alpineDashboardAsideItem = function (link = '', strict = false) {
|
||||
return {
|
||||
link,
|
||||
strict,
|
||||
is_active: false,
|
||||
@@ -29,5 +49,5 @@ export const dashboardAsideItem = {
|
||||
this.checkActive()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/eduardolat/pgbackweb/internal/config"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/component"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
|
||||
"github.com/maragudk/gomponents"
|
||||
@@ -28,7 +24,7 @@ func dashboardHeader() gomponents.Node {
|
||||
Size: component.SizeMd,
|
||||
}),
|
||||
component.StarOnGithub(component.SizeMd),
|
||||
dashboardHeaderCheckForUpdates(),
|
||||
dashboardHeaderUpdates(),
|
||||
component.HxLoadingMd("header-indicator"),
|
||||
),
|
||||
html.Div(
|
||||
@@ -55,26 +51,3 @@ func dashboardHeader() gomponents.Node {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func dashboardHeaderCheckForUpdates() gomponents.Node {
|
||||
return html.A(
|
||||
alpine.XData("githubRepoInfo"),
|
||||
alpine.XCloak(),
|
||||
alpine.XShow(fmt.Sprintf(
|
||||
"latestRelease !== '' && latestRelease !== '%s'",
|
||||
config.Version,
|
||||
)),
|
||||
|
||||
components.Classes{
|
||||
"btn btn-warning": true,
|
||||
},
|
||||
html.Href("https://github.com/eduardolat/pgbackweb/releases"),
|
||||
html.Target("_blank"),
|
||||
lucide.ExternalLink(),
|
||||
component.SpanText("Update available"),
|
||||
html.Span(
|
||||
alpine.XShow("stars"),
|
||||
alpine.XText("'( ' + latestRelease + ' )'"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
32
internal/view/web/layout/dashboard_header_updates.go
Normal file
32
internal/view/web/layout/dashboard_header_updates.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
lucide "github.com/eduardolat/gomponents-lucide"
|
||||
"github.com/eduardolat/pgbackweb/internal/config"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
|
||||
"github.com/eduardolat/pgbackweb/internal/view/web/component"
|
||||
"github.com/maragudk/gomponents"
|
||||
"github.com/maragudk/gomponents/html"
|
||||
)
|
||||
|
||||
func dashboardHeaderUpdates() gomponents.Node {
|
||||
return html.A(
|
||||
alpine.XData("alpineDashboardHeaderUpdates()"),
|
||||
alpine.XCloak(),
|
||||
alpine.XShow(fmt.Sprintf(
|
||||
"latestRelease !== null && latestRelease !== '%s'",
|
||||
config.Version,
|
||||
)),
|
||||
|
||||
html.Class("btn btn-warning"),
|
||||
html.Href("https://github.com/eduardolat/pgbackweb/releases"),
|
||||
html.Target("_blank"),
|
||||
lucide.ExternalLink(),
|
||||
component.SpanText("Update available"),
|
||||
html.Span(
|
||||
alpine.XText("'( ' + latestRelease + ' )'"),
|
||||
),
|
||||
)
|
||||
}
|
||||
42
internal/view/web/layout/dashboard_header_updates.inc.js
Normal file
42
internal/view/web/layout/dashboard_header_updates.inc.js
Normal file
@@ -0,0 +1,42 @@
|
||||
window.alpineDashboardHeaderUpdates = function () {
|
||||
return {
|
||||
latestRelease: null,
|
||||
|
||||
async init () {
|
||||
const latestRelease = await this.getLatestRelease()
|
||||
if (latestRelease !== null) {
|
||||
this.latestRelease = latestRelease
|
||||
}
|
||||
},
|
||||
|
||||
async getLatestRelease () {
|
||||
const cacheKey = 'pbw-gh-last-release'
|
||||
|
||||
const cachedJSON = localStorage.getItem(cacheKey)
|
||||
if (cachedJSON) {
|
||||
const cached = JSON.parse(cachedJSON)
|
||||
if (Date.now() - cached.timestamp < 2 * 60 * 1000) {
|
||||
return cached.value
|
||||
}
|
||||
}
|
||||
|
||||
const url = 'https://api.github.com/repos/eduardolat/pgbackweb/releases/latest'
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
return null
|
||||
}
|
||||
const data = await response.json()
|
||||
const value = data.name
|
||||
const dataToCache = JSON.stringify({
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
localStorage.setItem(cacheKey, dataToCache)
|
||||
return value
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user