Add LogViewer to ui

master
blallo 2023-03-13 23:41:23 +01:00
parent 8ad6721315
commit 79a2c8aa66
Signed by: blallo
GPG Key ID: C530464EEDCF489A
7 changed files with 150 additions and 20 deletions

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { RadioToggler } from './features/radio/Radio'; import { RadioToggler } from './features/radio/Radio';
import { LogViewer } from './features/radio/LogViewer';
import './App.css'; import './App.css';
function App() { function App() {
@ -7,6 +8,7 @@ function App() {
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">
<RadioToggler /> <RadioToggler />
<LogViewer />
</header> </header>
</div> </div>
); );

View File

@ -0,0 +1,44 @@
$button-color: #4CAF50;
$button-background: white;
$row-even-background: transparent;
$row-odd-background: rgba(138, 230, 141, 0.7);
.button {
transition-duration: 0.2s;
border: 2px solid $button-color;
border-radius: 4px;
padding: 10px 24px;
background-color: transparent;
}
.button:hover {
background-color: $button-color; /* Green */
color: $button-background;
}
.logsContainer {
width: 50%;
}
.buttonContainer {
display: flex;
justify-content: space-evenly;
}
.rowsContainer {
height: 500px;
overflow: auto;
}
.rows {
padding: 10px;
overflow-y: scroll;
.even {
background-color: $row-even-background;
}
.odd {
background-color: $row-odd-background;
}
}

View File

@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { useAppSelector, useAppDispatch } from '../../app/hooks';
import { selectLogsLoading, selectLogs, requestLogsThunk } from './radioSlice';
import styles from './LogViewer.module.scss';
export function LogViewer() {
const loading = useAppSelector(selectLogsLoading);
const logs = useAppSelector(selectLogs);
const [hidden, setHidden] = useState(true);
const dispatch = useAppDispatch();
return (
<div className={styles.logsContainer}>
<div className={styles.buttonContainer}>
<button
className={styles.button}
onClick={() => dispatch(requestLogsThunk())}
>Recupera stato</button>
<button
className={styles.button}
onClick={() => setHidden(!hidden)}
>{hidden ? 'Mostra' : 'Nascondi'}</button>
</div>
<div className={styles.rowsContainer}>
{!hidden &&
<div className={styles.rows}>
{loading ?
'Caricamento' :
logs?.map((line, i) => <div
className={(i % 2) ? styles.even : styles.odd}
>{line}</div>)}
</div>
}
</div>
</div >
);
}

View File

@ -5,7 +5,7 @@ import { useAppSelector, useAppDispatch } from '../../app/hooks';
import { RadioState, ConnectionState } from '../../app/types'; import { RadioState, ConnectionState } from '../../app/types';
import { import {
toggleRadioState, toggleRadioState,
selectLoading, selectRadioLoading,
selectRadio, selectRadio,
selectConnection, selectConnection,
} from './radioSlice'; } from './radioSlice';
@ -26,7 +26,7 @@ const getWSUrl = async () => {
}; };
export function RadioToggler() { export function RadioToggler() {
const loading = useAppSelector(selectLoading); const loading = useAppSelector(selectRadioLoading);
const radioState = useAppSelector(selectRadio); const radioState = useAppSelector(selectRadio);
const connectionState = useAppSelector(selectConnection); const connectionState = useAppSelector(selectConnection);

View File

@ -46,3 +46,23 @@ export const requestToStop = async () => {
return success; return success;
} }
export const requestLogs = async () => {
console.log("[REST] request logs");
try {
const logsUrl = window.location.href + 'status';
const resp = await fetch(logsUrl);
if (resp.ok) {
console.log("[REST] Logs: success");
const body = await resp.json();
if (Array.isArray(body['lines'])) {
return body['lines'];
}
}
} catch (err) {
console.log("[REST] Logs: failure: %s", err);
}
return undefined;
}

View File

@ -1,46 +1,46 @@
import { RadioState, ConnectionState } from '../../app/types'; import { RadioState, ConnectionState } from '../../app/types';
import reducer, { import reducer, {
AppState, AppState,
toggleLoading, toggleRadioLoading,
setRadioStarted, setRadioStarted,
setRadioStopped, setRadioStopped,
toggleRadio, toggleRadio,
setConnected, setConnected,
setDisconnected, setDisconnected,
toggleConnection, toggleConnection,
selectLoading, selectRadioLoading,
selectRadio, selectRadio,
selectConnection, selectConnection,
} from './radioSlice'; } from './radioSlice';
describe('radio reducer', () => { describe('radio reducer', () => {
const initialState: AppState = { const initialState: AppState = {
loading: false, loading: { radio: false, logs: false },
connection: ConnectionState.DISCONNECTED, connection: ConnectionState.DISCONNECTED,
}; };
const connectedStarted: AppState = { const connectedStarted: AppState = {
radio: RadioState.STARTED, radio: RadioState.STARTED,
loading: false, loading: { radio: false, logs: false },
connection: ConnectionState.CONNECTED, connection: ConnectionState.CONNECTED,
}; };
const connectedStopped: AppState = { const connectedStopped: AppState = {
radio: RadioState.STOPPED, radio: RadioState.STOPPED,
loading: false, loading: { radio: false, logs: false },
connection: ConnectionState.CONNECTED, connection: ConnectionState.CONNECTED,
}; };
it('should handle initial state', () => { it('should handle initial state', () => {
expect(reducer(undefined, { type: 'unknown' })).toEqual({ expect(reducer(undefined, { type: 'unknown' })).toEqual({
loading: false, loading: { radio: false, logs: false },
connection: ConnectionState.DISCONNECTED, connection: ConnectionState.DISCONNECTED,
}); });
}); });
it('should handle set loading', () => { it('should handle set loading', () => {
const actual = reducer(initialState, toggleLoading()); const actual = reducer(initialState, toggleRadioLoading());
expect(actual.loading).toEqual(true); expect(actual.loading.radio).toEqual(true);
}); });
it('should hande toggle radio started', () => { it('should hande toggle radio started', () => {

View File

@ -1,17 +1,23 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store'; import { RootState, AppThunk } from '../../app/store';
import { RadioState, ConnectionState } from '../../app/types'; import { RadioState, ConnectionState } from '../../app/types';
import { requestToStart, requestToStop } from './radioAPI'; import { requestToStart, requestToStop, requestLogs } from './radioAPI';
export interface LoadingState {
radio: boolean,
logs: boolean,
}
export interface AppState { export interface AppState {
radio?: RadioState, radio?: RadioState,
loading: boolean, logs?: Array<string>,
loading: LoadingState,
connection: ConnectionState connection: ConnectionState
} }
const initialState: AppState = { const initialState: AppState = {
radio: undefined, radio: undefined,
loading: false, loading: { radio: false, logs: false },
connection: ConnectionState.DISCONNECTED, connection: ConnectionState.DISCONNECTED,
}; };
@ -31,11 +37,19 @@ export const requestToStopThunk = createAsyncThunk(
} }
); );
export const requestLogsThunk = createAsyncThunk(
'radio/restStatus',
async () => {
return await requestLogs();
}
);
export const radioSlice = createSlice({ export const radioSlice = createSlice({
name: 'radio', name: 'radio',
initialState, initialState,
reducers: { reducers: {
toggleLoading: (state) => { state.loading = false ? state.loading : true }, toggleRadioLoading: (state) => { state.loading.radio = !state.loading.radio },
toggleLogsLoading: (state) => { state.loading.logs = !state.loading.logs },
setRadioStarted: (state) => { console.log("[Redux] STARTED"); state.radio = RadioState.STARTED }, setRadioStarted: (state) => { console.log("[Redux] STARTED"); state.radio = RadioState.STARTED },
setRadioStopped: (state) => { console.log("[Redux] STOPPED"); state.radio = RadioState.STOPPED }, setRadioStopped: (state) => { console.log("[Redux] STOPPED"); state.radio = RadioState.STOPPED },
unsetRadio: (state) => { state.radio = undefined }, unsetRadio: (state) => { state.radio = undefined },
@ -59,32 +73,42 @@ export const radioSlice = createSlice({
extraReducers: builder => { extraReducers: builder => {
builder builder
.addCase(requestToStartThunk.pending, (state) => { .addCase(requestToStartThunk.pending, (state) => {
state.loading = true; state.loading.radio = true;
return state; return state;
}) })
.addCase(requestToStartThunk.fulfilled, (state, action) => { .addCase(requestToStartThunk.fulfilled, (state, action) => {
state.loading = false; state.loading.radio = false;
if (action.payload) { if (action.payload) {
state.radio = RadioState.STARTED; state.radio = RadioState.STARTED;
} }
return state; return state;
}) })
.addCase(requestToStopThunk.pending, (state, _) => { .addCase(requestToStopThunk.pending, (state, _) => {
state.loading = true; state.loading.radio = true;
return state; return state;
}) })
.addCase(requestToStopThunk.fulfilled, (state, action) => { .addCase(requestToStopThunk.fulfilled, (state, action) => {
state.loading = false; state.loading.radio = false;
if (action.payload) { if (action.payload) {
state.radio = RadioState.STOPPED; state.radio = RadioState.STOPPED;
} }
return state; return state;
}) })
.addCase(requestLogsThunk.pending, (state, _) => {
state.loading.logs = true;
return state;
})
.addCase(requestLogsThunk.fulfilled, (state, action) => {
state.loading.logs = false;
state.logs = action.payload;
return state;
})
}, },
}); });
export const { export const {
toggleLoading, toggleRadioLoading,
toggleLogsLoading,
setRadioStarted, setRadioStarted,
setRadioStopped, setRadioStopped,
unsetRadio, unsetRadio,
@ -94,8 +118,10 @@ export const {
toggleConnection, toggleConnection,
} = radioSlice.actions; } = radioSlice.actions;
export const selectLoading = (state: RootState) => state.globalState.loading; export const selectRadioLoading = (state: RootState) => state.globalState.loading.radio;
export const selectLogsLoading = (state: RootState) => state.globalState.loading.logs;
export const selectRadio = (state: RootState) => state.globalState.radio; export const selectRadio = (state: RootState) => state.globalState.radio;
export const selectLogs = (state: RootState) => state.globalState.logs;
export const selectConnection = (state: RootState) => state.globalState.connection; export const selectConnection = (state: RootState) => state.globalState.connection;
export const toggleRadioState = export const toggleRadioState =