@@ 42,6 42,15 @@
<script setup>
import { computed, inject, nextTick, ref, unref, watch } from 'vue'
+import { ZipWriter } from '@zip.js/zip.js'
+import { compareDates } from '@/utils/date'
+import {
+ download,
+ generateAgentConf,
+ generateAgentSetupConf,
+ generateWgQuickConf,
+} from '@/utils/download'
+import { env } from '@/utils/env'
import {
generateNextAddress,
joinAddress,
@@ 75,6 84,11 @@ const emit = defineEmits([
const $axiosApi = inject('$axiosApi')
+/** Promise to generate zip file content. */
+let zipPromise = null
+/** Active ZipWriter object. */
+let zipWriter = null
+
/**
* List of processed host models.
* Each host model has an added `log` meta attr for list of log messages.
@@ 126,6 140,7 @@ function cancelOrCloseModal() {
async function load() {
loading.value = true
data.value = []
+ initZip()
await loadHub()
await loadAddresses()
nextTick(loadNext)
@@ 175,7 190,7 @@ async function loadNext() {
await findOrCreateHubEndpoint(host, iface, endpoint)
if (abortHost(host)) return nextHost(host)
- addToZip(host, iface)
+ await addToZip(host, iface)
nextHost(host)
}
@@ 669,19 684,80 @@ function makeSafeName(name) {
}
/**
+ * Initializes the zip file structures.
+ */
+function initZip() {
+ if (zipWriter) zipWriter.close()
+ if (props.downloadFiles === 'nothing') return
+
+ const { readable, writable } = new TransformStream()
+ zipPromise = new Response(readable).blob()
+ zipWriter = new ZipWriter(writable)
+}
+
+/**
* Adds the configuration files for specified host and interface to the zip.
* @param host Host model.
* @param iface Interface model.
*/
-function addToZip(host, _iface) {
+async function addToZip(host, iface) {
if (props.downloadFiles === 'nothing') return
- log(host, 'Zipped', 'success')
+
+ const directory = `hosts/${makeSafeName(host.attr.name)}`
+ let result = null
+
+ if (props.downloadFiles === 'wireguard') {
+ iface = await loadInterfacePrivateKey(iface.id)
+ result = await addFileToZip(
+ `${directory}/${iface.attr.name}.conf`,
+ generateWgQuickConf(iface),
+ )
+ } else {
+ const agent = await loadAgent(host.id)
+ result = await addFileToZip(
+ `${directory}/${env.confName}.conf`,
+ generateAgentConf(agent, host),
+ )
+ if (result.variant === 'success') {
+ const code = await loadCode(agent.id)
+ result = await addFileToZip(
+ `${directory}/${env.confName}-setup.conf`,
+ generateAgentSetupConf(agent, code),
+ )
+ }
+ }
+
+ log(host, result.message, result.variant)
+}
+
+/**
+ * Adds the specified content to the zip under the specified path.
+ * Returns a log entry indicating success or error.
+ * @param {string} path Path in zip (eg 'hosts/Foo Bar/agent.conf').
+ * @param {string} content File content.
+ * @return Log entry with `message` and `variant` properties.
+ */
+async function addFileToZip(path, content) {
+ try {
+ await zipWriter.add(path, new Blob([content]).stream())
+ return { message: 'Zipped', variant: 'success' }
+ } catch (e) {
+ console.error(e) // eslint-disable-line no-console
+ return { message: `Zip error: ${e}`, variant: 'danger' }
+ }
}
/**
* Completes the processing and downloads the zip file.
*/
-function downloadZip() {
+async function downloadZip() {
+ if (zipWriter) {
+ await zipWriter.close()
+ zipWriter = null
+ const zip = await zipPromise
+ if (zip.size > 100) download(zip, `${env.confName}-hosts.zip`, 'blob')
+ }
+
const message = buildDoneMessage()
toast(message, { variant: / 0 /.test(message) ? 'warning' : 'success' })
loading.value = false
@@ 689,6 765,46 @@ function downloadZip() {
}
/**
+ * Retreives the full key material for the specified interface, as well as its
+ * enpoints (from which to generate the full wg-quick conf).
+ * @param {string} interfaceId Interface pub ID.
+ * @return Model for interface.
+ */
+async function loadInterfacePrivateKey(interfaceId) {
+ const url = `/interfaces/${interfaceId}/expected-conf-with-private-key`
+ const { data } = await $axiosApi.get(url)
+ return normalizeModel(data)
+}
+
+/**
+ * Retrieves the active agent from the api.
+ * @param {string} hostId Host pub ID.
+ * @return Model for agent user.
+ */
+async function loadAgent(hostId) {
+ const { data } = await $axiosApi.get(`/hosts/${hostId}/members`)
+ const agent = normalizeModels(data)
+ .concat()
+ .sort((a, b) => compareDates(a, b, (x) => x.attr.created))
+ .find((x) => ['logger', 'supervisor'].includes(x.attr.membership))
+ if (agent) return agent
+
+ const rs = await $axiosApi.post(`/hosts/${hostId}/members/agent`)
+ return normalizeModel(rs.data)
+}
+
+/**
+ * Retrieves the active setup code from the api.
+ * @param {string} agentId Agent pub ID.
+ * @return Model for agent_setup user-code.
+ */
+async function loadCode(agentId) {
+ const url = `/users/${agentId}/credentials/signature/setup`
+ const { data } = await $axiosApi.post(url)
+ return normalizeModel(data)
+}
+
+/**
* Builds the completion message.
* @return {string} Completion message.
*/