M src/components/host/host-bulk-download-panel.vue => src/components/host/host-bulk-download-panel.vue +26 -104
@@ 39,7 39,7 @@
<host-pick-field
v-else
ref="hostInput"
- v-model="hostId"
+ v-model="hubHostId"
label="Hub Host"
required
/>
@@ 49,8 49,8 @@
<interface-pick-field
v-else
ref="interfaceInput"
- v-model="interfaceId"
- :host-id="hostId"
+ v-model="hubInterfaceId"
+ :host-id="hubHostId"
label="Hub Interface"
required
/>
@@ 78,8 78,8 @@
</app-form>
<host-bulk-download-modal
v-model="submitting"
- :hosts="hosts"
- :interface-model="iface"
+ :hosts="hostList"
+ :interface-model="hubInterface"
:download-files="downloadFiles"
:invalid-chars="invalidChars"
/>
@@ 87,7 87,8 @@
</template>
<script setup>
-import { computed, inject, onMounted, ref, unref, watch } from 'vue'
+import { computed, onMounted, ref, unref, watch } from 'vue'
+import { useHubSelection } from '@/mixins/host/use-host-bulk-download'
import { normalizeModel } from '@/utils/model'
const props = defineProps({
@@ 100,33 101,22 @@ const props = defineProps({
})
const emit = defineEmits(['cancel'])
-const $axiosApi = inject('$axiosApi')
-
-/** Selected host ID. */
-const hostId = ref('')
-const interfaceId = ref('')
-/** Selected interface model. */
-const iface = ref(props.interfaceModel)
-
-/** Raw CSV input text. */
-const csv = ref('')
-/** 'Download' radio button value. */
-const downloadFiles = ref('agent')
-/** 'Invalid name' radio button value. */
-const invalidChars = ref('skip')
-/** 'Source' radio button value. */
-const source = ref('csv')
-/** True if submitting form. */
-const submitting = ref(false)
-
-/** Placeholder text for CSV input. */
-const csvPlaceholder = ref(
- `
-Alice's Laptop
-Bob's Phone
-Carla's Workstation
-`.trim(),
-)
+const {
+ hubHostId,
+ hubInterfaceId,
+ hubInterface,
+ hostList,
+ source,
+ csv,
+ csvPlaceholder,
+ downloadFiles,
+ invalidChars,
+ submitting,
+ submit,
+ selectDefaultHubHost,
+ selectDefaultHubInterface,
+ loadHubInterface,
+} = useHubSelection(props)
const csvInput = ref()
const hostInput = ref()
@@ 135,71 125,9 @@ const inputRefs = computed(() => {
return unref(source) === 'csv' ? [csvInput] : [hostInput, interfaceInput]
})
-/** Parsed list of host names. */
-const hosts = computed(() => {
- if (unref(source) !== 'csv') return []
- return unref(csv)
- .split(/\r?\n/)
- .map((x) => x.trim())
- .filter((x) => x)
-})
-
-watch(hostId, selectDefaultInterface)
-watch(interfaceId, loadInterface)
-onMounted(selectDefaultHost)
-
-/**
- * Selects the default hub host on load.
- */
-async function selectDefaultHost() {
- if (props.hostModel.id) return
-
- const { data } = await $axiosApi.get('/hosts', {
- params: { q: '"hub"', field: 'external' },
- })
- const host = normalizeModel(data)
- if (host.id) hostId.value = host.id
-}
-
-/**
- * Selects the default interface for the specified host.
- * @param {string} id Host pub ID.
- * @param {string} old Previous ID value.
- */
-async function selectDefaultInterface(id, old) {
- if (id === old) return
- if (!id) {
- interfaceId.value = ''
- return
- }
- const { data } = await $axiosApi.get(`/hosts/${id}/interfaces/all`)
- const iface = normalizeModel(data)
- interfaceId.value = iface.id || ''
-}
-
-/**
- * Loads the specified interface model.
- * @param {string} id Interface pub ID.
- * @param {string} old Previous ID value.
- */
-async function loadInterface(id, old) {
- if (id === old) return
- if (!id) {
- iface.value = normalizeModel()
- return
- }
- const { data } = await $axiosApi.get(`/interfaces/${id}`, {
- params: { included: 'peer' },
- })
- iface.value = normalizeModel(data)
-}
-
-/**
- * Submits the upload form, starting the upload process.
- */
-function submit() {
- submitting.value = true
-}
+watch(hubHostId, selectDefaultHubInterface)
+watch(hubInterfaceId, loadHubInterface)
+onMounted(selectDefaultHubHost)
/**
* Cancels the upload form.
@@ 208,9 136,3 @@ function cancel() {
emit('cancel')
}
</script>
-
-<style scoped lang="scss">
-.preview-table {
- padding-top: 1rem;
-}
-</style>
M src/components/host/host-bulk-upload-panel.vue => src/components/host/host-bulk-upload-panel.vue +32 -155
@@ 5,7 5,7 @@
:cancel="cancel"
:submitting="submitting"
:submit="submit"
- :submit-disabled="!hasPlan || !!interfaceErrors.length"
+ :submit-disabled="!hasPlan || !!hubInterfaceErrors.length"
submit-label="Add"
>
<app-message :active="!hasPlan" variant="warning">
@@ 25,14 25,14 @@
autofocus
/>
</o-field>
- <app-message :active="!!interfaceErrors.length" variant="warning">
+ <app-message :active="!!hubInterfaceErrors.length" variant="warning">
<div class="content">
<div>
The selected interface does not appear to be a hub. It should have
the following defaults set:
</div>
<ul>
- <li v-for="item in interfaceErrors" :key="item">{{ item }}</li>
+ <li v-for="item in hubInterfaceErrors" :key="item">{{ item }}</li>
</ul>
</div>
</app-message>
@@ 42,7 42,7 @@
<host-pick-field
v-else
ref="hostInput"
- v-model="hostId"
+ v-model="hubHostId"
label="Hub Host"
required
/>
@@ 52,8 52,8 @@
<interface-pick-field
v-else
ref="interfaceInput"
- v-model="interfaceId"
- :host-id="hostId"
+ v-model="hubInterfaceId"
+ :host-id="hubHostId"
label="Hub Interface"
required
/>
@@ 110,8 110,8 @@
</app-form>
<host-bulk-upload-modal
v-model="submitting"
- :hosts="hosts"
- :interface-model="iface"
+ :hosts="hostList"
+ :interface-model="hubInterface"
:download-files="downloadFiles"
:invalid-chars="invalidChars"
:existing-hosts="existingHosts"
@@ 120,11 120,11 @@
</template>
<script setup>
-import { computed, inject, onMounted, ref, unref, watch } from 'vue'
+import { computed, onMounted, ref, watch } from 'vue'
import { useInputRefs } from '@/mixins/app/use-input-refs'
+import { useHubSelection } from '@/mixins/host/use-host-bulk-download'
import { useFeaturesStore } from '@/stores/features'
import { env } from '@/utils/env'
-import { splitAddress } from '@/utils/ip'
import { normalizeModel } from '@/utils/model'
const props = defineProps({
@@ 137,28 137,27 @@ const props = defineProps({
})
const emit = defineEmits(['cancel'])
-const $axiosApi = inject('$axiosApi')
const { inputRefs } = useInputRefs(['csvInput', 'hostInput', 'interfaceInput'])
const features = useFeaturesStore()
-/** Selected host ID. */
-const hostId = ref('')
-const interfaceId = ref('')
-/** Selected interface model. */
-const iface = ref(props.interfaceModel)
-/** List of error messages to display about selected interface. */
-const interfaceErrors = ref([])
-
-/** Raw CSV input text. */
-const csv = ref('')
-/** 'Download' radio button value. */
-const downloadFiles = ref('agent')
-/** 'Invalid name' radio button value. */
-const invalidChars = ref('skip')
-/** 'Existing host or peer' radio button value. */
-const existingHosts = ref('skip')
-/** True if submitting form. */
-const submitting = ref(false)
+const {
+ hubHostId,
+ hubInterfaceId,
+ hubInterface,
+ hubInterfaceErrors,
+ hostList,
+ csv,
+ csvPlaceholder,
+ downloadFiles,
+ invalidChars,
+ existingHosts,
+ submitting,
+ submit,
+ selectDefaultHubHost,
+ selectDefaultHubInterface,
+ loadHubInterface,
+ validateHubInterface,
+} = useHubSelection(props)
/** Help message for CSV input. */
const csvMessage = ref(`
@@ 166,132 165,16 @@ List of hosts to add (one host per line).
A new connection to the existing hub host will be created for each listed host,
using the hub interface's defaults.
`)
-/** Placeholder text for CSV input. */
-const csvPlaceholder = ref(
- `
-Alice's Laptop
-Bob's Phone
-Carla's Workstation
-`.trim(),
-)
-/** Parsed list of host names. */
-const hosts = computed(() => {
- return unref(csv)
- .split(/\r?\n/)
- .map((x) => x.trim())
- .filter((x) => x)
-})
/** True if user on plan with unlimited hosts. */
const hasPlan = computed(() => !features.hostLimit || features.hostLimit >= 100)
/** URL to pricing page. */
const pricingPage = computed(() => `${env.wwwUrl}/pricing/`)
-watch(hostId, selectDefaultInterface)
-watch(interfaceId, loadInterface)
-watch(iface, validateInterface)
-onMounted(selectDefaultHost)
-
-/**
- * Selects the default hub host on load.
- */
-async function selectDefaultHost() {
- if (props.hostModel.id) return
-
- const { data } = await $axiosApi.get('/hosts', {
- params: { q: '"hub"', field: 'external' },
- })
- const host = normalizeModel(data)
- if (host.id) hostId.value = host.id
-}
-
-/**
- * Selects the default interface for the specified host.
- * @param {string} id Host pub ID.
- * @param {string} old Previous ID value.
- */
-async function selectDefaultInterface(id, old) {
- if (id === old) return
- if (!id) {
- interfaceId.value = ''
- return
- }
- const { data } = await $axiosApi.get(`/hosts/${id}/interfaces/all`)
- const iface = normalizeModel(data)
- interfaceId.value = iface.id || ''
-}
-
-/**
- * Loads the specified interface model.
- * @param {string} id Interface pub ID.
- * @param {string} old Previous ID value.
- */
-async function loadInterface(id, old) {
- if (id === old) return
- if (!id) {
- iface.value = normalizeModel()
- return
- }
- const { data } = await $axiosApi.get(`/interfaces/${id}`, {
- params: { included: 'peer' },
- })
- iface.value = normalizeModel(data)
-}
-
-/**
- * Validates the currently selected interface, updating the errors list.
- * @param iface Interface model.
- */
-async function validateInterface(iface) {
- const { id } = iface.rel('peer')
- if (!id) {
- interfaceErrors.value = []
- return
- }
-
- const { data } = await $axiosApi.get(`/peers/${id}`, {
- params: { included: 'defaults' },
- })
- const model = normalizeModel(data)
- const endpoint = model.rel('peer_endpoint_default').attr || {}
- const routing = model.rel('peer_endpoint_routing_default').attr || {}
- const errors = []
-
- const hubTypes = ['hub-and-spoke', 'internet-to-point', 'site-to-point']
- if (!hubTypes.includes(endpoint.wizard)) {
- errors.push(
- 'Type: either Hub (in Hub-and-Spoke), Internet (in Point-to-Internet), or Site (in Point-to-Site)',
- )
- }
-
- if (!endpoint.allowed_ips || !endpoint.allowed_ips.length) {
- errors.push('Allowed IPs')
- }
- if (!(endpoint.hostname || endpoint.ip)) errors.push('Hostname')
- if (!endpoint.port) errors.push('Port')
-
- const bigSubnets =
- routing.subnet &&
- routing.subnet.length &&
- routing.subnet.every((x) => {
- const { mask, v4, v6 } = splitAddress(x)
- return (v4 && mask < 30) || (v6 && mask < 126)
- })
- if (!bigSubnets) {
- errors.push(
- 'Network Addresses: each with a mask less than /30 (for IPv4, or /126 for IPv6)',
- )
- }
-
- interfaceErrors.value = errors
-}
-
-/**
- * Submits the upload form, starting the upload process.
- */
-function submit() {
- submitting.value = true
-}
+watch(hubHostId, selectDefaultHubInterface)
+watch(hubInterfaceId, loadHubInterface)
+watch(hubInterface, validateHubInterface)
+onMounted(selectDefaultHubHost)
/**
* Cancels the upload form.
@@ 300,9 183,3 @@ function cancel() {
emit('cancel')
}
</script>
-
-<style scoped lang="scss">
-.preview-table {
- padding-top: 1rem;
-}
-</style>
A src/mixins/host/use-host-bulk-download.js => src/mixins/host/use-host-bulk-download.js +191 -0
@@ 0,0 1,191 @@
+import { computed, inject, ref, unref } from 'vue'
+import { splitAddress } from '@/utils/ip'
+import { normalizeModel } from '@/utils/model'
+
+/**
+ * Selects the default hub host on load.
+ * @param hubHost Ref to host model.
+ * @param hubHostId Ref to host pub ID.
+ * @param $axiosApi Axios API instance.
+ */
+async function selectDefaultHubHost(hubHost, hubHostId, $axiosApi) {
+ if (hubHost.value.id) return
+
+ const { data } = await $axiosApi.get('/hosts', {
+ params: { q: '"hub"', field: 'external' },
+ })
+ const host = normalizeModel(data)
+ if (host.id) hubHostId.value = host.id
+}
+
+/**
+ * Selects the default interface for the specified host.
+ * @param {string} id Host pub ID.
+ * @param {string} old Previous ID value.
+ * @param hubInterfaceId Ref to interface pub ID.
+ * @param $axiosApi Axios API instance.
+ */
+async function selectDefaultHubInterface(id, old, hubInterfaceId, $axiosApi) {
+ if (id === old) return
+ if (!id) {
+ hubInterfaceId.value = ''
+ return
+ }
+ const { data } = await $axiosApi.get(`/hosts/${id}/interfaces/all`)
+ const iface = normalizeModel(data)
+ hubInterfaceId.value = iface.id || ''
+}
+
+/**
+ * Loads the specified interface model.
+ * @param {string} id Interface pub ID.
+ * @param {string} old Previous ID value.
+ * @param hubInterface Ref to interface model.
+ * @param $axiosApi Axios API instance.
+ */
+async function loadHubInterface(id, old, hubInterface, $axiosApi) {
+ if (id === old) return
+ if (!id) {
+ hubInterface.value = normalizeModel()
+ return
+ }
+ const { data } = await $axiosApi.get(`/interfaces/${id}`, {
+ params: { included: 'peer' },
+ })
+ hubInterface.value = normalizeModel(data)
+}
+
+/**
+ * Validates the currently selected interface, updating the errors list.
+ * @param hubInterface Ref to interface model.
+ * @param hubInterfaceErrors Ref to errors list.
+ * @param $axiosApi Axios API instance.
+ */
+async function validateHubInterface(
+ hubInterface,
+ hubInterfaceErrors,
+ $axiosApi,
+) {
+ const { id } = hubInterface.value.rel('peer')
+ if (!id) {
+ hubInterfaceErrors.value = []
+ return
+ }
+
+ const { data } = await $axiosApi.get(`/peers/${id}`, {
+ params: { included: 'defaults' },
+ })
+ const model = normalizeModel(data)
+ const endpoint = model.rel('peer_endpoint_default').attr || {}
+ const routing = model.rel('peer_endpoint_routing_default').attr || {}
+ const errors = []
+
+ const hubTypes = ['hub-and-spoke', 'internet-to-point', 'site-to-point']
+ if (!hubTypes.includes(endpoint.wizard)) {
+ errors.push(
+ 'Type: either Hub (in Hub-and-Spoke), Internet (in Point-to-Internet), or Site (in Point-to-Site)',
+ )
+ }
+
+ if (!endpoint.allowed_ips || !endpoint.allowed_ips.length) {
+ errors.push('Allowed IPs')
+ }
+ if (!(endpoint.hostname || endpoint.ip)) errors.push('Hostname')
+ if (!endpoint.port) errors.push('Port')
+
+ const bigSubnets =
+ routing.subnet &&
+ routing.subnet.length &&
+ routing.subnet.every((x) => {
+ const { mask, v4, v6 } = splitAddress(x)
+ return (v4 && mask < 30) || (v6 && mask < 126)
+ })
+ if (!bigSubnets) {
+ errors.push(
+ 'Network Addresses: each with a mask less than /30 (for IPv4, or /126 for IPv6)',
+ )
+ }
+
+ hubInterfaceErrors.value = errors
+}
+
+/**
+ * Refs for hub selection.
+ *
+ * @param props Props instance.
+ * @return Refs.
+ */
+export function useHubSelection(props) {
+ const $axiosApi = inject('$axiosApi')
+
+ /** Selected host ID. */
+ const hubHostId = ref('')
+ /** Selected host model. */
+ const hubHost = ref(props.hostModel)
+ /** Selected interface ID. */
+ const hubInterfaceId = ref('')
+ /** Selected interface model. */
+ const hubInterface = ref(props.interfaceModel)
+ /** List of error messages to display about selected interface. */
+ const hubInterfaceErrors = ref([])
+
+ /** 'Source' radio button value. */
+ const source = ref('csv')
+ /** Raw CSV input text. */
+ const csv = ref('')
+ /** 'Download' radio button value. */
+ const downloadFiles = ref('agent')
+ /** 'Invalid name' radio button value. */
+ const invalidChars = ref('skip')
+ /** 'Existing host or peer' radio button value. */
+ const existingHosts = ref('skip')
+ /** True if submitting form. */
+ const submitting = ref(false)
+
+ /** Placeholder text for CSV input. */
+ const csvPlaceholder = ref(
+ `
+Alice's Laptop
+Bob's Phone
+Carla's Workstation
+ `.trim(),
+ )
+
+ /** Parsed list of host names. */
+ const hostList = computed(() => {
+ if (unref(source) !== 'csv') return []
+ return unref(csv)
+ .split(/\r?\n/)
+ .map((x) => x.trim())
+ .filter((x) => x)
+ })
+
+ return {
+ hubHostId,
+ hubHost,
+ hubInterfaceId,
+ hubInterface,
+ hubInterfaceErrors,
+ hostList,
+ source,
+ csv,
+ csvPlaceholder,
+ downloadFiles,
+ invalidChars,
+ existingHosts,
+ submitting,
+ submit: () => (submitting.value = true),
+ selectDefaultHubHost: async () => {
+ return selectDefaultHubHost(hubHost, hubHostId, $axiosApi)
+ },
+ selectDefaultHubInterface: async (id, old) => {
+ return selectDefaultHubInterface(id, old, hubInterfaceId, $axiosApi)
+ },
+ loadHubInterface: async (id, old) => {
+ return loadHubInterface(id, old, hubInterface, $axiosApi)
+ },
+ validateHubInterface: async () => {
+ return validateHubInterface(hubInterface, hubInterfaceErrors, $axiosApi)
+ },
+ }
+}