Skip to content

Commit ac130ff

Browse files
Williangalvanipatrickelectric
authored andcommitted
frontend: add master endpoint management interface
1 parent b10e35b commit ac130ff

File tree

2 files changed

+233
-1
lines changed

2 files changed

+233
-1
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
<template>
2+
<div class="master-endpoint-manager d-flex flex-column align-center">
3+
<v-card
4+
width="100%"
5+
class="pa-4"
6+
>
7+
<v-card-title class="text-h6 mb-2">
8+
Master Endpoint Configuration
9+
</v-card-title>
10+
11+
<v-form
12+
ref="form"
13+
v-model="form_valid"
14+
lazy-validation
15+
>
16+
<v-select
17+
v-model="endpoint.connection_type"
18+
:items="endpoint_types"
19+
label="Connection Type"
20+
:rules="[validate_required_field]"
21+
@change="updateDefaultPlace"
22+
/>
23+
24+
<v-text-field
25+
v-model="endpoint.place"
26+
:rules="[validate_required_field, is_ip_address_path, is_useable_ip_address]"
27+
label="IP/Device"
28+
/>
29+
30+
<v-text-field
31+
v-model.number="endpoint.argument"
32+
label="Port/Baudrate"
33+
:rules="[is_socket_port_baudrate]"
34+
/>
35+
36+
<v-card-actions class="mt-4">
37+
<v-spacer />
38+
<v-btn
39+
color="primary"
40+
:loading="saving"
41+
:disabled="!form_valid || !has_changes"
42+
@click="saveEndpoint"
43+
>
44+
Save Changes
45+
</v-btn>
46+
</v-card-actions>
47+
</v-form>
48+
</v-card>
49+
50+
<v-snackbar
51+
v-model="show_success"
52+
color="success"
53+
timeout="3000"
54+
>
55+
Master endpoint updated successfully
56+
<template #action="{ attrs }">
57+
<v-btn
58+
text
59+
v-bind="attrs"
60+
@click="show_success = false"
61+
>
62+
Close
63+
</v-btn>
64+
</template>
65+
</v-snackbar>
66+
</div>
67+
</template>
68+
69+
<script lang="ts">
70+
import Vue from 'vue'
71+
72+
import Notifier from '@/libs/notifier'
73+
import autopilot from '@/store/autopilot_manager'
74+
import beacon from '@/store/beacon'
75+
import { AutopilotEndpoint, EndpointType, userFriendlyEndpointType } from '@/types/autopilot'
76+
import { autopilot_service } from '@/types/frontend_services'
77+
import { VForm } from '@/types/vuetify'
78+
import back_axios from '@/utils/api'
79+
import {
80+
isBaudrate, isFilepath, isIpAddress, isNotEmpty, isSocketPort,
81+
} from '@/utils/pattern_validators'
82+
83+
const notifier = new Notifier(autopilot_service)
84+
85+
const defaultEndpoint: AutopilotEndpoint = {
86+
name: 'master',
87+
owner: 'User',
88+
connection_type: EndpointType.udpin,
89+
place: '0.0.0.0',
90+
argument: 14551,
91+
persistent: true,
92+
protected: false,
93+
enabled: true,
94+
}
95+
96+
export default Vue.extend({
97+
name: 'MasterEndpointManager',
98+
data() {
99+
return {
100+
form_valid: true,
101+
saving: false,
102+
show_success: false,
103+
original_endpoint: { ...defaultEndpoint },
104+
endpoint: { ...defaultEndpoint },
105+
}
106+
},
107+
computed: {
108+
endpoint_types(): {value: EndpointType, text: string}[] {
109+
return Object.entries(EndpointType).map(
110+
(type) => ({ value: type[1], text: userFriendlyEndpointType(type[1]) }),
111+
)
112+
},
113+
form(): VForm {
114+
return this.$refs.form as VForm
115+
},
116+
user_ip_address(): string {
117+
return beacon.client_ip_address
118+
},
119+
available_ips(): string[] {
120+
return [...new Set(beacon.available_domains.map((domain) => domain.ip))]
121+
},
122+
has_changes(): boolean {
123+
return this.endpoint.connection_type !== this.original_endpoint.connection_type
124+
|| this.endpoint.place !== this.original_endpoint.place
125+
|| this.endpoint.argument !== this.original_endpoint.argument
126+
},
127+
},
128+
mounted() {
129+
this.fetchCurrentEndpoint()
130+
},
131+
methods: {
132+
validate_required_field(input: string): (true | string) {
133+
return isNotEmpty(input) ? true : 'Required field.'
134+
},
135+
is_ip_address_path(input: string): (true | string) {
136+
return isIpAddress(input) || isFilepath(input) ? true : 'Invalid IP/Device-path.'
137+
},
138+
is_useable_ip_address(input: string): (true | string) {
139+
if ([EndpointType.udpin, EndpointType.tcpin].includes(this.endpoint.connection_type)) {
140+
if (!['0.0.0.0', ...this.available_ips].includes(input)) {
141+
return 'This IP is not available at any of the network interfaces.'
142+
}
143+
}
144+
if ([EndpointType.udpout, EndpointType.tcpout].includes(this.endpoint.connection_type)) {
145+
if (input === '0.0.0.0') return '0.0.0.0 as a client is undefined behavior.'
146+
}
147+
return true
148+
},
149+
is_socket_port_baudrate(input: number): (true | string) {
150+
if (typeof input === 'string') {
151+
return 'Please use an integer value.'
152+
}
153+
return isSocketPort(input) || isBaudrate(input) ? true : 'Invalid Port/Baudrate.'
154+
},
155+
updateDefaultPlace(): void {
156+
switch (this.endpoint.connection_type) {
157+
case EndpointType.udpin:
158+
case EndpointType.tcpin:
159+
this.endpoint.place = '0.0.0.0'
160+
break
161+
case EndpointType.udpout:
162+
case EndpointType.tcpout:
163+
this.endpoint.place = this.user_ip_address
164+
break
165+
default:
166+
this.endpoint.place = '/dev/ttyAMA1' // Serial3
167+
}
168+
},
169+
async fetchCurrentEndpoint(): Promise<void> {
170+
try {
171+
const response = await back_axios({
172+
method: 'get',
173+
url: `${autopilot.API_URL}/endpoints/manual_board_master_endpoint`,
174+
timeout: 10000,
175+
})
176+
const endpoint_data = {
177+
...defaultEndpoint,
178+
...response.data,
179+
}
180+
this.endpoint = { ...endpoint_data }
181+
this.original_endpoint = { ...endpoint_data }
182+
} catch (error) {
183+
notifier.pushBackError('MASTER_ENDPOINT_FETCH_FAIL', error)
184+
}
185+
},
186+
async saveEndpoint(): Promise<void> {
187+
if (!this.form.validate()) {
188+
return
189+
}
190+
191+
this.saving = true
192+
try {
193+
await back_axios({
194+
method: 'post',
195+
url: `${autopilot.API_URL}/endpoints/manual_board_master_endpoint`,
196+
timeout: 10000,
197+
data: this.endpoint,
198+
})
199+
this.original_endpoint = { ...this.endpoint }
200+
this.show_success = true
201+
} catch (error) {
202+
notifier.pushBackError('MASTER_ENDPOINT_SAVE_FAIL', error)
203+
} finally {
204+
this.saving = false
205+
}
206+
},
207+
},
208+
})
209+
</script>
210+
211+
<style scoped>
212+
.master-endpoint-manager {
213+
width: 100%;
214+
max-width: 600px;
215+
margin: 0 auto;
216+
}
217+
</style>

core/frontend/src/views/Autopilot.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@
4444
<br>
4545
</div>
4646
<not-safe-overlay />
47-
<v-expansion-panels>
47+
<v-expansion-panels v-if="is_external_board">
48+
<v-expansion-panel>
49+
<v-expansion-panel-header>
50+
Master endpoint
51+
</v-expansion-panel-header>
52+
<v-expansion-panel-content>
53+
<master-endpoint-manager />
54+
</v-expansion-panel-content>
55+
</v-expansion-panel>
56+
</v-expansion-panels>
57+
<v-expansion-panels v-else>
4858
<v-expansion-panel>
4959
<v-expansion-panel-header>
5060
Firmware update
@@ -127,6 +137,7 @@ import {
127137
import AutopilotSerialConfiguration from '@/components/autopilot/AutopilotSerialConfiguration.vue'
128138
import BoardChangeDialog from '@/components/autopilot/BoardChangeDialog.vue'
129139
import FirmwareManager from '@/components/autopilot/FirmwareManager.vue'
140+
import MasterEndpointManager from '@/components/autopilot/MasterEndpointManager.vue'
130141
import NotSafeOverlay from '@/components/common/NotSafeOverlay.vue'
131142
import { MavAutopilot } from '@/libs/MAVLink2Rest/mavlink2rest-ts/messages/mavlink2rest-enum'
132143
import Notifier from '@/libs/notifier'
@@ -148,6 +159,7 @@ export default Vue.extend({
148159
FirmwareManager,
149160
AutopilotSerialConfiguration,
150161
NotSafeOverlay,
162+
MasterEndpointManager,
151163
},
152164
data() {
153165
return {
@@ -201,6 +213,9 @@ export default Vue.extend({
201213
}
202214
return ['Navigator', 'Navigator64', 'SITL'].includes(boardname)
203215
},
216+
is_external_board(): boolean {
217+
return autopilot.current_board?.name === 'Manual'
218+
},
204219
current_board(): FlightController | null {
205220
return autopilot.current_board
206221
},

0 commit comments

Comments
 (0)