Skip to content

Commit 4341675

Browse files
authored
Merge pull request #6628 from FlowFuse/6599_add-the-plain-embedded-remote-instance-editor
Add the embedded remote instance editor route
2 parents 7ed08a3 + 3ecd004 commit 4341675

6 files changed

Lines changed: 225 additions & 4 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<template>
2+
<section class="editor-wrapper">
3+
<LoadingScreenWrapper
4+
v-if="!isDeviceRunning"
5+
:state="computedStatus"
6+
/>
7+
8+
<iframe
9+
v-else
10+
ref="iframe"
11+
width="100%"
12+
height="100%"
13+
name="immersive-editor-iframe"
14+
:src="device.editor.url"
15+
referrerpolicy="strict-origin-when-cross-origin"
16+
allowfullscreen
17+
:style="{'pointer-events': disableEvents ? 'none' : 'auto'}"
18+
data-el="editor-iframe"
19+
/>
20+
</section>
21+
</template>
22+
23+
<script>
24+
import LoadingScreenWrapper from './LoadingScreenWrapper.vue'
25+
26+
export default {
27+
name: 'RemoteInstanceEditorWrapper',
28+
components: { LoadingScreenWrapper },
29+
props: {
30+
device: {
31+
required: false,
32+
type: Object,
33+
default: null
34+
},
35+
disableEvents: {
36+
type: Boolean,
37+
default: false
38+
}
39+
},
40+
computed: {
41+
isDeviceRunning () {
42+
return this.computedStatus === 'running'
43+
},
44+
computedStatus () {
45+
if (!this.device || !Object.prototype.hasOwnProperty.call(this.device, 'editor')) {
46+
return 'loading'
47+
}
48+
49+
return this.device.status
50+
}
51+
}
52+
}
53+
</script>
54+
55+
<style scoped lang="scss">
56+
57+
</style>
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<template>
2+
<div ref="resizeTarget" class="ff--immersive-editor-wrapper" :class="{resizing: isEditorResizing}">
3+
<EditorWrapper
4+
:url="device?.editor?.url"
5+
:device="device"
6+
/>
7+
8+
<section
9+
class="tabs-wrapper drawer"
10+
:class="{'open': drawer.open, resizing: isEditorResizing}"
11+
:style="{ width: editorWidthStyle }"
12+
data-el="tabs-drawer"
13+
@mouseenter="handleDrawerMouseEnter"
14+
@mouseleave="handleDrawerMouseLeave"
15+
>
16+
<resize-bar
17+
@mousedown="startEditorResize"
18+
/>
19+
</section>
20+
</div>
21+
</template>
22+
23+
<script>
24+
25+
import semver from 'semver'
26+
import { mapActions } from 'vuex'
27+
28+
import deviceApi from '../../../api/devices.js'
29+
import ResizeBar from '../../../components/ResizeBar.vue'
30+
import EditorWrapper from '../../../components/immersive-editor/RemoteInstanceEditorWrapper.vue'
31+
import { useDrawerHelper } from '../../../composables/DrawerHelper.js'
32+
import { useResizingHelper } from '../../../composables/ResizingHelper.js'
33+
import Alerts from '../../../services/alerts.js'
34+
35+
export default {
36+
name: 'DeviceEditor',
37+
components: {
38+
ResizeBar,
39+
EditorWrapper
40+
},
41+
setup () {
42+
const {
43+
drawer,
44+
toggleDrawer,
45+
notifyDrawerState,
46+
handleDrawerMouseEnter,
47+
handleDrawerMouseLeave,
48+
runInitialTease,
49+
bindDrawer,
50+
cleanup: cleanupDrawer
51+
} = useDrawerHelper()
52+
53+
const {
54+
startResize: startEditorResize,
55+
widthStyle: editorWidthStyle,
56+
bindResizer: bindDrawerResizer,
57+
isResizing: isEditorResizing
58+
} = useResizingHelper()
59+
60+
return {
61+
startEditorResize,
62+
bindDrawerResizer,
63+
editorWidthStyle,
64+
drawer,
65+
isEditorResizing,
66+
toggleDrawer,
67+
notifyDrawerState,
68+
handleDrawerMouseEnter,
69+
handleDrawerMouseLeave,
70+
runInitialTease,
71+
bindDrawer,
72+
cleanupDrawer
73+
}
74+
},
75+
data () {
76+
return {
77+
agentSupportsDeviceAccess: null,
78+
agentSupportsActions: null,
79+
device: null,
80+
openingTunnel: false,
81+
openTunnelTimeout: null
82+
}
83+
},
84+
computed: {
85+
86+
},
87+
watch: {
88+
device (device) {
89+
if (device && Object.prototype.hasOwnProperty.call(device, 'editor')) {
90+
this.setContextDevice(device)
91+
} else {
92+
Alerts.emit('Unable to connect to the Remote Instance', 'warning')
93+
94+
setTimeout(() => this.$router.push({ name: 'device-overview' }), 2000)
95+
}
96+
}
97+
},
98+
mounted () {
99+
this.loadDevice().catch(err => err)
100+
},
101+
methods: {
102+
...mapActions('context', { setContextDevice: 'setDevice' }),
103+
loadDevice: async function () {
104+
try {
105+
this.device = await deviceApi.getDevice(this.$route.params.id)
106+
} catch (err) {
107+
if (err.status === 403) {
108+
clearTimeout(this.openTunnelTimeout)
109+
return this.$router.push({ name: 'Home' })
110+
}
111+
} finally {
112+
this.loading = false
113+
}
114+
115+
this.agentSupportsDeviceAccess = this.device.agentVersion && semver.gte(this.device.agentVersion, '0.8.0')
116+
this.agentSupportsActions = this.device.agentVersion && semver.gte(this.device.agentVersion, '2.3.0')
117+
118+
// todo we first need to get the device and set the team afterwards
119+
await this.$store.dispatch('account/setTeam', this.device.team.slug)
120+
}
121+
}
122+
}
123+
</script>
124+
125+
<style scoped lang="scss">
126+
127+
</style>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import DeviceEditor from './index.vue'
2+
3+
export default [
4+
{
5+
path: '/device/:id/editor',
6+
name: 'device-editor',
7+
component: DeviceEditor,
8+
meta: {
9+
title: 'Device - Editor',
10+
layout: 'plain'
11+
}
12+
}
13+
]

frontend/src/routes.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import PageNotFound from './pages/PageNotFound.vue'
77

88
import AccountRoutes from './pages/account/routes.js'
99
import AdminRoutes from './pages/admin/routes.js'
10+
import DeviceEditorRoutes from './pages/device/Editor/routes.js'
1011
import DeviceRoutes from './pages/device/routes.js'
1112
import HelpRoutes from './pages/help/routes.js'
12-
import EditorRoutes from './pages/instance/Editor/routes.js'
13+
import InstanceEditorRoutes from './pages/instance/Editor/routes.js'
1314
import InstanceRoutes from './pages/instance/routes.js'
1415
import TeamRoutes from './pages/team/routes.js'
1516

@@ -30,7 +31,8 @@ const routes = [
3031
...TeamRoutes,
3132
...AdminRoutes,
3233
...HelpRoutes,
33-
...EditorRoutes,
34+
...InstanceEditorRoutes,
35+
...DeviceEditorRoutes,
3436
{
3537
name: 'page-not-found',
3638
path: '/:pathMatch(.*)*',

frontend/src/store/modules/context/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const initialState = () => ({
22
route: null,
3-
instance: null
3+
instance: null,
4+
device: null
45
})
56

67
const meta = {
@@ -76,6 +77,7 @@ const mutations = {
7677
state.route = route
7778
},
7879
SET_INSTANCE (state, instance) { state.instance = instance },
80+
SET_DEVICE (state, device) { state.device = device },
7981
CLEAR_INSTANCE (state) { state.instance = null }
8082
}
8183

@@ -84,6 +86,7 @@ const actions = {
8486
commit('UPDATE_ROUTE', route)
8587
},
8688
setInstance ({ commit }, instance) { commit('SET_INSTANCE', instance) },
89+
setDevice ({ commit }, device) { commit('SET_DEVICE', device) },
8790
clearInstance ({ commit }) { commit('CLEAR_INSTANCE') }
8891
}
8992

frontend/src/store/modules/product/assistant/index.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,26 @@ const getters = {
8585
immersiveInstance: (state, getters, rootState) => {
8686
return rootState.context.instance
8787
},
88+
immersiveDevice: (state, getters, rootState) => {
89+
return rootState.context.device
90+
},
8891
hasUserSelection: (state) => {
8992
return state.selectedNodes.length
93+
},
94+
allowedInboundOrigins: (state, getters) => {
95+
const allowedOrigins = [window.origin]
96+
97+
if (getters.immersiveInstance?.url) {
98+
allowedOrigins.push(getters.immersiveInstance.url)
99+
}
100+
101+
if (getters.immersiveDevice?.editor?.url) {
102+
// todo this might not be needed because it's just the path to the editor tunnel, not an actual origin
103+
// and the only origin we might receive messages is the current window origin
104+
allowedOrigins.push(getters.immersiveDevice.editor.url)
105+
}
106+
107+
return allowedOrigins
90108
}
91109
}
92110

@@ -131,10 +149,11 @@ const mutations = {
131149

132150
const actions = {
133151
async handleMessage ({ commit, getters, dispatch }, payload) {
134-
if (payload.origin !== getters.immersiveInstance.url) {
152+
if (!getters.allowedInboundOrigins.includes(payload.origin)) {
135153
console.warn('Received message from unknown origin. Ignoring.')
136154
return
137155
}
156+
138157
switch (true) {
139158
case payload.data.type === 'assistant-ready':
140159
commit('SET_VERSION', payload.data.version)

0 commit comments

Comments
 (0)