thelounge/client/components/NetworkForm.vue

573 lines
14 KiB
Vue
Raw Normal View History

<template>
2019-08-03 19:03:45 +00:00
<div id="connect" class="window" role="tabpanel" aria-label="Connect">
<div class="header">
<SidebarToggle />
</div>
2019-08-03 19:03:45 +00:00
<form class="container" method="post" action="" @submit.prevent="onSubmit">
2019-12-12 11:20:07 +00:00
<h1 class="title">
<template v-if="defaults.uuid">
<input v-model="defaults.uuid" type="hidden" name="uuid" />
2019-12-12 11:20:07 +00:00
Edit {{ defaults.name }}
</template>
2019-12-12 11:20:07 +00:00
<template v-else>
Connect
<template
v-if="config?.lockNetwork && store?.state.serverConfiguration?.public"
>
to {{ defaults.name }}
</template>
2019-12-12 11:20:07 +00:00
</template>
</h1>
<template v-if="!config?.lockNetwork">
2019-12-12 11:20:07 +00:00
<h2>Network settings</h2>
<div class="connect-row">
<label for="connect:name">Name</label>
<input
2019-12-12 11:20:07 +00:00
id="connect:name"
v-model.trim="defaults.name"
2019-12-12 11:20:07 +00:00
class="input"
name="name"
maxlength="100"
2019-08-03 19:03:45 +00:00
/>
</div>
2019-12-12 11:20:07 +00:00
<div class="connect-row">
<label for="connect:host">Server</label>
<div class="input-wrap">
<input
2019-12-12 11:20:07 +00:00
id="connect:host"
v-model.trim="defaults.host"
2019-12-12 11:20:07 +00:00
class="input"
name="host"
aria-label="Server address"
maxlength="255"
required
2019-08-03 19:03:45 +00:00
/>
2019-12-12 11:20:07 +00:00
<span id="connect:portseparator">:</span>
<input
2019-12-12 11:20:07 +00:00
id="connect:port"
v-model="defaults.port"
class="input"
2019-12-12 11:20:07 +00:00
type="number"
min="1"
max="65535"
name="port"
aria-label="Server port"
2019-08-03 19:03:45 +00:00
/>
2019-12-12 11:20:07 +00:00
</div>
</div>
2020-03-31 08:02:18 +00:00
<div class="connect-row">
<label for="connect:password">Password</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:password"
v-model="defaults.password"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
placeholder="Server password (optional)"
name="password"
maxlength="300"
/>
</RevealPassword>
</div>
2019-12-12 11:20:07 +00:00
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input
v-model="defaults.tls"
2019-12-12 11:20:07 +00:00
type="checkbox"
name="tls"
:disabled="defaults.hasSTSPolicy"
2019-12-12 11:20:07 +00:00
/>
Use secure connection (TLS)
<span
v-if="defaults.hasSTSPolicy"
class="tooltipped tooltipped-n tooltipped-no-delay"
aria-label="This network has a strict transport security policy, you will be unable to disable TLS"
>🔒 STS</span
>
2019-12-12 11:20:07 +00:00
</label>
<label class="tls">
<input
v-model="defaults.rejectUnauthorized"
2019-12-12 11:20:07 +00:00
type="checkbox"
name="rejectUnauthorized"
/>
Only allow trusted certificates
</label>
</div>
</div>
<h2>Proxy Settings</h2>
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label for="connect:proxyEnabled">
<input
2021-06-15 17:55:54 +00:00
id="connect:proxyEnabled"
v-model="defaults.proxyEnabled"
type="checkbox"
name="proxyEnabled"
/>
Enable Proxy
</label>
</div>
</div>
<template v-if="defaults.proxyEnabled">
<div class="connect-row">
<label for="connect:proxyHost">SOCKS Address</label>
<div class="input-wrap">
<input
id="connect:proxyHost"
v-model.trim="defaults.proxyHost"
class="input"
name="proxyHost"
aria-label="Proxy host"
maxlength="255"
/>
<span id="connect:proxyPortSeparator">:</span>
<input
id="connect:proxyPort"
v-model="defaults.proxyPort"
class="input"
type="number"
min="1"
max="65535"
name="proxyPort"
aria-label="SOCKS port"
/>
</div>
</div>
<div class="connect-row">
<label for="connect:proxyUsername">Proxy username</label>
<input
id="connect:proxyUsername"
ref="proxyUsernameInput"
v-model.trim="defaults.proxyUsername"
class="input username"
name="proxyUsername"
maxlength="100"
placeholder="Proxy username"
/>
</div>
<div class="connect-row">
<label for="connect:proxyPassword">Proxy password</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:proxyPassword"
ref="proxyPassword"
v-model="defaults.proxyPassword"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
placeholder="Proxy password"
2021-10-22 23:11:08 +00:00
name="proxyPassword"
maxlength="300"
/>
</RevealPassword>
</div>
</template>
2019-12-12 11:20:07 +00:00
</template>
<template v-else-if="config.lockNetwork && !store.state.serverConfiguration?.public">
<h2>Network settings</h2>
<div class="connect-row">
<label for="connect:name">Name</label>
<input
id="connect:name"
v-model.trim="defaults.name"
class="input"
name="name"
maxlength="100"
/>
</div>
<div class="connect-row">
<label for="connect:password">Password</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:password"
v-model="defaults.password"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
placeholder="Server password (optional)"
name="password"
maxlength="300"
/>
</RevealPassword>
</div>
</template>
2019-12-12 11:20:07 +00:00
<h2>User preferences</h2>
<div class="connect-row">
<label for="connect:nick">Nick</label>
<input
id="connect:nick"
v-model="defaults.nick"
2019-12-12 11:20:07 +00:00
class="input nick"
name="nick"
pattern="[^\s:!@]+"
2019-12-12 11:20:07 +00:00
maxlength="100"
required
@input="onNickChanged"
/>
</div>
<template v-if="!config?.useHexIp">
2019-12-12 11:20:07 +00:00
<div class="connect-row">
<label for="connect:username">Username</label>
<input
2019-12-12 11:20:07 +00:00
id="connect:username"
ref="usernameInput"
v-model.trim="defaults.username"
2019-12-12 11:20:07 +00:00
class="input username"
name="username"
maxlength="100"
2019-08-03 19:03:45 +00:00
/>
</div>
2019-12-12 11:20:07 +00:00
</template>
<div class="connect-row">
<label for="connect:realname">Real name</label>
<input
id="connect:realname"
v-model.trim="defaults.realname"
2019-12-12 11:20:07 +00:00
class="input"
name="realname"
maxlength="300"
2019-12-12 11:20:07 +00:00
/>
</div>
2020-11-25 23:37:46 +00:00
<div class="connect-row">
<label for="connect:leaveMessage">Leave message</label>
2020-11-25 23:37:46 +00:00
<input
id="connect:leaveMessage"
v-model.trim="defaults.leaveMessage"
autocomplete="off"
2020-11-25 23:37:46 +00:00
class="input"
name="leaveMessage"
2021-02-12 22:07:48 +00:00
placeholder="The Lounge - https://thelounge.chat"
2020-11-25 23:37:46 +00:00
/>
</div>
<template v-if="defaults.uuid && !store.state.serverConfiguration?.public">
2019-12-12 11:20:07 +00:00
<div class="connect-row">
<label for="connect:commands">
Commands
<span
class="tooltipped tooltipped-ne tooltipped-no-delay"
aria-label="One /command per line.
Each command will be executed in
the server tab on new connection"
>
<button class="extra-help" />
</span>
</label>
2019-12-12 11:20:07 +00:00
<textarea
id="connect:commands"
ref="commandsInput"
autocomplete="off"
:value="defaults.commands ? defaults.commands.join('\n') : ''"
2019-12-12 11:20:07 +00:00
class="input"
name="commands"
@input="resizeCommandsInput"
2019-12-12 11:20:07 +00:00
/>
</div>
</template>
<template v-else-if="!defaults.uuid">
2019-12-12 11:20:07 +00:00
<div class="connect-row">
<label for="connect:channels">Channels</label>
<input
id="connect:channels"
v-model.trim="defaults.join"
class="input"
name="join"
/>
2019-12-12 11:20:07 +00:00
</div>
2020-03-31 08:02:18 +00:00
</template>
<template v-if="store.state.serverConfiguration?.public">
<template v-if="config?.lockNetwork">
2020-03-31 08:02:18 +00:00
<div class="connect-row">
<label></label>
<div class="input-wrap">
<label class="tls">
<input v-model="displayPasswordField" type="checkbox" />
I have a password
</label>
</div>
</div>
<div v-if="displayPasswordField" class="connect-row">
<label for="connect:password">Password</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:password"
ref="publicPassword"
2020-03-31 08:02:18 +00:00
v-model="defaults.password"
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
placeholder="Server password (optional)"
name="password"
maxlength="300"
/>
</RevealPassword>
</div>
</template>
</template>
<template v-else>
<h2 id="label-auth">Authentication</h2>
<div class="connect-row connect-auth" role="group" aria-labelledby="label-auth">
<label class="opt">
<input
:checked="!defaults.sasl"
type="radio"
name="sasl"
value=""
@change="setSaslAuth('')"
/>
No authentication
</label>
<label class="opt">
<input
:checked="defaults.sasl === 'plain'"
type="radio"
name="sasl"
value="plain"
@change="setSaslAuth('plain')"
/>
Username + password (SASL PLAIN)
</label>
<label
v-if="!store.state.serverConfiguration?.public && defaults.tls"
2020-03-31 08:02:18 +00:00
class="opt"
>
<input
:checked="defaults.sasl === 'external'"
type="radio"
name="sasl"
value="external"
@change="setSaslAuth('external')"
/>
Client certificate (SASL EXTERNAL)
</label>
</div>
<template v-if="defaults.sasl === 'plain'">
<div class="connect-row">
<label for="connect:username">Account</label>
<input
id="connect:saslAccount"
v-model.trim="defaults.saslAccount"
2020-03-31 08:02:18 +00:00
class="input"
name="saslAccount"
maxlength="100"
required
/>
</div>
<div class="connect-row">
<label for="connect:password">Password</label>
<RevealPassword
v-slot:default="slotProps"
class="input-wrap password-container"
>
<input
id="connect:saslPassword"
v-model="defaults.saslPassword"
2020-03-31 08:02:18 +00:00
class="input"
:type="slotProps.isVisible ? 'text' : 'password'"
name="saslPassword"
maxlength="300"
required
/>
</RevealPassword>
</div>
</template>
<div v-else-if="defaults.sasl === 'external'" class="connect-sasl-external">
2020-08-25 09:49:53 +00:00
<p>The Lounge automatically generates and manages the client certificate.</p>
2020-03-31 08:02:18 +00:00
<p>
On the IRC server, you will need to tell the services to attach the
certificate fingerprint (certfp) to your account, for example:
</p>
<pre><code>/msg NickServ CERT ADD</code></pre>
2019-12-12 11:20:07 +00:00
</div>
</template>
2020-03-31 08:02:18 +00:00
<div>
<button type="submit" class="btn" :disabled="disabled ? true : false">
<template v-if="defaults.uuid">Save network</template>
<template v-else>Connect</template>
</button>
</div>
</form>
</div>
</template>
2020-03-31 08:02:18 +00:00
<style>
#connect .connect-auth {
display: block;
margin-bottom: 10px;
}
#connect .connect-auth .opt {
display: block;
width: 100%;
}
#connect .connect-auth input {
margin: 3px 10px 0 0;
}
#connect .connect-sasl-external {
padding: 10px;
border-radius: 2px;
background-color: #d9edf7;
color: #31708f;
}
#connect .connect-sasl-external pre {
margin: 0;
user-select: text;
}
</style>
<script lang="ts">
import RevealPassword from "./RevealPassword.vue";
import SidebarToggle from "./SidebarToggle.vue";
import {defineComponent, nextTick, PropType, ref, watch} from "vue";
import {useStore} from "../js/store";
import {ClientNetwork} from "../js/types";
export type NetworkFormDefaults = Partial<ClientNetwork> & {
join?: string;
};
export default defineComponent({
name: "NetworkForm",
components: {
RevealPassword,
SidebarToggle,
},
props: {
handleSubmit: {
type: Function as PropType<(network: ClientNetwork) => void>,
required: true,
},
defaults: {
type: Object as PropType<NetworkFormDefaults>,
required: true,
},
disabled: Boolean,
},
setup(props) {
const store = useStore();
const config = ref(store.state.serverConfiguration);
const previousUsername = ref(props.defaults?.username);
const displayPasswordField = ref(false);
const publicPassword = ref<HTMLInputElement | null>(null);
watch(displayPasswordField, (newValue) => {
if (newValue) {
void nextTick(() => {
publicPassword.value?.focus();
});
}
});
const commandsInput = ref<HTMLInputElement | null>(null);
const resizeCommandsInput = () => {
if (!commandsInput.value) {
2020-01-03 17:51:38 +00:00
return;
}
// Reset height first so it can down size
commandsInput.value.style.height = "";
// 2 pixels to account for the border
commandsInput.value.style.height = `${Math.ceil(
commandsInput.value.scrollHeight + 2
)}px`;
};
watch(
// eslint-disable-next-line
() => props.defaults?.commands,
() => {
void nextTick(() => {
resizeCommandsInput();
});
}
);
watch(
// eslint-disable-next-line
() => props.defaults?.tls,
(isSecureChecked) => {
const ports = [6667, 6697];
const newPort = isSecureChecked ? 0 : 1;
// If you disable TLS and current port is 6697,
// set it to 6667, and vice versa
if (props.defaults?.port === ports[newPort]) {
props.defaults.port = ports[1 - newPort];
}
}
);
const setSaslAuth = (type: string) => {
if (props.defaults) {
props.defaults.sasl = type;
}
};
const usernameInput = ref<HTMLInputElement | null>(null);
const onNickChanged = (event: Event) => {
if (!usernameInput.value) {
return;
}
const usernameRef = usernameInput.value;
if (!usernameRef.value || usernameRef.value === previousUsername.value) {
usernameRef.value = (event.target as HTMLInputElement)?.value;
}
previousUsername.value = (event.target as HTMLInputElement)?.value;
};
const onSubmit = (event: Event) => {
const formData = new FormData(event.target as HTMLFormElement);
const data: Partial<ClientNetwork> = {};
formData.forEach((value, key) => {
data[key] = value;
});
props.handleSubmit(data as ClientNetwork);
};
return {
store,
config,
displayPasswordField,
publicPassword,
commandsInput,
resizeCommandsInput,
setSaslAuth,
usernameInput,
onNickChanged,
onSubmit,
};
},
});
</script>