Gateway setup shows a `continuum-proxy enrol` command with a one-time code; the manual settings stay behind "Set it up by hand". New members section for inviting by email, plus an /invite page to accept, and the login page honours a safe `next` redirect.
165 lines
5.0 KiB
Vue
165 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from "vue";
|
|
import type { AppFarm, EnrolmentCode } from "../types";
|
|
|
|
const props = defineProps<{
|
|
farm: AppFarm;
|
|
/** null until the first answer arrives. */
|
|
gatewayOnline: boolean | null;
|
|
/** Where the gateway connects; shown in its settings so there is one place to copy from. */
|
|
uplinkUrl: string;
|
|
/** Mints a new proxy token. Rejects with a readable message. */
|
|
mint: () => Promise<string>;
|
|
/** Makes a one-time enrolment code. Rejects with a readable message. */
|
|
enrol: () => Promise<EnrolmentCode>;
|
|
/** The API's address as a gateway reaches it, for the enrol command. */
|
|
apiUrl: string;
|
|
}>();
|
|
|
|
const enrolment = ref<EnrolmentCode | null>(null);
|
|
const enrolBusy = ref(false);
|
|
const enrolCopied = ref(false);
|
|
const command = computed(() => `continuum-proxy enrol ${props.apiUrl} ${enrolment.value?.code ?? ""}`);
|
|
|
|
async function makeCode() {
|
|
enrolBusy.value = true;
|
|
failure.value = "";
|
|
try {
|
|
enrolment.value = await props.enrol();
|
|
enrolCopied.value = false;
|
|
} catch (err) {
|
|
failure.value = err instanceof Error ? err.message : String(err);
|
|
} finally {
|
|
enrolBusy.value = false;
|
|
}
|
|
}
|
|
|
|
async function copyCommand() {
|
|
try {
|
|
await navigator.clipboard.writeText(command.value);
|
|
enrolCopied.value = true;
|
|
} catch {
|
|
enrolCopied.value = false;
|
|
}
|
|
}
|
|
|
|
const token = ref("");
|
|
const confirming = ref(false);
|
|
const busy = ref(false);
|
|
const failure = ref("");
|
|
const copied = ref(false);
|
|
|
|
// Minting replaces the old token, which disconnects a gateway that already
|
|
// has it at its next reconnect - so a farm that has a gateway online asks
|
|
// first, and a farm that has never had one does not.
|
|
async function run() {
|
|
busy.value = true;
|
|
failure.value = "";
|
|
try {
|
|
token.value = await props.mint();
|
|
confirming.value = false;
|
|
} catch (err) {
|
|
failure.value = err instanceof Error ? err.message : String(err);
|
|
} finally {
|
|
busy.value = false;
|
|
}
|
|
}
|
|
|
|
function start() {
|
|
if (props.gatewayOnline) confirming.value = true;
|
|
else void run();
|
|
}
|
|
|
|
const settings = computed(() =>
|
|
[
|
|
`gateway_id = "gateway-1"`,
|
|
`farm_id = "${props.farm.id}"`,
|
|
`gateway_token = "${token.value}"`,
|
|
`uplink_url = "${props.uplinkUrl}"`,
|
|
].join("\n"),
|
|
);
|
|
|
|
async function copy() {
|
|
try {
|
|
await navigator.clipboard.writeText(settings.value);
|
|
copied.value = true;
|
|
} catch {
|
|
// Clipboard access can be refused; the text is selectable on screen.
|
|
copied.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section class="gateway-setup" :aria-label="`Gateway for ${farm.name}`">
|
|
<h3>Gateway</h3>
|
|
<p v-if="gatewayOnline" class="gateway-setup__ok">A gateway is connected.</p>
|
|
<p v-else>
|
|
No gateway is connected. Install the Continuum gateway on a computer on the printers' network and give it the
|
|
settings below.
|
|
</p>
|
|
|
|
<div class="gateway-setup__enrol">
|
|
<template v-if="!enrolment">
|
|
<button type="button" :disabled="enrolBusy" @click="makeCode">Connect a gateway with a code</button>
|
|
</template>
|
|
<template v-else>
|
|
<p>
|
|
On the computer that will run the gateway, run this within 15 minutes. The code works once, and it does not
|
|
replace a gateway that is already connected.
|
|
</p>
|
|
<pre class="gateway-setup__settings">{{ command }}</pre>
|
|
<button type="button" @click="copyCommand">{{ enrolCopied ? "Copied" : "Copy" }}</button>
|
|
<button type="button" @click="enrolment = null">Done</button>
|
|
</template>
|
|
</div>
|
|
|
|
<details class="gateway-setup__manual">
|
|
<summary>Set it up by hand instead</summary>
|
|
<template v-if="!token">
|
|
<button v-if="!confirming" type="button" :disabled="busy" @click="start">
|
|
{{ gatewayOnline ? "Create a new gateway token" : "Create gateway settings" }}
|
|
</button>
|
|
<div v-else class="gateway-setup__confirm" role="alertdialog">
|
|
<p>
|
|
This replaces the current token. The connected gateway will be refused the next time it reconnects, until
|
|
you give it the new one.
|
|
</p>
|
|
<button type="button" :disabled="busy" @click="run">Replace token</button>
|
|
<button type="button" :disabled="busy" @click="confirming = false">Keep the current one</button>
|
|
</div>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<p>
|
|
Put this in the gateway's <code>continuum.toml</code>. <strong>The token is shown only now</strong> - it is not
|
|
kept anywhere you can read it back.
|
|
</p>
|
|
<pre class="gateway-setup__settings">{{ settings }}</pre>
|
|
<button type="button" @click="copy">{{ copied ? "Copied" : "Copy" }}</button>
|
|
<button type="button" @click="token = ''">Done</button>
|
|
</template>
|
|
</details>
|
|
|
|
<p v-if="failure" role="alert" class="gateway-setup__error">{{ failure }}</p>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.gateway-setup__ok {
|
|
color: #35d07f;
|
|
}
|
|
|
|
.gateway-setup__settings {
|
|
background: #12151c;
|
|
padding: 0.75rem;
|
|
border-radius: 6px;
|
|
overflow-x: auto;
|
|
user-select: all;
|
|
}
|
|
|
|
.gateway-setup__error {
|
|
color: #f2604c;
|
|
}
|
|
</style>
|