Skip to content

Commit fb34d4b

Browse files
authored
Merge branch 'main' into 6655-expert-debuglog-context
2 parents f888471 + acf04b8 commit fb34d4b

3 files changed

Lines changed: 196 additions & 15 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<template>
2+
<form class="space-y-6" data-el="instance-editor" @submit.prevent>
3+
<FormHeading>Limits</FormHeading>
4+
<div v-if="limitAvailable">
5+
<div v-if="limitsLauncherEnabled" class="flex flex-col sm:flex-row">
6+
<div class="w-full max-w-md sm:mr-8">
7+
<FormRow v-model="editable.settings.apiMaxLength" :error="editable.errors.apiMaxLength" type="text">
8+
Max HTTP Payload Size
9+
<template #description>
10+
The maximum number of bytes allowed in a HTTP Request in bytes ('kb','mb' modifiers allowed)
11+
</template>
12+
<template #append><ChangeIndicator :value="editable.changed.apiMaxLength" /></template>
13+
</FormRow>
14+
</div>
15+
</div>
16+
<notice-banner v-else title="Upgrade Required">
17+
<p>
18+
Please <a target="_blank" class="ff-link" href="https://flowfuse.com/docs/device-agent/install/device-agent-installer/#device-agent">upgrade</a> your Device Agent to v3.8.3 to be able to set apiMaxLength
19+
</p>
20+
</notice-banner>
21+
</div>
22+
<FeatureUnavailableToTeam v-if="!limitAvailable" featureName="Set API Size Limits" />
23+
<div v-if="hasPermission('device:edit-env')" class="space-x-4 whitespace-nowrap">
24+
<ff-button data-el="submit" size="small" :disabled="!unsavedChanges || editable.hasErrors" @click="saveSettings()">
25+
Save Settings
26+
</ff-button>
27+
</div>
28+
</form>
29+
</template>
30+
31+
<script>
32+
import semver from 'semver'
33+
34+
import { mapState } from 'vuex'
35+
36+
import deviceApi from '../../../api/devices.js'
37+
import FormHeading from '../../../components/FormHeading.vue'
38+
import FormRow from '../../../components/FormRow.vue'
39+
import FeatureUnavailableToTeam from '../../../components/banners/FeatureUnavailableToTeam.vue'
40+
import NoticeBanner from '../../../components/notices/NoticeBanner.vue'
41+
import usePermissions from '../../../composables/Permissions.js'
42+
43+
import Alerts from '../../../services/alerts.js'
44+
45+
import ChangeIndicator from '../../admin/Template/components/ChangeIndicator.vue'
46+
47+
export default {
48+
name: 'DeviceSettingsEditor',
49+
components: {
50+
NoticeBanner,
51+
FeatureUnavailableToTeam,
52+
FormRow,
53+
FormHeading,
54+
ChangeIndicator
55+
},
56+
props: {
57+
device: { type: Object, default: null }
58+
},
59+
emits: ['device-updated', 'assign-device'],
60+
setup () {
61+
const { hasPermission } = usePermissions()
62+
63+
return { hasPermission }
64+
},
65+
data () {
66+
return {
67+
editable: {
68+
settings: {
69+
apiMaxLength: ''
70+
},
71+
changed: {
72+
apiMaxLength: false
73+
},
74+
errors: {
75+
apiMaxLength: ''
76+
},
77+
hasErrors: false
78+
},
79+
original: {
80+
apiMaxLength: '',
81+
nodeRedVersion: ''
82+
}
83+
}
84+
},
85+
computed: {
86+
...mapState('account', ['features', 'team']),
87+
limitsLauncherEnabled () {
88+
if (!this.device.agentVersion) {
89+
// Device has not called home yet - so we don't know what agent
90+
// version it is running, so assume it will be new version
91+
return true
92+
}
93+
return semver.gte(this.device.agentVersion, '3.8.3')
94+
},
95+
limitAvailable () {
96+
const flag = this.features.editorLimits && this.team.type.properties.features?.editorLimits
97+
return !!flag
98+
},
99+
unsavedChanges () {
100+
return this.editable.changed.apiMaxLength
101+
}
102+
},
103+
watch: {
104+
device: {
105+
handler () {
106+
this.getSettings()
107+
},
108+
deep: true
109+
},
110+
'editable.settings': {
111+
handler () {
112+
this.validate()
113+
},
114+
deep: true
115+
}
116+
},
117+
mounted () {
118+
this.getSettings()
119+
},
120+
methods: {
121+
saveSettings: async function () {
122+
if (!this.validate()) {
123+
return
124+
}
125+
const settings = {
126+
editor: {
127+
apiMaxLength: this.editable.settings.apiMaxLength,
128+
nodeRedVersion: this.original.nodeRedVersion
129+
}
130+
}
131+
await deviceApi.updateSettings(this.device.id, settings)
132+
this.$emit('device-updated')
133+
Alerts.emit('Device settings successfully updated. NOTE: changes will be applied once the device restarts.', 'confirmation', 6000)
134+
},
135+
getSettings: async function () {
136+
if (this.device) {
137+
const settings = await deviceApi.getSettings(this.device.id)
138+
this.editable.settings.apiMaxLength = settings.editor?.apiMaxLength || '5mb'
139+
this.original.apiMaxLength = this.editable.settings.apiMaxLength
140+
this.original.nodeRedVersion = settings.editor?.nodeRedVersion
141+
this.editable.changed.apiMaxLength = false
142+
}
143+
},
144+
validate: function () {
145+
this.editable.changed.apiMaxLength = (this.editable.settings.apiMaxLength !== this.original.apiMaxLength)
146+
const pattern = /^\d+([km]b)?$/
147+
if (this.editable.settings.apiMaxLength.length > 0) {
148+
if (pattern.test(this.editable.settings.apiMaxLength)) {
149+
this.editable.errors.apiMaxLength = ''
150+
} else {
151+
this.editable.errors.apiMaxLength = 'Invalid Value'
152+
}
153+
} else {
154+
this.editable.errors.apiMaxLength = ''
155+
}
156+
this.editable.hasErrors = !!this.editable.errors.apiMaxLength
157+
return !this.editable.hasErrors
158+
}
159+
}
160+
}
161+
</script>

frontend/src/pages/device/Settings/index.vue

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div class="flex flex-col sm:flex-row flex-1 overflow-auto">
3-
<SectionSideMenu :options="sideNavigation" />
3+
<SectionSideMenu :options="sideNav" />
44
<div class="flex-grow flex-1 flex flex-col overflow-auto">
55
<router-view :device="device" @device-updated="$emit('device-updated')" @assign-device="$emit('assign-device')" />
66
</div>
@@ -29,9 +29,35 @@ export default {
2929
}
3030
},
3131
computed: {
32-
...mapState('account', ['teamMembership'])
32+
...mapState('account', ['teamMembership']),
33+
sideNav () {
34+
const canEditDevice = this.hasPermission('device:edit', { application: this.device.application })
35+
const isApplicationOwned = this.device.ownerType === 'application'
36+
37+
const nav = [
38+
{ name: 'General', path: { name: 'device-settings-general', props: { id: this.device.id } } },
39+
{ name: 'Environment', path: { name: 'device-settings-environment' } },
40+
{ name: 'Editor', path: { name: 'device-settings-editor' }, hidden: !(canEditDevice && isApplicationOwned) },
41+
{ name: 'Security', path: { name: 'device-settings-security' }, hidden: !(canEditDevice && isApplicationOwned) },
42+
{ name: 'Palette', path: { name: 'device-settings-palette' }, hidden: !(canEditDevice && isApplicationOwned) },
43+
{ name: 'Danger', path: { name: 'device-settings-danger' }, hidden: !canEditDevice }
44+
]
45+
46+
if (!this.$route.name.includes('-editor-')) return nav
47+
48+
return nav.map(route => ({
49+
...route,
50+
path: {
51+
...route.path,
52+
name: route.path.name.replace('device-', 'device-editor-')
53+
}
54+
}))
55+
}
3356
},
34-
mounted () {
57+
async mounted () {
58+
// compensate for the time it takes for the device to load when reloading the page or accessing the page via URL
59+
while (!this.device) await new Promise(resolve => setTimeout(resolve, 250))
60+
3561
if (this.checkAccess()) {
3662
// device state polling is disabled on settings pages (in ../index.vue:pollTimer())
3763
// so we need to manually refresh the device upon mounting
@@ -44,18 +70,6 @@ export default {
4470
useRouter().push({ replace: true, path: 'overview' })
4571
return false
4672
}
47-
this.sideNavigation = [
48-
{ name: 'General', path: './general' },
49-
{ name: 'Environment', path: './environment' }
50-
]
51-
if (this.hasPermission('device:edit', { application: this.device.application })) {
52-
if (this.device.ownerType === 'application') {
53-
this.sideNavigation.push({ name: 'Security', path: './security' })
54-
this.sideNavigation.push({ name: 'Palette', path: './palette' })
55-
}
56-
this.sideNavigation.push({ name: 'Danger', path: './danger' })
57-
}
58-
return true
5973
}
6074
},
6175
watch: {

frontend/src/pages/device/routes.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import DeviceLogs from './Logs.vue'
77
import DeviceOverview from './Overview.vue'
88
import DevicePerformance from './Performance.vue'
99
import DeviceSettingsDanger from './Settings/Danger.vue'
10+
import DeviceSettingsEditor from './Settings/Editor.vue'
1011
import DeviceSettingsEnvironment from './Settings/Environment.vue'
1112
import DeviceSettingsGeneral from './Settings/General.vue'
1213
import DeviceSettingsPalette from './Settings/Palette.vue'
@@ -44,6 +45,11 @@ const children = [
4445
path: 'environment',
4546
component: DeviceSettingsEnvironment
4647
},
48+
{
49+
name: 'device-settings-editor',
50+
path: 'editor',
51+
component: DeviceSettingsEditor
52+
},
4753
{
4854
name: 'device-settings-security',
4955
path: 'security',

0 commit comments

Comments
 (0)