Add LogViewer to ui
This commit is contained in:
parent
8ad6721315
commit
79a2c8aa66
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
44
ui/src/features/radio/LogViewer.module.scss
Normal file
44
ui/src/features/radio/LogViewer.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
38
ui/src/features/radio/LogViewer.tsx
Normal file
38
ui/src/features/radio/LogViewer.tsx
Normal 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 >
|
||||||
|
);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
Loading…
Reference in New Issue
Block a user