apps/ui: Warp's design system for Vue 3 + Tailwind v4. Tokens, fonts, icons and logo come from the brand package, with a Tailwind theme on the tokens and Vue components on the brand component styles. apps/admin: the admin panel as a separate, optional service. warp-admin embeds the built Vue app and proxies only the login endpoints and /_warp/admin/v1 to the homeserver. Pages: dashboard, users, rooms, federation, registration and tokens, server notices, application services, audit log. Also: pnpm workspace, go.work entry, Dockerfile, an `admin` compose profile, Makefile targets, a CI job with a proxy smoke test, and docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.2 KiB
Vue
73 lines
2.2 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref } from 'vue'
|
||
import { WBanner, WButton, WTextArea, WTextField } from '@warp/ui'
|
||
import { sendNotice } from '@/api/admin'
|
||
import Mxid from '@/components/Mxid.vue'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
import { useAction } from '@/lib/useLoad'
|
||
import { useSession } from '@/stores/session'
|
||
|
||
const session = useSession()
|
||
|
||
const user = ref('')
|
||
const body = ref('')
|
||
const send = useAction()
|
||
const last = ref<{ user: string; room: string } | null>(null)
|
||
|
||
const target = computed(() => {
|
||
const v = user.value.trim()
|
||
if (!v) return ''
|
||
return v.startsWith('@') ? v : `@${v}:${session.serverName}`
|
||
})
|
||
|
||
async function submit() {
|
||
let room = ''
|
||
const to = target.value
|
||
const ok = await send.run(async () => {
|
||
room = (await sendNotice(to, body.value.trim())).room_id
|
||
})
|
||
if (ok) {
|
||
last.value = { user: to, room }
|
||
body.value = ''
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<PageHeader
|
||
title="Server notices"
|
||
description="Messages from the server to one of its users, in a room they can read but not reply in."
|
||
/>
|
||
|
||
<WBanner v-if="send.error.value" tone="danger" class="mb-4">
|
||
{{ send.error.value }}
|
||
</WBanner>
|
||
<WBanner v-else-if="last" tone="success" title="Notice sent" class="mb-4">
|
||
<Mxid :value="last.user" /> will see it in their server notices room <Mxid :value="last.room" />.
|
||
</WBanner>
|
||
|
||
<form class="wp-card flex max-w-2xl flex-col gap-4 p-4" @submit.prevent="submit">
|
||
<WTextField
|
||
v-model="user"
|
||
wide
|
||
mono
|
||
label="User"
|
||
:hint="target && !user.trim().startsWith('@') ? target : 'A localpart or a full Matrix ID on this server'"
|
||
:placeholder="`@alice:${session.serverName}`"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
required
|
||
/>
|
||
<WTextArea v-model="body" label="Message" placeholder="Maintenance at 22:00 UTC." required rows="5" />
|
||
<p class="small text-ink-muted">
|
||
The user doesn’t have to be online: the invite and the message wait for them. Needs
|
||
<code class="code">server_notices.enabled</code> and a signing key.
|
||
</p>
|
||
<div>
|
||
<WButton type="submit" variant="primary" icon="send" :loading="send.busy.value" :disabled="!target || !body.trim()">
|
||
Send notice
|
||
</WButton>
|
||
</div>
|
||
</form>
|
||
</template>
|