Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions frontend/src/pages/device/Settings/Editor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<template>
<form class="space-y-6" data-el="instance-editor" @submit.prevent>
<FormHeading>Limits</FormHeading>
<div v-if="limitAvailable">
<div v-if="limitsLauncherEnabled" class="flex flex-col sm:flex-row">
<div class="w-full max-w-md sm:mr-8">
<FormRow v-model="editable.settings.apiMaxLength" :error="editable.errors.apiMaxLength" type="text">
Max HTTP Payload Size
<template #description>
The maximum number of bytes allowed in a HTTP Request in bytes ('kb','mb' modifiers allowed)
</template>
<template #append><ChangeIndicator :value="editable.changed.apiMaxLength" /></template>
</FormRow>
</div>
</div>
<notice-banner v-else title="Upgrade Required">
<p>
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
</p>
</notice-banner>
</div>
<FeatureUnavailableToTeam v-if="!limitAvailable" featureName="Set API Size Limits" />
<div v-if="hasPermission('device:edit-env')" class="space-x-4 whitespace-nowrap">
<ff-button data-el="submit" size="small" :disabled="!unsavedChanges || editable.hasErrors" @click="saveSettings()">
Save Settings
</ff-button>
</div>
</form>
</template>

<script>
import semver from 'semver'

import { mapState } from 'vuex'

import deviceApi from '../../../api/devices.js'
import FormHeading from '../../../components/FormHeading.vue'
import FormRow from '../../../components/FormRow.vue'
import FeatureUnavailableToTeam from '../../../components/banners/FeatureUnavailableToTeam.vue'
import NoticeBanner from '../../../components/notices/NoticeBanner.vue'
import usePermissions from '../../../composables/Permissions.js'

import Alerts from '../../../services/alerts.js'

import ChangeIndicator from '../../admin/Template/components/ChangeIndicator.vue'

export default {
name: 'DeviceSettingsEditor',
components: {
NoticeBanner,
FeatureUnavailableToTeam,
FormRow,
FormHeading,
ChangeIndicator
},
props: {
device: { type: Object, default: null }
},
emits: ['device-updated', 'assign-device'],
setup () {
const { hasPermission } = usePermissions()

return { hasPermission }
},
data () {
return {
editable: {
settings: {
apiMaxLength: ''
},
changed: {
apiMaxLength: false
},
errors: {
apiMaxLength: ''
},
hasErrors: false
},
original: {
apiMaxLength: '',
nodeRedVersion: ''
}
}
},
computed: {
...mapState('account', ['features', 'team']),
limitsLauncherEnabled () {
if (!this.device.agentVersion) {
// Device has not called home yet - so we don't know what agent
// version it is running, so assume it will be new version
return true
}
return semver.gte(this.device.agentVersion, '3.8.3')
},
limitAvailable () {
const flag = this.features.editorLimits && this.team.type.properties.features?.editorLimits
return !!flag
},
unsavedChanges () {
return this.editable.changed.apiMaxLength
}
},
watch: {
device: {
handler () {
this.getSettings()
},
deep: true
},
'editable.settings': {
handler () {
this.validate()
},
deep: true
}
},
mounted () {
this.getSettings()
},
methods: {
saveSettings: async function () {
if (!this.validate()) {
return
}
const settings = {
editor: {
apiMaxLength: this.editable.settings.apiMaxLength,
nodeRedVersion: this.original.nodeRedVersion
}
}
await deviceApi.updateSettings(this.device.id, settings)
this.$emit('device-updated')
Alerts.emit('Device settings successfully updated. NOTE: changes will be applied once the device restarts.', 'confirmation', 6000)
},
getSettings: async function () {
if (this.device) {
const settings = await deviceApi.getSettings(this.device.id)
this.editable.settings.apiMaxLength = settings.editor?.apiMaxLength || '5mb'
this.original.apiMaxLength = this.editable.settings.apiMaxLength
this.original.nodeRedVersion = settings.editor?.nodeRedVersion
this.editable.changed.apiMaxLength = false
}
},
validate: function () {
this.editable.changed.apiMaxLength = (this.editable.settings.apiMaxLength !== this.original.apiMaxLength)
const pattern = /^\d+([km]b)?$/
if (this.editable.settings.apiMaxLength.length > 0) {
if (pattern.test(this.editable.settings.apiMaxLength)) {
this.editable.errors.apiMaxLength = ''
} else {
this.editable.errors.apiMaxLength = 'Invalid Value'
}
} else {
this.editable.errors.apiMaxLength = ''
}
this.editable.hasErrors = !!this.editable.errors.apiMaxLength
return !this.editable.hasErrors
}
}
}
</script>
44 changes: 29 additions & 15 deletions frontend/src/pages/device/Settings/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col sm:flex-row flex-1 overflow-auto">
<SectionSideMenu :options="sideNavigation" />
<SectionSideMenu :options="sideNav" />
<div class="flex-grow flex-1 flex flex-col overflow-auto">
<router-view :device="device" @device-updated="$emit('device-updated')" @assign-device="$emit('assign-device')" />
</div>
Expand Down Expand Up @@ -29,9 +29,35 @@ export default {
}
},
computed: {
...mapState('account', ['teamMembership'])
...mapState('account', ['teamMembership']),
sideNav () {
const canEditDevice = this.hasPermission('device:edit', { application: this.device.application })
const isApplicationOwned = this.device.ownerType === 'application'

const nav = [
{ name: 'General', path: { name: 'device-settings-general', props: { id: this.device.id } } },
{ name: 'Environment', path: { name: 'device-settings-environment' } },
{ name: 'Editor', path: { name: 'device-settings-editor' }, hidden: !(canEditDevice && isApplicationOwned) },
{ name: 'Security', path: { name: 'device-settings-security' }, hidden: !(canEditDevice && isApplicationOwned) },
{ name: 'Palette', path: { name: 'device-settings-palette' }, hidden: !(canEditDevice && isApplicationOwned) },
{ name: 'Danger', path: { name: 'device-settings-danger' }, hidden: !canEditDevice }
]

if (!this.$route.name.includes('-editor-')) return nav

return nav.map(route => ({
...route,
path: {
...route.path,
name: route.path.name.replace('device-', 'device-editor-')
}
}))
}
},
mounted () {
async mounted () {
// compensate for the time it takes for the device to load when reloading the page or accessing the page via URL
while (!this.device) await new Promise(resolve => setTimeout(resolve, 250))

if (this.checkAccess()) {
// device state polling is disabled on settings pages (in ../index.vue:pollTimer())
// so we need to manually refresh the device upon mounting
Expand All @@ -44,18 +70,6 @@ export default {
useRouter().push({ replace: true, path: 'overview' })
return false
}
this.sideNavigation = [
{ name: 'General', path: './general' },
{ name: 'Environment', path: './environment' }
]
if (this.hasPermission('device:edit', { application: this.device.application })) {
if (this.device.ownerType === 'application') {
this.sideNavigation.push({ name: 'Security', path: './security' })
this.sideNavigation.push({ name: 'Palette', path: './palette' })
}
this.sideNavigation.push({ name: 'Danger', path: './danger' })
}
return true
}
},
watch: {
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/pages/device/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import DeviceLogs from './Logs.vue'
import DeviceOverview from './Overview.vue'
import DevicePerformance from './Performance.vue'
import DeviceSettingsDanger from './Settings/Danger.vue'
import DeviceSettingsEditor from './Settings/Editor.vue'
import DeviceSettingsEnvironment from './Settings/Environment.vue'
import DeviceSettingsGeneral from './Settings/General.vue'
import DeviceSettingsPalette from './Settings/Palette.vue'
Expand Down Expand Up @@ -44,6 +45,11 @@ const children = [
path: 'environment',
component: DeviceSettingsEnvironment
},
{
name: 'device-settings-editor',
path: 'editor',
component: DeviceSettingsEditor
},
{
name: 'device-settings-security',
path: 'security',
Expand Down
Loading