mirror of
https://github.com/thomasnordquist/MQTT-Explorer.git
synced 2026-09-11 09:03:33 +00:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
affa56f8a2 | ||
|
|
f0533a25db | ||
|
|
e0f6f86773 | ||
|
|
de53571b88 | ||
|
|
c7021e19ca | ||
|
|
a84d79ac3a | ||
|
|
a3bca962ae | ||
|
|
715c50127b | ||
|
|
1ad2ed73ec | ||
|
|
e607d70374 | ||
|
|
1b1558ff12 | ||
|
|
96b64fcffd | ||
|
|
1ebb813261 |
@@ -10,6 +10,12 @@ import { globalActions } from './'
|
||||
import { showError } from './Global'
|
||||
import { showTree } from './Tree'
|
||||
import { TopicViewModel } from '../model/TopicViewModel'
|
||||
import { backendEvents } from '../../../events'
|
||||
import {
|
||||
Events,
|
||||
MAX_MESSAGE_SIZE_UNLIMITED,
|
||||
MAX_MESSAGE_SIZE_DEFAULT,
|
||||
} from '../../../events/EventsV2'
|
||||
|
||||
const settingsIdentifier: StorageIdentifier<Partial<SettingsStateModel>> = {
|
||||
id: 'Settings',
|
||||
@@ -22,6 +28,9 @@ export const loadSettings = () => async (dispatch: Dispatch<any>, getState: () =
|
||||
settings: getState().settings.merge(settings),
|
||||
type: ActionTypes.SETTINGS_DID_LOAD_SETTINGS,
|
||||
})
|
||||
// Emit the maxMessageSize to backend after loading settings
|
||||
const maxMessageSize = getState().settings.get('maxMessageSize')
|
||||
backendEvents.emit(Events.setMaxMessageSize, maxMessageSize)
|
||||
} catch (error) {
|
||||
dispatch(showError(error))
|
||||
}
|
||||
@@ -29,11 +38,14 @@ export const loadSettings = () => async (dispatch: Dispatch<any>, getState: () =
|
||||
}
|
||||
|
||||
export const storeSettings = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const currentSettings = getState().settings.toJS()
|
||||
const settings = {
|
||||
...getState().settings.toJS(),
|
||||
...currentSettings,
|
||||
autoExpandLimit: undefined,
|
||||
topicFilter: undefined,
|
||||
visible: undefined,
|
||||
// Don't persist unlimited - reset to default
|
||||
maxMessageSize: currentSettings.maxMessageSize === MAX_MESSAGE_SIZE_UNLIMITED ? MAX_MESSAGE_SIZE_DEFAULT : currentSettings.maxMessageSize,
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -169,3 +181,12 @@ export const toggleTheme = () => (dispatch: Dispatch<any>, getState: () => AppSt
|
||||
})
|
||||
dispatch(storeSettings())
|
||||
}
|
||||
|
||||
export const setMaxMessageSize = (maxMessageSize: number) => (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
maxMessageSize,
|
||||
type: ActionTypes.SETTINGS_SET_MAX_MESSAGE_SIZE,
|
||||
})
|
||||
dispatch(storeSettings())
|
||||
backendEvents.emit(Events.setMaxMessageSize, maxMessageSize)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ import { shell } from 'electron'
|
||||
import { Theme } from '@mui/material/styles'
|
||||
import { withStyles } from '@mui/styles'
|
||||
import { TopicOrder } from '../../reducers/Settings'
|
||||
import {
|
||||
MAX_MESSAGE_SIZE_20KB,
|
||||
MAX_MESSAGE_SIZE_100KB,
|
||||
MAX_MESSAGE_SIZE_1MB,
|
||||
MAX_MESSAGE_SIZE_5MB,
|
||||
MAX_MESSAGE_SIZE_UNLIMITED,
|
||||
} from '../../../../events/EventsV2'
|
||||
|
||||
import {
|
||||
Divider,
|
||||
@@ -89,6 +96,7 @@ interface Props {
|
||||
topicOrder: TopicOrder
|
||||
visible: boolean
|
||||
theme: 'light' | 'dark'
|
||||
maxMessageSize: number
|
||||
}
|
||||
|
||||
class Settings extends React.PureComponent<Props, {}> {
|
||||
@@ -204,6 +212,47 @@ class Settings extends React.PureComponent<Props, {}> {
|
||||
this.props.actions.settings.setTopicOrder(e.target.value as TopicOrder)
|
||||
}
|
||||
|
||||
private renderMaxMessageSize() {
|
||||
const { classes, maxMessageSize } = this.props
|
||||
|
||||
const formatSize = (size: number) => {
|
||||
if (size === MAX_MESSAGE_SIZE_UNLIMITED) {
|
||||
return 'Unlimited'
|
||||
} else if (size >= 1000000) {
|
||||
return `${size / 1000000} MB`
|
||||
} else if (size >= 1000) {
|
||||
return `${size / 1000} KB`
|
||||
}
|
||||
return `${size} bytes`
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '8px', display: 'flex' }}>
|
||||
<InputLabel htmlFor="max-message-size" style={{ flex: '1', marginTop: '8px' }}>
|
||||
Max Message Size
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={maxMessageSize}
|
||||
onChange={this.onChangeMaxMessageSize}
|
||||
input={<Input name="max-message-size" id="max-message-size-label-placeholder" />}
|
||||
name="max-message-size"
|
||||
className={classes.input}
|
||||
style={{ flex: '1' }}
|
||||
>
|
||||
<MenuItem value={MAX_MESSAGE_SIZE_20KB}>{formatSize(MAX_MESSAGE_SIZE_20KB)}</MenuItem>
|
||||
<MenuItem value={MAX_MESSAGE_SIZE_100KB}>{formatSize(MAX_MESSAGE_SIZE_100KB)}</MenuItem>
|
||||
<MenuItem value={MAX_MESSAGE_SIZE_1MB}>{formatSize(MAX_MESSAGE_SIZE_1MB)}</MenuItem>
|
||||
<MenuItem value={MAX_MESSAGE_SIZE_5MB}>{formatSize(MAX_MESSAGE_SIZE_5MB)}</MenuItem>
|
||||
<MenuItem value={MAX_MESSAGE_SIZE_UNLIMITED}>{formatSize(MAX_MESSAGE_SIZE_UNLIMITED)}</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private onChangeMaxMessageSize = (e: React.ChangeEvent<{ value: unknown }>) => {
|
||||
this.props.actions.settings.setMaxMessageSize(parseInt(String(e.target.value), 10))
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { classes, actions, visible } = this.props
|
||||
return (
|
||||
@@ -221,6 +270,7 @@ class Settings extends React.PureComponent<Props, {}> {
|
||||
{this.renderAutoExpand()}
|
||||
{this.renderNodeOrder()}
|
||||
<TimeLocale />
|
||||
{this.renderMaxMessageSize()}
|
||||
{this.renderHighlightTopicUpdates()}
|
||||
{this.selectTopicsOnMouseOver()}
|
||||
{this.toggleTheme()}
|
||||
@@ -244,6 +294,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
highlightTopicUpdates: state.settings.get('highlightTopicUpdates'),
|
||||
selectTopicWithMouseOver: state.settings.get('selectTopicWithMouseOver'),
|
||||
theme: state.settings.get('theme'),
|
||||
maxMessageSize: state.settings.get('maxMessageSize'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createReducer } from './lib'
|
||||
import { Record } from 'immutable'
|
||||
import { MAX_MESSAGE_SIZE_DEFAULT } from '../../../events/EventsV2'
|
||||
|
||||
export enum TopicOrder {
|
||||
none = 'none',
|
||||
@@ -18,6 +19,7 @@ export interface SettingsStateModel {
|
||||
valueRendererDisplayMode: ValueRendererDisplayMode
|
||||
selectTopicWithMouseOver: boolean
|
||||
theme: 'light' | 'dark'
|
||||
maxMessageSize: number
|
||||
}
|
||||
|
||||
export type SettingsState = Record<SettingsStateModel>
|
||||
@@ -30,7 +32,8 @@ export type Actions = SetAutoExpandLimitAction &
|
||||
SetValueRendererDisplayModeAction &
|
||||
SetTheme &
|
||||
SetSelectTopicWithMouseOverAction &
|
||||
SetTimeLocale
|
||||
SetTimeLocale &
|
||||
SetMaxMessageSizeAction
|
||||
|
||||
export enum ActionTypes {
|
||||
SETTINGS_SET_AUTO_EXPAND_LIMIT = 'SETTINGS_SET_AUTO_EXPAND_LIMIT',
|
||||
@@ -43,6 +46,7 @@ export enum ActionTypes {
|
||||
SETTINGS_SET_THEME_LIGHT = 'SETTINGS_SET_THEME_LIGHT',
|
||||
SETTINGS_SET_THEME_DARK = 'SETTINGS_SET_THEME_DARK',
|
||||
SETTINGS_SET_TIME_LOCALE = 'SETTINGS_SET_TIME_LOCALE',
|
||||
SETTINGS_SET_MAX_MESSAGE_SIZE = 'SETTINGS_SET_MAX_MESSAGE_SIZE',
|
||||
}
|
||||
|
||||
const initialState = Record<SettingsStateModel>({
|
||||
@@ -54,6 +58,7 @@ const initialState = Record<SettingsStateModel>({
|
||||
selectTopicWithMouseOver: false,
|
||||
theme: 'light',
|
||||
topicFilter: undefined,
|
||||
maxMessageSize: MAX_MESSAGE_SIZE_DEFAULT,
|
||||
})
|
||||
|
||||
const setTheme = (theme: 'light' | 'dark') => (state: SettingsState) => {
|
||||
@@ -73,6 +78,7 @@ const reducerActions: {
|
||||
SETTINGS_SET_THEME_LIGHT: setTheme('light'),
|
||||
SETTINGS_SET_THEME_DARK: setTheme('dark'),
|
||||
SETTINGS_SET_TIME_LOCALE: setTimeLocale,
|
||||
SETTINGS_SET_MAX_MESSAGE_SIZE: setMaxMessageSize,
|
||||
}
|
||||
|
||||
export const settingsReducer = createReducer(initialState(), reducerActions)
|
||||
@@ -153,3 +159,12 @@ export interface FilterTopicsAction {
|
||||
function filterTopics(state: SettingsState, action: FilterTopicsAction) {
|
||||
return state.set('topicFilter', action.topicFilter)
|
||||
}
|
||||
|
||||
export interface SetMaxMessageSizeAction {
|
||||
type: ActionTypes.SETTINGS_SET_MAX_MESSAGE_SIZE
|
||||
maxMessageSize: number
|
||||
}
|
||||
|
||||
function setMaxMessageSize(state: SettingsState, action: SetMaxMessageSizeAction) {
|
||||
return state.set('maxMessageSize', action.maxMessageSize)
|
||||
}
|
||||
|
||||
+18
-2
@@ -9,10 +9,17 @@ import {
|
||||
makePublishEvent,
|
||||
removeConnection,
|
||||
} from '../../events'
|
||||
import {
|
||||
Events,
|
||||
MAX_MESSAGE_SIZE_DEFAULT,
|
||||
MAX_MESSAGE_SIZE_20KB,
|
||||
MAX_MESSAGE_SIZE_UNLIMITED,
|
||||
} from '../../events/EventsV2'
|
||||
import { EventBusInterface } from '../../events/EventSystem/EventBusInterface'
|
||||
|
||||
export class ConnectionManager {
|
||||
private connections: { [s: string]: DataSource<any> } = {}
|
||||
private maxMessageSize: number = MAX_MESSAGE_SIZE_DEFAULT
|
||||
private backendEvents: EventBusInterface
|
||||
|
||||
constructor(backendEvents: EventBusInterface) {
|
||||
@@ -47,8 +54,9 @@ export class ConnectionManager {
|
||||
const messageEvent = makeConnectionMessageEvent(connectionId)
|
||||
connection.onMessage((topic: string, payload: Buffer, packet: any) => {
|
||||
let buffer = payload
|
||||
if (buffer.length > 20000) {
|
||||
buffer = buffer.slice(0, 20000)
|
||||
// Only apply limit if not unlimited
|
||||
if (this.maxMessageSize !== MAX_MESSAGE_SIZE_UNLIMITED && buffer.length > this.maxMessageSize) {
|
||||
buffer = buffer.slice(0, this.maxMessageSize)
|
||||
}
|
||||
|
||||
let decoded_payload = null
|
||||
@@ -69,6 +77,14 @@ export class ConnectionManager {
|
||||
this.backendEvents.subscribe(removeConnection, (connectionId: string) => {
|
||||
this.removeConnection(connectionId)
|
||||
})
|
||||
this.backendEvents.subscribe(Events.setMaxMessageSize, (maxMessageSize: number) => {
|
||||
// Validate: must be an integer >= 20KB or unlimited (-1)
|
||||
if (typeof maxMessageSize === 'number' && Number.isInteger(maxMessageSize)) {
|
||||
if (maxMessageSize === MAX_MESSAGE_SIZE_UNLIMITED || maxMessageSize >= MAX_MESSAGE_SIZE_20KB) {
|
||||
this.maxMessageSize = maxMessageSize
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public removeConnection(connectionId: string) {
|
||||
|
||||
@@ -62,3 +62,15 @@ export const writeToFile: RpcEvent<{ filePath: string; data: string; encoding?:
|
||||
export const readFromFile: RpcEvent<{ filePath: string; encoding?: string }, Buffer> = {
|
||||
topic: 'readFromFile',
|
||||
}
|
||||
|
||||
export const MAX_MESSAGE_SIZE_20KB = 20000
|
||||
export const MAX_MESSAGE_SIZE_100KB = 100000
|
||||
export const MAX_MESSAGE_SIZE_1MB = 1000000
|
||||
export const MAX_MESSAGE_SIZE_5MB = 5000000
|
||||
export const MAX_MESSAGE_SIZE_UNLIMITED = -1
|
||||
export const MAX_MESSAGE_SIZE_DEFAULT = MAX_MESSAGE_SIZE_20KB
|
||||
|
||||
export const setMaxMessageSize: Event<number> = {
|
||||
topic: 'settings/maxMessageSize',
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ export const Events = {
|
||||
removeConnection: { topic: 'connection/remove' } as EventV2<string>,
|
||||
updateAvailable: { topic: 'app/update/available' } as EventV2<UpdateInfo>,
|
||||
|
||||
// Settings
|
||||
setMaxMessageSize: { topic: 'settings/maxMessageSize' } as EventV2<number>,
|
||||
|
||||
// Parameterized events (for connection-specific events)
|
||||
connectionState: (connectionId: string) => ({ topic: `conn/state/${connectionId}` }) as EventV2<DataSourceState>,
|
||||
connectionMessage: (connectionId: string) => ({ topic: `conn/${connectionId}` }) as EventV2<MqttMessageV2>,
|
||||
@@ -62,6 +65,14 @@ export interface CertificateUploadResponse {
|
||||
data: string // base64 encoded
|
||||
}
|
||||
|
||||
// Message size constants
|
||||
export const MAX_MESSAGE_SIZE_20KB = 20000
|
||||
export const MAX_MESSAGE_SIZE_100KB = 100000
|
||||
export const MAX_MESSAGE_SIZE_1MB = 1000000
|
||||
export const MAX_MESSAGE_SIZE_5MB = 5000000
|
||||
export const MAX_MESSAGE_SIZE_UNLIMITED = -1
|
||||
export const MAX_MESSAGE_SIZE_DEFAULT = MAX_MESSAGE_SIZE_20KB
|
||||
|
||||
// Electron dialog types (re-exported for convenience)
|
||||
import { OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user