mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
parent
2dce3076ee
commit
d931690802
13 changed files with 155 additions and 25 deletions
14
app.go
14
app.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
"github.com/strimertul/strimertul/docs"
|
||||
|
@ -143,3 +144,16 @@ func (a *App) GetLastLogs() []LogEntry {
|
|||
func (a *App) GetDocumentation() map[string]docs.KeyObject {
|
||||
return docs.Keys
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
Release string `json:"release"`
|
||||
BuildInfo *debug.BuildInfo `json:"build"`
|
||||
}
|
||||
|
||||
func (a *App) GetAppVersion() VersionInfo {
|
||||
info, _ := debug.ReadBuildInfo()
|
||||
return VersionInfo{
|
||||
Release: appVersion,
|
||||
BuildInfo: info,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ export const blankTemplate = (slug: string) => `// ==Extension==
|
|||
// @version 1.0
|
||||
// @author Put your name here!
|
||||
// @description A new extension for strimertul
|
||||
// @apiversion 3.1.0
|
||||
// ==/Extension==
|
||||
`;
|
||||
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
// ==/Extension==
|
||||
|
||||
interface ExtensionMetadata {
|
||||
name: string;
|
||||
version: string;
|
||||
author: string;
|
||||
description: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
apiversion: string;
|
||||
}
|
||||
|
||||
export function parseExtensionMetadata(
|
||||
|
@ -40,6 +41,7 @@ export function parseExtensionMetadata(
|
|||
version: metadata.version,
|
||||
author: metadata.author,
|
||||
description: metadata.description,
|
||||
apiversion: metadata.apiversion ?? '3.1.0',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -332,7 +332,9 @@
|
|||
"error": "Error encountered",
|
||||
"terminated": "Stopped"
|
||||
},
|
||||
"error-alert": "Error details for {{name}}"
|
||||
"error-alert": "Error details for {{name}}",
|
||||
"incompatible-body": "This extension requires {{APPNAME}} version {{version}} and up, you are currently running {{appversion}}, which may be too old and miss required features",
|
||||
"incompatible-warning": "This extension is not compatible"
|
||||
}
|
||||
},
|
||||
"form-actions": {
|
||||
|
|
|
@ -5,12 +5,14 @@ import thunkMiddleware from 'redux-thunk';
|
|||
import apiReducer from './api/reducer';
|
||||
import loggingReducer from './logging/reducer';
|
||||
import extensionsReducer from './extensions/reducer';
|
||||
import serverReducer from './server/reducer';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
api: apiReducer.reducer,
|
||||
logging: loggingReducer.reducer,
|
||||
extensions: extensionsReducer.reducer,
|
||||
server: serverReducer.reducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
|
|
31
frontend/src/store/server/reducer.ts
Normal file
31
frontend/src/store/server/reducer.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { GetAppVersion } from '@wailsapp/go/main/App';
|
||||
import { main } from '@wailsapp/go/models';
|
||||
|
||||
interface ServerState {
|
||||
version: main.VersionInfo;
|
||||
}
|
||||
|
||||
const initialState: ServerState = {
|
||||
version: null,
|
||||
};
|
||||
|
||||
const serverReducer = createSlice({
|
||||
name: 'server',
|
||||
initialState,
|
||||
reducers: {
|
||||
loadedVersionData(state, { payload }: PayloadAction<main.VersionInfo>) {
|
||||
state.version = payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const initializeServerInfo = createAsyncThunk(
|
||||
'server/init-info',
|
||||
async (_: void, { dispatch }) => {
|
||||
dispatch(serverReducer.actions.loadedVersionData(await GetAppVersion()));
|
||||
},
|
||||
);
|
||||
|
||||
export default serverReducer;
|
|
@ -26,6 +26,7 @@ import { createWSClient, useAuthBypass } from '~/store/api/reducer';
|
|||
import { ConnectionStatus } from '~/store/api/types';
|
||||
import loggingReducer from '~/store/logging/reducer';
|
||||
import { initializeExtensions } from '~/store/extensions/reducer';
|
||||
import { initializeServerInfo } from '~/store/server/reducer';
|
||||
|
||||
import LogViewer from './components/LogViewer';
|
||||
import Sidebar, { RouteSection } from './components/Sidebar';
|
||||
|
@ -165,6 +166,11 @@ export default function App(): JSX.Element {
|
|||
);
|
||||
};
|
||||
|
||||
// Fill application info
|
||||
useEffect(() => {
|
||||
void dispatch(initializeServerInfo());
|
||||
});
|
||||
|
||||
// Get application logs
|
||||
useEffect(() => {
|
||||
void GetLastLogs().then((logs) => {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import { styled } from '@stitches/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link, useMatch, useResolvedPath } from 'react-router-dom';
|
||||
|
||||
// @ts-expect-error Asset import
|
||||
import logo from '~/assets/icon-logo.svg';
|
||||
|
||||
import { RootState } from '~/store';
|
||||
import { useAppSelector } from '~/store';
|
||||
import { APPNAME, APPREPO } from '../theme';
|
||||
import BrowserLink from './BrowserLink';
|
||||
import Scrollbar from './utils/Scrollbar';
|
||||
|
@ -160,18 +159,12 @@ export default function Sidebar({
|
|||
const { t } = useTranslation();
|
||||
const resolved = useResolvedPath('/about');
|
||||
const matchApp = useMatch({ path: resolved.pathname, end: true });
|
||||
const client = useSelector((state: RootState) => state.api.client);
|
||||
const [version, setVersion] = useState<string>(null);
|
||||
const version = useAppSelector((state) => state.server.version?.release);
|
||||
const [lastVersion, setLastVersion] = useState<{ url: string; name: string }>(
|
||||
null,
|
||||
);
|
||||
const dev = version && version.startsWith('v0.0.0');
|
||||
|
||||
async function fetchVersion() {
|
||||
const versionString = await client.getKey('stul-meta/version');
|
||||
setVersion(versionString);
|
||||
}
|
||||
|
||||
async function fetchLastVersion() {
|
||||
try {
|
||||
const req = await fetch(
|
||||
|
@ -197,12 +190,6 @@ export default function Sidebar({
|
|||
void fetchLastVersion();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
void fetchVersion();
|
||||
}
|
||||
}, [client]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Scrollbar vertical={true} viewport={{ maxHeight: '100vh' }}>
|
||||
|
@ -219,11 +206,14 @@ export default function Sidebar({
|
|||
{version && !dev ? version : t('debug.dev-build')}
|
||||
</VersionLabel>
|
||||
</AppLink>
|
||||
{!dev && lastVersion && !version.startsWith(lastVersion.name) && (
|
||||
<UpdateButton href={lastVersion.url}>
|
||||
{t('menu.messages.update-available')}
|
||||
</UpdateButton>
|
||||
)}
|
||||
{!dev &&
|
||||
version &&
|
||||
lastVersion &&
|
||||
!version.startsWith(lastVersion.name) && (
|
||||
<UpdateButton href={lastVersion.url}>
|
||||
{t('menu.messages.update-available')}
|
||||
</UpdateButton>
|
||||
)}
|
||||
</Header>
|
||||
{sections.map(({ title: sectionTitle, links }) => (
|
||||
<MenuSection key={sectionTitle}>
|
||||
|
|
|
@ -145,6 +145,11 @@ type ExtensionListItemProps = {
|
|||
function ExtensionListItem(props: ExtensionListItemProps) {
|
||||
const { t } = useTranslation();
|
||||
const metadata = parseExtensionMetadata(props.entry.source);
|
||||
const version = useAppSelector((state) => state.server.version?.release);
|
||||
const isDev = version && version.startsWith('v0.0.0');
|
||||
const showIncompatibleWarning =
|
||||
!isDev && version && version < `v${metadata.apiversion}`;
|
||||
|
||||
return (
|
||||
<ExtensionRow
|
||||
status={props.enabled && isRunning(props.status) ? 'enabled' : 'disabled'}
|
||||
|
@ -180,6 +185,24 @@ function ExtensionListItem(props: ExtensionListItemProps) {
|
|||
<ExtensionStatusNote color={colorByStatus(props.status)}>
|
||||
{t(`pages.extensions.statuses.${props.status}`)}
|
||||
</ExtensionStatusNote>
|
||||
{showIncompatibleWarning ? (
|
||||
<Alert>
|
||||
<AlertTrigger asChild>
|
||||
<Button variation="warning" size="small">
|
||||
<ExclamationTriangleIcon />
|
||||
</Button>
|
||||
</AlertTrigger>
|
||||
<AlertContent
|
||||
title={t('pages.extensions.incompatible-warning')}
|
||||
showCancel={false}
|
||||
>
|
||||
{t('pages.extensions.incompatible-body', {
|
||||
version: metadata.apiversion,
|
||||
appversion: version,
|
||||
})}
|
||||
</AlertContent>
|
||||
</Alert>
|
||||
) : null}
|
||||
{props.error ? (
|
||||
<Alert>
|
||||
<AlertTrigger asChild>
|
||||
|
|
|
@ -231,6 +231,19 @@ const button = {
|
|||
},
|
||||
},
|
||||
},
|
||||
warning: {
|
||||
border: '1px solid $yellow6',
|
||||
backgroundColor: '$yellow4',
|
||||
'&:not(:disabled)': {
|
||||
'&:hover': {
|
||||
backgroundColor: '$yellow5',
|
||||
borderColor: '$yellow8',
|
||||
},
|
||||
'&:active': {
|
||||
background: '$yellow6',
|
||||
},
|
||||
},
|
||||
},
|
||||
danger: {
|
||||
border: '1px solid $red6',
|
||||
backgroundColor: '$red4',
|
||||
|
|
5
frontend/wailsjs/go/main/App.d.ts
vendored
5
frontend/wailsjs/go/main/App.d.ts
vendored
|
@ -1,10 +1,15 @@
|
|||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {main} from '../models';
|
||||
import {map[string]docs} from '../models';
|
||||
import {helix} from '../models';
|
||||
|
||||
export function AuthenticateKVClient(arg1:string):Promise<void>;
|
||||
|
||||
export function GetAppVersion():Promise<main.VersionInfo>;
|
||||
|
||||
export function GetDocumentation():Promise<map[string]docs.KeyObject>;
|
||||
|
||||
export function GetKilovoltBind():Promise<string>;
|
||||
|
||||
export function GetLastLogs():Promise<Array<main.LogEntry>>;
|
||||
|
|
|
@ -6,6 +6,14 @@ export function AuthenticateKVClient(arg1) {
|
|||
return window['go']['main']['App']['AuthenticateKVClient'](arg1);
|
||||
}
|
||||
|
||||
export function GetAppVersion() {
|
||||
return window['go']['main']['App']['GetAppVersion']();
|
||||
}
|
||||
|
||||
export function GetDocumentation() {
|
||||
return window['go']['main']['App']['GetDocumentation']();
|
||||
}
|
||||
|
||||
export function GetKilovoltBind() {
|
||||
return window['go']['main']['App']['GetKilovoltBind']();
|
||||
}
|
||||
|
|
|
@ -76,6 +76,39 @@ export namespace main {
|
|||
this.data = source["data"];
|
||||
}
|
||||
}
|
||||
export class VersionInfo {
|
||||
release: string;
|
||||
// Go type: debug.BuildInfo
|
||||
build?: any;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new VersionInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.release = source["release"];
|
||||
this.build = this.convertValues(source["build"], null);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue