From 79a2c8aa665882c2ff19f0b3dfde4d0beb17430c Mon Sep 17 00:00:00 2001 From: Blallo Date: Mon, 13 Mar 2023 23:41:23 +0100 Subject: [PATCH] Add LogViewer to ui --- ui/src/App.tsx | 2 + ui/src/features/radio/LogViewer.module.scss | 44 ++++++++++++++++++++ ui/src/features/radio/LogViewer.tsx | 38 +++++++++++++++++ ui/src/features/radio/Radio.tsx | 4 +- ui/src/features/radio/radioAPI.ts | 20 +++++++++ ui/src/features/radio/radioSlice.spec.ts | 16 +++---- ui/src/features/radio/radioSlice.ts | 46 ++++++++++++++++----- 7 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 ui/src/features/radio/LogViewer.module.scss create mode 100644 ui/src/features/radio/LogViewer.tsx diff --git a/ui/src/App.tsx b/ui/src/App.tsx index fcd8b00..b1f25f2 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -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() {
+
); diff --git a/ui/src/features/radio/LogViewer.module.scss b/ui/src/features/radio/LogViewer.module.scss new file mode 100644 index 0000000..b5f502a --- /dev/null +++ b/ui/src/features/radio/LogViewer.module.scss @@ -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; + } +} diff --git a/ui/src/features/radio/LogViewer.tsx b/ui/src/features/radio/LogViewer.tsx new file mode 100644 index 0000000..36c6138 --- /dev/null +++ b/ui/src/features/radio/LogViewer.tsx @@ -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 ( +
+
+ + +
+
+ {!hidden && +
+ {loading ? + 'Caricamento' : + logs?.map((line, i) =>
{line}
)} +
+ } +
+
+ ); +} diff --git a/ui/src/features/radio/Radio.tsx b/ui/src/features/radio/Radio.tsx index a68a78e..62e710f 100644 --- a/ui/src/features/radio/Radio.tsx +++ b/ui/src/features/radio/Radio.tsx @@ -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); diff --git a/ui/src/features/radio/radioAPI.ts b/ui/src/features/radio/radioAPI.ts index e2b2efb..e2007b3 100644 --- a/ui/src/features/radio/radioAPI.ts +++ b/ui/src/features/radio/radioAPI.ts @@ -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; +} diff --git a/ui/src/features/radio/radioSlice.spec.ts b/ui/src/features/radio/radioSlice.spec.ts index 810c354..d59cc1b 100644 --- a/ui/src/features/radio/radioSlice.spec.ts +++ b/ui/src/features/radio/radioSlice.spec.ts @@ -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', () => { diff --git a/ui/src/features/radio/radioSlice.ts b/ui/src/features/radio/radioSlice.ts index 05c942c..cd0e1f3 100644 --- a/ui/src/features/radio/radioSlice.ts +++ b/ui/src/features/radio/radioSlice.ts @@ -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, + 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 =