~arx10/procustodibus-app

fa682724de6765f62dd0246131729d6f71228239 — Justin Ludwig a month ago 5457706
impl download option from host bulk upload page
1 files changed, 120 insertions(+), 4 deletions(-)

M src/components/host/host-bulk-upload-modal.vue
M src/components/host/host-bulk-upload-modal.vue => src/components/host/host-bulk-upload-modal.vue +120 -4
@@ 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.
 */