Add LogViewer to ui

This commit is contained in:
sfigato 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 { RadioToggler } from './features/radio/Radio';
import { LogViewer } from './features/radio/LogViewer';
import './App.css';
function App() {
@ -7,6 +8,7 @@ function App() {
<div className="App">
<header className="App-header">
<RadioToggler />
<LogViewer />
</header>
</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 {
toggleRadioState,
selectLoading,
selectRadioLoading,
selectRadio,
selectConnection,
} from './radioSlice';
@ -26,7 +26,7 @@ const getWSUrl = async () => {
};
export function RadioToggler() {
const loading = useAppSelector(selectLoading);
const loading = useAppSelector(selectRadioLoading);
const radioState = useAppSelector(selectRadio);
const connectionState = useAppSelector(selectConnection);

View File

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

View File

@ -1,17 +1,23 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../app/store';
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 {
radio?: RadioState,
loading: boolean,
logs?: Array<string>,
loading: LoadingState,
connection: ConnectionState
}
const initialState: AppState = {
radio: undefined,
loading: false,
loading: { radio: false, logs: false },
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({
name: 'radio',
initialState,
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 },
setRadioStopped: (state) => { console.log("[Redux] STOPPED"); state.radio = RadioState.STOPPED },
unsetRadio: (state) => { state.radio = undefined },
@ -59,32 +73,42 @@ export const radioSlice = createSlice({
extraReducers: builder => {
builder
.addCase(requestToStartThunk.pending, (state) => {
state.loading = true;
state.loading.radio = true;
return state;
})
.addCase(requestToStartThunk.fulfilled, (state, action) => {
state.loading = false;
state.loading.radio = false;
if (action.payload) {
state.radio = RadioState.STARTED;
}
return state;
})
.addCase(requestToStopThunk.pending, (state, _) => {
state.loading = true;
state.loading.radio = true;
return state;
})
.addCase(requestToStopThunk.fulfilled, (state, action) => {
state.loading = false;
state.loading.radio = false;
if (action.payload) {
state.radio = RadioState.STOPPED;
}
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 {
toggleLoading,
toggleRadioLoading,
toggleLogsLoading,
setRadioStarted,
setRadioStopped,
unsetRadio,
@ -94,8 +118,10 @@ export const {
toggleConnection,
} = 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 selectLogs = (state: RootState) => state.globalState.logs;
export const selectConnection = (state: RootState) => state.globalState.connection;
export const toggleRadioState =