mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
feat: rework loggers and pass data to the UI
This commit is contained in:
parent
0a3b6eae4c
commit
bb8f3f754e
16 changed files with 310 additions and 78 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,4 +6,5 @@
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
strimertul_*
|
strimertul_*
|
||||||
build/bin
|
build/bin
|
||||||
|
*.log
|
27
app.go
27
app.go
|
@ -4,14 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/nicklaw5/helix/v2"
|
|
||||||
|
|
||||||
"github.com/strimertul/strimertul/modules"
|
"github.com/strimertul/strimertul/modules"
|
||||||
"github.com/strimertul/strimertul/modules/database"
|
"github.com/strimertul/strimertul/modules/database"
|
||||||
"github.com/strimertul/strimertul/modules/http"
|
"github.com/strimertul/strimertul/modules/http"
|
||||||
"github.com/strimertul/strimertul/modules/twitch"
|
"github.com/strimertul/strimertul/modules/twitch"
|
||||||
|
|
||||||
|
"git.sr.ht/~hamcha/containers"
|
||||||
|
"github.com/nicklaw5/helix/v2"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,12 +22,14 @@ type App struct {
|
||||||
cliParams *cli.Context
|
cliParams *cli.Context
|
||||||
driver DatabaseDriver
|
driver DatabaseDriver
|
||||||
manager *modules.Manager
|
manager *modules.Manager
|
||||||
|
ready *containers.RWSync[bool]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp creates a new App application struct
|
// NewApp creates a new App application struct
|
||||||
func NewApp(cliParams *cli.Context) *App {
|
func NewApp(cliParams *cli.Context) *App {
|
||||||
return &App{
|
return &App{
|
||||||
cliParams: cliParams,
|
cliParams: cliParams,
|
||||||
|
ready: containers.NewRWSync(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,8 +87,19 @@ func (a *App) startup(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
a.ready.Set(true)
|
||||||
|
runtime.EventsEmit(ctx, "ready", true)
|
||||||
|
logger.Info("app is ready")
|
||||||
|
|
||||||
|
// Start redirecting logs to UI
|
||||||
|
go func() {
|
||||||
|
for entry := range incomingLogs {
|
||||||
|
runtime.EventsEmit(ctx, "log-event", entry)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Run HTTP server
|
// Run HTTP server
|
||||||
go failOnError(httpServer.Listen(), "HTTP server stopped")
|
failOnError(httpServer.Listen(), "HTTP server stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) stop(context.Context) {
|
func (a *App) stop(context.Context) {
|
||||||
|
@ -101,6 +115,9 @@ func (a *App) AuthenticateKVClient(id string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) IsServerReady() bool {
|
func (a *App) IsServerReady() bool {
|
||||||
|
if !a.ready.Get() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return a.manager.Modules[modules.ModuleHTTP].Status().Working
|
return a.manager.Modules[modules.ModuleHTTP].Status().Working
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,3 +138,7 @@ func (a *App) GetTwitchAuthURL() string {
|
||||||
func (a *App) GetTwitchLoggedUser() (helix.User, error) {
|
func (a *App) GetTwitchLoggedUser() (helix.User, error) {
|
||||||
return a.manager.Modules[modules.ModuleTwitch].(*twitch.Client).GetLoggedUser()
|
return a.manager.Modules[modules.ModuleTwitch].(*twitch.Client).GetLoggedUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetLastLogs() []LogEntry {
|
||||||
|
return lastLogs
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"bind": "Webserver Bind",
|
"bind": "Webserver Bind",
|
||||||
"kilovolt-password": "Kilovolt password",
|
"kilovolt-password": "Kilovolt password",
|
||||||
"kilovolt-placeholder": "Leave empty to disable authentication",
|
"kilovolt-placeholder": "Leave empty to disable authentication",
|
||||||
"bind-help": "Every application that uses strimertul will need to be updated, your browser will be redirected to the new URL automatically.",
|
"bind-help": "Every application that uses {{APPNAME}} will need to be updated!",
|
||||||
"static-path": "Static assets (leave empty to disable)",
|
"static-path": "Static assets (leave empty to disable)",
|
||||||
"static-placeholder": "Absolute path to static assets",
|
"static-placeholder": "Absolute path to static assets",
|
||||||
"static-help": "Will be served at the following URL: {{url}}",
|
"static-help": "Will be served at the following URL: {{url}}",
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
},
|
},
|
||||||
"sim-events": "Send test event",
|
"sim-events": "Send test event",
|
||||||
"auth-button": "Authenticate with Twitch",
|
"auth-button": "Authenticate with Twitch",
|
||||||
"auth-message": "Click the following button to authenticate strimertul with your Twitch account:",
|
"auth-message": "Click the following button to authenticate {{APPNAME}} with your Twitch account:",
|
||||||
"current-status": "Current status"
|
"current-status": "Current status"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"title": "Authentication required",
|
"title": "Authentication required",
|
||||||
"desc": "The installation of strimertül you are trying to reach is protected with a password. Please write the password below to access the control panel.",
|
"desc": "The installation of {{APPNAME}} you are trying to reach is protected with a password. Please write the password below to access the control panel.",
|
||||||
"no-pwd-note": " If the database has no password (for example, it was recently changed from having one to none), leave the field empty.",
|
"no-pwd-note": " If the database has no password (for example, it was recently changed from having one to none), leave the field empty.",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"submit": "Authenticate"
|
"submit": "Authenticate"
|
||||||
|
@ -284,6 +284,7 @@
|
||||||
"wip": {
|
"wip": {
|
||||||
"header": "WIP - Page under development",
|
"header": "WIP - Page under development",
|
||||||
"text": "This page is still under construction, apologies for the lackluster view :("
|
"text": "This page is still under construction, apologies for the lackluster view :("
|
||||||
}
|
},
|
||||||
|
"loading": "{{APPNAME}} is starting up, please wait!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { configureStore } from '@reduxjs/toolkit';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import thunkMiddleware from 'redux-thunk';
|
import thunkMiddleware from 'redux-thunk';
|
||||||
import apiReducer from './api/reducer';
|
import apiReducer from './api/reducer';
|
||||||
|
import loggingReducer from './logging/reducer';
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
api: apiReducer.reducer,
|
api: apiReducer.reducer,
|
||||||
|
logging: loggingReducer.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
|
|
53
frontend/src/store/logging/reducer.ts
Normal file
53
frontend/src/store/logging/reducer.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { main } from '@wailsapp/go/models';
|
||||||
|
|
||||||
|
interface ProcessedLogEntry {
|
||||||
|
time: Date;
|
||||||
|
caller: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
data: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processEntry({
|
||||||
|
time,
|
||||||
|
caller,
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
data,
|
||||||
|
}: main.LogEntry): ProcessedLogEntry {
|
||||||
|
return {
|
||||||
|
time: new Date(time),
|
||||||
|
caller,
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
data: JSON.parse(data) as object,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoggingState {
|
||||||
|
messages: ProcessedLogEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: LoggingState = {
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const loggingReducer = createSlice({
|
||||||
|
name: 'logging',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
loadedLogData(state, { payload }: PayloadAction<main.LogEntry[]>) {
|
||||||
|
state.messages = payload.map(processEntry);
|
||||||
|
},
|
||||||
|
receivedEvent(state, { payload }: PayloadAction<main.LogEntry>) {
|
||||||
|
state.messages = [...state.messages, processEntry(payload)];
|
||||||
|
},
|
||||||
|
clearedEvents(state) {
|
||||||
|
state.messages = [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default loggingReducer;
|
|
@ -1,5 +1,3 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import {
|
import {
|
||||||
ChatBubbleIcon,
|
ChatBubbleIcon,
|
||||||
DashboardIcon,
|
DashboardIcon,
|
||||||
|
@ -10,34 +8,45 @@ import {
|
||||||
TableIcon,
|
TableIcon,
|
||||||
TimerIcon,
|
TimerIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
|
import { EventsOff, EventsOn } from '@wailsapp/runtime/runtime';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Route, Routes } from 'react-router-dom';
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import { GetKilovoltBind } from '@wailsapp/go/main/App';
|
|
||||||
|
|
||||||
import { delay } from 'src/lib/time-utils';
|
import {
|
||||||
import Dashboard from './pages/Dashboard';
|
GetKilovoltBind,
|
||||||
import Sidebar, { RouteSection } from './components/Sidebar';
|
GetLastLogs,
|
||||||
import ServerSettingsPage from './pages/ServerSettings';
|
IsServerReady,
|
||||||
|
} from '@wailsapp/go/main/App';
|
||||||
|
import { main } from '@wailsapp/go/models';
|
||||||
|
|
||||||
import { RootState, useAppDispatch } from '../store';
|
import { RootState, useAppDispatch } from '../store';
|
||||||
import { createWSClient, useAuthBypass } from '../store/api/reducer';
|
import { createWSClient, useAuthBypass } from '../store/api/reducer';
|
||||||
import { ConnectionStatus } from '../store/api/types';
|
import { ConnectionStatus } from '../store/api/types';
|
||||||
import { styled } from './theme';
|
import loggingReducer from '../store/logging/reducer';
|
||||||
|
import Sidebar, { RouteSection } from './components/Sidebar';
|
||||||
|
import AuthDialog from './pages/AuthDialog';
|
||||||
|
import TwitchBotCommandsPage from './pages/BotCommands';
|
||||||
|
import TwitchBotTimersPage from './pages/BotTimers';
|
||||||
|
import ChatAlertsPage from './pages/ChatAlerts';
|
||||||
|
import Dashboard from './pages/Dashboard';
|
||||||
|
import DebugPage from './pages/Debug';
|
||||||
|
import LoyaltyConfigPage from './pages/LoyaltyConfig';
|
||||||
|
import LoyaltyQueuePage from './pages/LoyaltyQueue';
|
||||||
|
import LoyaltyRewardsPage from './pages/LoyaltyRewards';
|
||||||
|
import ServerSettingsPage from './pages/ServerSettings';
|
||||||
|
import StrimertulPage from './pages/Strimertul';
|
||||||
|
import TwitchSettingsPage from './pages/TwitchSettings';
|
||||||
|
import { APPNAME, styled } from './theme';
|
||||||
|
|
||||||
// @ts-expect-error Asset import
|
// @ts-expect-error Asset import
|
||||||
import spinner from '../assets/icon-loading.svg';
|
import spinner from '../assets/icon-loading.svg';
|
||||||
import TwitchSettingsPage from './pages/TwitchSettings';
|
|
||||||
import TwitchBotCommandsPage from './pages/BotCommands';
|
|
||||||
import TwitchBotTimersPage from './pages/BotTimers';
|
|
||||||
import AuthDialog from './pages/AuthDialog';
|
|
||||||
import ChatAlertsPage from './pages/ChatAlerts';
|
|
||||||
import LoyaltyConfigPage from './pages/LoyaltyConfig';
|
|
||||||
import LoyaltyQueuePage from './pages/LoyaltyQueue';
|
|
||||||
import StrimertulPage from './pages/Strimertul';
|
|
||||||
import DebugPage from './pages/Debug';
|
|
||||||
import LoyaltyRewardsPage from './pages/LoyaltyRewards';
|
|
||||||
|
|
||||||
const LoadingDiv = styled('div', {
|
const LoadingDiv = styled('div', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
|
@ -47,10 +56,15 @@ const Spinner = styled('img', {
|
||||||
maxWidth: '100px',
|
maxWidth: '100px',
|
||||||
});
|
});
|
||||||
|
|
||||||
function Loading() {
|
interface LoadingProps {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Loading({ message }: React.PropsWithChildren<LoadingProps>) {
|
||||||
return (
|
return (
|
||||||
<LoadingDiv>
|
<LoadingDiv>
|
||||||
<Spinner src={spinner as string} alt="Loading..." />
|
<Spinner src={spinner as string} alt="Loading..." />
|
||||||
|
<p>{message}</p>
|
||||||
</LoadingDiv>
|
</LoadingDiv>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -143,6 +157,7 @@ const PageWrapper = styled('div', {
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
const client = useSelector((state: RootState) => state.api.client);
|
const client = useSelector((state: RootState) => state.api.client);
|
||||||
const connected = useSelector(
|
const connected = useSelector(
|
||||||
(state: RootState) => state.api.connectionStatus,
|
(state: RootState) => state.api.connectionStatus,
|
||||||
|
@ -150,16 +165,7 @@ export default function App(): JSX.Element {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const connectToKV = async () => {
|
const connectToKV = async () => {
|
||||||
let address = '';
|
const address = await GetKilovoltBind();
|
||||||
while (address === '') {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
address = await GetKilovoltBind();
|
|
||||||
if (address === '') {
|
|
||||||
// Server not ready yet, wait a second
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await delay(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await dispatch(
|
await dispatch(
|
||||||
createWSClient({
|
createWSClient({
|
||||||
address: `ws://${address}/ws`,
|
address: `ws://${address}/ws`,
|
||||||
|
@ -167,17 +173,43 @@ export default function App(): JSX.Element {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get application logs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
void GetLastLogs().then((logs) => {
|
||||||
|
dispatch(loggingReducer.actions.loadedLogData(logs));
|
||||||
|
});
|
||||||
|
EventsOn('log-event', (event: main.LogEntry) => {
|
||||||
|
dispatch(loggingReducer.actions.receivedEvent(event));
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
EventsOff('log-event');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void IsServerReady().then(setReady);
|
||||||
|
EventsOn('ready', (newValue: boolean) => {
|
||||||
|
setReady(newValue);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
EventsOff('ready');
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!client) {
|
if (!client) {
|
||||||
void connectToKV();
|
void connectToKV();
|
||||||
}
|
}
|
||||||
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
||||||
void dispatch(useAuthBypass());
|
void dispatch(useAuthBypass());
|
||||||
}
|
}
|
||||||
});
|
}, [ready, connected]);
|
||||||
|
|
||||||
if (connected === ConnectionStatus.NotConnected) {
|
if (connected === ConnectionStatus.NotConnected) {
|
||||||
return <Loading />;
|
return <Loading message={t('special.loading', { APPNAME })} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
if (connected === ConnectionStatus.AuthenticationNeeded) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { styled } from '@stitches/react';
|
import { styled } from '@stitches/react';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, InputBox, TextBlock } from '../theme';
|
import { APPNAME, Button, InputBox, TextBlock } from '../theme';
|
||||||
|
|
||||||
const AuthWrapper = styled('div', {
|
const AuthWrapper = styled('div', {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -59,7 +59,9 @@ export default function AuthDialog(): React.ReactElement {
|
||||||
>
|
>
|
||||||
<AuthTitle>{t('pages.auth.title')}</AuthTitle>
|
<AuthTitle>{t('pages.auth.title')}</AuthTitle>
|
||||||
<Content>
|
<Content>
|
||||||
<TextBlock spacing="none">{t('pages.auth.desc')}</TextBlock>
|
<TextBlock spacing="none">
|
||||||
|
{t('pages.auth.desc', { APPNAME })}
|
||||||
|
</TextBlock>
|
||||||
<TextBlock spacing="none">{t('pages.auth.no-pwd-note')}</TextBlock>
|
<TextBlock spacing="none">{t('pages.auth.no-pwd-note')}</TextBlock>
|
||||||
</Content>
|
</Content>
|
||||||
<Actions>
|
<Actions>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { useAppDispatch } from '../../store';
|
||||||
import apiReducer, { modules } from '../../store/api/reducer';
|
import apiReducer, { modules } from '../../store/api/reducer';
|
||||||
import SaveButton from '../components/utils/SaveButton';
|
import SaveButton from '../components/utils/SaveButton';
|
||||||
import {
|
import {
|
||||||
|
APPNAME,
|
||||||
Field,
|
Field,
|
||||||
FieldNote,
|
FieldNote,
|
||||||
InputBox,
|
InputBox,
|
||||||
|
@ -53,7 +54,7 @@ export default function ServerSettingsPage(): React.ReactElement {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<FieldNote>{t('pages.http.bind-help')}</FieldNote>
|
<FieldNote>{t('pages.http.bind-help', { APPNAME })}</FieldNote>
|
||||||
</Field>
|
</Field>
|
||||||
<Field size="fullWidth">
|
<Field size="fullWidth">
|
||||||
<Label htmlFor="kvpassword">
|
<Label htmlFor="kvpassword">
|
||||||
|
|
|
@ -13,6 +13,7 @@ import BrowserLink from '../components/BrowserLink';
|
||||||
import DefinitionTable from '../components/DefinitionTable';
|
import DefinitionTable from '../components/DefinitionTable';
|
||||||
import SaveButton from '../components/utils/SaveButton';
|
import SaveButton from '../components/utils/SaveButton';
|
||||||
import {
|
import {
|
||||||
|
APPNAME,
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
@ -304,7 +305,6 @@ const TwitchPic = styled('img', {
|
||||||
});
|
});
|
||||||
const TwitchName = styled('p', { fontWeight: 'bold' });
|
const TwitchName = styled('p', { fontWeight: 'bold' });
|
||||||
|
|
||||||
|
|
||||||
function TwitchEventSubSettings() {
|
function TwitchEventSubSettings() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [userStatus, setUserStatus] = useState<helix.User | SyncError>(null);
|
const [userStatus, setUserStatus] = useState<helix.User | SyncError>(null);
|
||||||
|
@ -337,8 +337,8 @@ function TwitchEventSubSettings() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get user info
|
// Get user info
|
||||||
void getUserInfo();
|
void getUserInfo();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let userBlock = <i>{t('pages.twitch-settings.events.loading-data')}</i>;
|
let userBlock = <i>{t('pages.twitch-settings.events.loading-data')}</i>;
|
||||||
|
@ -362,7 +362,7 @@ function TwitchEventSubSettings() {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>{t('pages.twitch-settings.events.auth-message')}</p>
|
<p>{t('pages.twitch-settings.events.auth-message', { APPNAME })}</p>
|
||||||
<Button
|
<Button
|
||||||
variation="primary"
|
variation="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -371,9 +371,13 @@ function TwitchEventSubSettings() {
|
||||||
>
|
>
|
||||||
<ExternalLinkIcon /> {t('pages.twitch-settings.events.auth-button')}
|
<ExternalLinkIcon /> {t('pages.twitch-settings.events.auth-button')}
|
||||||
</Button>
|
</Button>
|
||||||
<SectionHeader>{t('pages.twitch-settings.events.current-status')}</SectionHeader>
|
<SectionHeader>
|
||||||
|
{t('pages.twitch-settings.events.current-status')}
|
||||||
|
</SectionHeader>
|
||||||
{userBlock}
|
{userBlock}
|
||||||
<SectionHeader>{t('pages.twitch-settings.events.sim-events')}</SectionHeader>
|
<SectionHeader>
|
||||||
|
{t('pages.twitch-settings.events.sim-events')}
|
||||||
|
</SectionHeader>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => (
|
{Object.keys(eventsubTests).map((ev: keyof typeof eventsubTests) => (
|
||||||
<Button
|
<Button
|
||||||
|
@ -390,7 +394,6 @@ function TwitchEventSubSettings() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default function TwitchSettingsPage(): React.ReactElement {
|
export default function TwitchSettingsPage(): React.ReactElement {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);
|
const [twitchConfig, setTwitchConfig] = useModule(modules.twitchConfig);
|
||||||
|
|
3
frontend/wailsjs/go/main/App.d.ts
vendored
3
frontend/wailsjs/go/main/App.d.ts
vendored
|
@ -1,11 +1,14 @@
|
||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
import {main} from '../models';
|
||||||
import {helix} from '../models';
|
import {helix} from '../models';
|
||||||
|
|
||||||
export function AuthenticateKVClient(arg1:string):Promise<void>;
|
export function AuthenticateKVClient(arg1:string):Promise<void>;
|
||||||
|
|
||||||
export function GetKilovoltBind():Promise<string>;
|
export function GetKilovoltBind():Promise<string>;
|
||||||
|
|
||||||
|
export function GetLastLogs():Promise<Array<main.LogEntry>>;
|
||||||
|
|
||||||
export function GetTwitchAuthURL():Promise<string>;
|
export function GetTwitchAuthURL():Promise<string>;
|
||||||
|
|
||||||
export function GetTwitchLoggedUser():Promise<helix.User>;
|
export function GetTwitchLoggedUser():Promise<helix.User>;
|
||||||
|
|
|
@ -10,6 +10,10 @@ export function GetKilovoltBind() {
|
||||||
return window['go']['main']['App']['GetKilovoltBind']();
|
return window['go']['main']['App']['GetKilovoltBind']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetLastLogs() {
|
||||||
|
return window['go']['main']['App']['GetLastLogs']();
|
||||||
|
}
|
||||||
|
|
||||||
export function GetTwitchAuthURL() {
|
export function GetTwitchAuthURL() {
|
||||||
return window['go']['main']['App']['GetTwitchAuthURL']();
|
return window['go']['main']['App']['GetTwitchAuthURL']();
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,3 +54,28 @@ export namespace helix {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace main {
|
||||||
|
|
||||||
|
export class LogEntry {
|
||||||
|
caller: string;
|
||||||
|
time: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
data: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new LogEntry(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.caller = source["caller"];
|
||||||
|
this.time = source["time"];
|
||||||
|
this.level = source["level"];
|
||||||
|
this.message = source["message"];
|
||||||
|
this.data = source["data"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
||||||
github.com/urfave/cli/v2 v2.23.5
|
github.com/urfave/cli/v2 v2.23.5
|
||||||
github.com/wailsapp/wails/v2 v2.2.0
|
github.com/wailsapp/wails/v2 v2.2.0
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.23.0
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/nicklaw5/helix/v2 => github.com/ashkeel/helix/v2 v2.11.0-ws
|
replace github.com/nicklaw5/helix/v2 => github.com/ashkeel/helix/v2 v2.11.0-ws
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -35,6 +35,7 @@ git.sr.ht/~hamcha/containers v0.0.3 h1:obG9X8s5iOIahVe+EGpkBDYmUAO78oTi9Y9gRurt3
|
||||||
git.sr.ht/~hamcha/containers v0.0.3/go.mod h1:RiZphUpy9t6EnL4Gf6uzByM9QrBoqRCEPo7kz2wzbhE=
|
git.sr.ht/~hamcha/containers v0.0.3/go.mod h1:RiZphUpy9t6EnL4Gf6uzByM9QrBoqRCEPo7kz2wzbhE=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
|
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||||
|
@ -831,6 +832,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
104
logging.go
Normal file
104
logging.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *zap.Logger
|
||||||
|
|
||||||
|
const LogHistory = 50
|
||||||
|
|
||||||
|
var (
|
||||||
|
lastLogs []LogEntry
|
||||||
|
incomingLogs chan LogEntry
|
||||||
|
)
|
||||||
|
|
||||||
|
func initLogger() {
|
||||||
|
lastLogs = make([]LogEntry, 0)
|
||||||
|
incomingLogs = make(chan LogEntry, 100)
|
||||||
|
logStorage := NewLogStorage(zap.InfoLevel)
|
||||||
|
consoleLogger := zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
|
||||||
|
zapcore.Lock(os.Stderr),
|
||||||
|
zap.InfoLevel,
|
||||||
|
)
|
||||||
|
fileLogger := zapcore.NewCore(
|
||||||
|
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
zapcore.AddSync(&lumberjack.Logger{
|
||||||
|
Filename: "strimertul.log",
|
||||||
|
MaxSize: 500,
|
||||||
|
MaxBackups: 3,
|
||||||
|
MaxAge: 28,
|
||||||
|
}),
|
||||||
|
zap.DebugLevel,
|
||||||
|
)
|
||||||
|
core := zapcore.NewTee(
|
||||||
|
consoleLogger,
|
||||||
|
fileLogger,
|
||||||
|
logStorage,
|
||||||
|
)
|
||||||
|
logger = zap.New(core, zap.AddCaller())
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogEntry struct {
|
||||||
|
Caller string `json:"caller"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogStorage struct {
|
||||||
|
zapcore.LevelEnabler
|
||||||
|
fields []zapcore.Field
|
||||||
|
encoder zapcore.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogStorage(enabler zapcore.LevelEnabler) *LogStorage {
|
||||||
|
return &LogStorage{
|
||||||
|
LevelEnabler: enabler,
|
||||||
|
encoder: zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (core *LogStorage) With(fields []zapcore.Field) zapcore.Core {
|
||||||
|
clone := *core
|
||||||
|
clone.fields = append(clone.fields, fields...)
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (core *LogStorage) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||||
|
if core.Enabled(entry.Level) {
|
||||||
|
return checked.AddCore(entry, core)
|
||||||
|
}
|
||||||
|
return checked
|
||||||
|
}
|
||||||
|
|
||||||
|
func (core *LogStorage) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||||
|
buf, err := core.encoder.EncodeEntry(entry, append(core.fields, fields...))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logEntry := LogEntry{
|
||||||
|
Caller: entry.Caller.String(),
|
||||||
|
Time: entry.Time.Format(time.RFC3339),
|
||||||
|
Level: entry.Level.String(),
|
||||||
|
Message: entry.Message,
|
||||||
|
Data: buf.String(),
|
||||||
|
}
|
||||||
|
lastLogs = append(lastLogs, logEntry)
|
||||||
|
if len(lastLogs) > LogHistory {
|
||||||
|
lastLogs = lastLogs[1:]
|
||||||
|
}
|
||||||
|
incomingLogs <- logEntry
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (core *LogStorage) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
34
main.go
34
main.go
|
@ -8,18 +8,16 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
"github.com/strimertul/strimertul/modules"
|
||||||
|
"github.com/strimertul/strimertul/modules/loyalty"
|
||||||
|
"github.com/strimertul/strimertul/modules/twitch"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/wailsapp/wails/v2"
|
"github.com/wailsapp/wails/v2"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
|
|
||||||
"github.com/strimertul/strimertul/modules"
|
|
||||||
"github.com/strimertul/strimertul/modules/loyalty"
|
|
||||||
"github.com/strimertul/strimertul/modules/twitch"
|
|
||||||
|
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
)
|
)
|
||||||
|
@ -30,8 +28,6 @@ const databaseDefaultDriver = "pebble"
|
||||||
|
|
||||||
var appVersion = "v0.0.0-UNKNOWN"
|
var appVersion = "v0.0.0-UNKNOWN"
|
||||||
|
|
||||||
var logger *zap.Logger
|
|
||||||
|
|
||||||
//go:embed frontend/dist/*
|
//go:embed frontend/dist/*
|
||||||
var frontend embed.FS
|
var frontend embed.FS
|
||||||
|
|
||||||
|
@ -49,8 +45,6 @@ func main() {
|
||||||
Version: appVersion,
|
Version: appVersion,
|
||||||
Action: cliMain,
|
Action: cliMain,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, Usage: "print more logs (for debugging)", Value: false},
|
|
||||||
&cli.BoolFlag{Name: "json-log", Usage: "print logs in JSON format", Value: false},
|
|
||||||
&cli.StringFlag{Name: "driver", Usage: "specify database driver", Value: "auto"},
|
&cli.StringFlag{Name: "driver", Usage: "specify database driver", Value: "auto"},
|
||||||
&cli.StringFlag{Name: "database-dir", Aliases: []string{"db-dir"}, Usage: "specify database directory", Value: "data"},
|
&cli.StringFlag{Name: "database-dir", Aliases: []string{"db-dir"}, Usage: "specify database directory", Value: "data"},
|
||||||
&cli.StringFlag{Name: "backup-dir", Aliases: []string{"b-dir"}, Usage: "specify backup directory", Value: "backups"},
|
&cli.StringFlag{Name: "backup-dir", Aliases: []string{"b-dir"}, Usage: "specify backup directory", Value: "backups"},
|
||||||
|
@ -91,7 +85,7 @@ func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
// Initialize logger with global flags
|
// Initialize logger with global flags
|
||||||
initLogger(ctx.Bool("debug"), ctx.Bool("json-log"))
|
initLogger()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
After: func(ctx *cli.Context) error {
|
After: func(ctx *cli.Context) error {
|
||||||
|
@ -106,24 +100,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initLogger(debug bool, json bool) {
|
|
||||||
if debug {
|
|
||||||
cfg := zap.NewDevelopmentConfig()
|
|
||||||
if json {
|
|
||||||
cfg.Encoding = "json"
|
|
||||||
}
|
|
||||||
logger, _ = cfg.Build()
|
|
||||||
} else {
|
|
||||||
cfg := zap.NewProductionConfig()
|
|
||||||
if !json {
|
|
||||||
cfg.Encoding = "console"
|
|
||||||
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
||||||
cfg.EncoderConfig.CallerKey = zapcore.OmitKey
|
|
||||||
}
|
|
||||||
logger, _ = cfg.Build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cliMain(ctx *cli.Context) error {
|
func cliMain(ctx *cli.Context) error {
|
||||||
// Create an instance of the app structure
|
// Create an instance of the app structure
|
||||||
app := NewApp(ctx)
|
app := NewApp(ctx)
|
||||||
|
|
Loading…
Reference in a new issue