438 lines
15 KiB
Vue
438 lines
15 KiB
Vue
<script setup>
|
|
import AdminLayout from "@/Layouts/AdminLayout.vue";
|
|
import { Head, Link, useForm } from "@inertiajs/vue3";
|
|
import { ref, computed } from "vue";
|
|
import {
|
|
PlusIcon,
|
|
FlaskConicalIcon,
|
|
MailIcon,
|
|
PencilIcon,
|
|
SendIcon,
|
|
MoreVerticalIcon,
|
|
} from "lucide-vue-next";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/Components/ui/card";
|
|
import { Button } from "@/Components/ui/button";
|
|
import { Input } from "@/Components/ui/input";
|
|
import { Label } from "@/Components/ui/label";
|
|
import { Badge } from "@/Components/ui/badge";
|
|
import { Switch } from "@/Components/ui/switch";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/Components/ui/table";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/Components/ui/dropdown-menu";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/Components/ui/dialog";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/Components/ui/select";
|
|
|
|
const props = defineProps({
|
|
profiles: { type: Array, default: () => [] },
|
|
});
|
|
|
|
const createOpen = ref(false); // create modal
|
|
const editOpen = ref(false); // edit modal
|
|
const editTarget = ref(null); // profile being edited
|
|
|
|
const form = useForm({
|
|
name: "",
|
|
host: "",
|
|
port: 587,
|
|
encryption: "tls",
|
|
username: "",
|
|
password: "",
|
|
from_address: "",
|
|
from_name: "",
|
|
priority: 10,
|
|
});
|
|
|
|
function openCreate() {
|
|
form.reset();
|
|
createOpen.value = true;
|
|
editTarget.value = null;
|
|
}
|
|
|
|
function openEdit(p) {
|
|
// populate form with existing profile data (exclude password which is write-only)
|
|
form.reset();
|
|
form.name = p.name || "";
|
|
form.host = p.host || "";
|
|
form.port = p.port || 587;
|
|
form.encryption = p.encryption || "";
|
|
form.username = p.username || "";
|
|
form.password = ""; // empty -> keep existing unless user fills
|
|
form.from_address = p.from_address || "";
|
|
form.from_name = p.from_name || "";
|
|
form.priority = p.priority ?? 10;
|
|
editTarget.value = p;
|
|
editOpen.value = true;
|
|
}
|
|
|
|
function closeCreate() {
|
|
if (form.processing) return;
|
|
createOpen.value = false;
|
|
}
|
|
|
|
function submitCreate() {
|
|
form.post(route("admin.mail-profiles.store"), {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
createOpen.value = false;
|
|
},
|
|
});
|
|
}
|
|
|
|
function closeEdit() {
|
|
if (form.processing) return;
|
|
editOpen.value = false;
|
|
editTarget.value = null;
|
|
}
|
|
|
|
function submitEdit() {
|
|
if (!editTarget.value) return;
|
|
// Build payload excluding empty password
|
|
const payload = {
|
|
name: form.name,
|
|
host: form.host,
|
|
port: form.port,
|
|
encryption: form.encryption || null,
|
|
username: form.username || null,
|
|
from_address: form.from_address,
|
|
from_name: form.from_name || null,
|
|
priority: form.priority,
|
|
};
|
|
if (form.password && form.password.trim() !== "") {
|
|
payload.password = form.password.trim();
|
|
}
|
|
form
|
|
.transform(() => payload)
|
|
.put(route("admin.mail-profiles.update", editTarget.value.id), {
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
editOpen.value = false;
|
|
editTarget.value = null;
|
|
},
|
|
});
|
|
}
|
|
|
|
function toggleActive(p) {
|
|
window.axios
|
|
.post(route("admin.mail-profiles.toggle", p.id))
|
|
.then(() => window.location.reload());
|
|
}
|
|
|
|
function testConnection(p) {
|
|
window.axios
|
|
.post(route("admin.mail-profiles.test", p.id))
|
|
.then(() => window.location.reload());
|
|
}
|
|
|
|
function sendTestEmail(p) {
|
|
window.axios
|
|
.post(route("admin.mail-profiles.send-test", p.id))
|
|
.then(() => window.location.reload());
|
|
}
|
|
|
|
const statusClass = (p) => {
|
|
if (p.test_status === "success") return "text-emerald-600";
|
|
if (p.test_status === "failed") return "text-rose-600";
|
|
if (p.test_status === "queued") return "text-amber-500";
|
|
return "text-gray-400";
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<AdminLayout title="Mail profili">
|
|
<Head title="Mail profili" />
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex items-start gap-3">
|
|
<div
|
|
class="inline-flex items-center justify-center h-10 w-10 rounded-lg bg-primary/10 text-primary"
|
|
>
|
|
<MailIcon class="h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<CardTitle>Mail profili</CardTitle>
|
|
<CardDescription
|
|
>Upravljajte SMTP profile za pošiljanje e-pošte ({{
|
|
profiles.length
|
|
}})</CardDescription
|
|
>
|
|
</div>
|
|
</div>
|
|
<Button @click="openCreate">
|
|
<PlusIcon class="h-4 w-4 mr-2" />
|
|
Nov profil
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Ime</TableHead>
|
|
<TableHead>Host</TableHead>
|
|
<TableHead class="text-center">Port</TableHead>
|
|
<TableHead class="text-center">Enc</TableHead>
|
|
<TableHead class="text-center">Aktivno</TableHead>
|
|
<TableHead class="text-center">Status</TableHead>
|
|
<TableHead>Zadnji uspeh</TableHead>
|
|
<TableHead>Napaka</TableHead>
|
|
<TableHead class="text-right">Akcije</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
<TableRow v-for="p in profiles" :key="p.id">
|
|
<TableCell class="font-medium">{{ p.name }}</TableCell>
|
|
<TableCell>{{ p.host }}</TableCell>
|
|
<TableCell class="text-center">{{ p.port }}</TableCell>
|
|
<TableCell class="text-center">
|
|
<Badge v-if="p.encryption" variant="outline">{{
|
|
p.encryption.toUpperCase()
|
|
}}</Badge>
|
|
<span v-else class="text-muted-foreground">—</span>
|
|
</TableCell>
|
|
<TableCell class="text-center">
|
|
<Switch
|
|
:default-value="p.active"
|
|
@update:model-value="() => toggleActive(p)"
|
|
/>
|
|
</TableCell>
|
|
<TableCell class="text-center">
|
|
<Badge
|
|
v-if="p.test_status === 'success'"
|
|
variant="default"
|
|
class="bg-green-100 text-green-800 hover:bg-green-100"
|
|
>{{ p.test_status }}</Badge
|
|
>
|
|
<Badge v-else-if="p.test_status === 'failed'" variant="destructive">{{
|
|
p.test_status
|
|
}}</Badge>
|
|
<Badge
|
|
v-else-if="p.test_status === 'queued'"
|
|
variant="secondary"
|
|
class="bg-amber-100 text-amber-800 hover:bg-amber-100"
|
|
>{{ p.test_status }}</Badge
|
|
>
|
|
<span v-else class="text-muted-foreground">—</span>
|
|
</TableCell>
|
|
<TableCell class="text-sm text-muted-foreground">
|
|
{{
|
|
p.last_success_at ? new Date(p.last_success_at).toLocaleString() : "—"
|
|
}}
|
|
</TableCell>
|
|
<TableCell
|
|
class="text-sm text-destructive max-w-[200px] truncate"
|
|
:title="p.last_error_message"
|
|
>
|
|
{{ p.last_error_message || "—" }}
|
|
</TableCell>
|
|
<TableCell class="text-right">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger as-child>
|
|
<Button variant="ghost" size="sm">
|
|
<MoreVerticalIcon class="h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Akcije</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem @click="testConnection(p)">
|
|
<FlaskConicalIcon class="h-4 w-4 mr-2" />
|
|
Test povezavo
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem @click="sendTestEmail(p)">
|
|
<SendIcon class="h-4 w-4 mr-2" />
|
|
Pošlji testni email
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem @click="openEdit(p)">
|
|
<PencilIcon class="h-4 w-4 mr-2" />
|
|
Uredi profil
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Dialog :open="createOpen" @update:open="(val) => (createOpen = val)">
|
|
<DialogContent class="max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Nov Mail profil</DialogTitle>
|
|
<DialogDescription
|
|
>Ustvarite nov SMTP profil za pošiljanje e-pošte</DialogDescription
|
|
>
|
|
</DialogHeader>
|
|
<form @submit.prevent="submitCreate" class="space-y-4">
|
|
<div class="grid gap-4 grid-cols-2">
|
|
<div class="space-y-2">
|
|
<Label for="create-name">Ime</Label>
|
|
<Input id="create-name" v-model="form.name" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-host">Host</Label>
|
|
<Input id="create-host" v-model="form.host" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-port">Port</Label>
|
|
<Input id="create-port" v-model.number="form.port" type="number" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-encryption">Encryption</Label>
|
|
<Select v-model="form.encryption">
|
|
<SelectTrigger id="create-encryption">
|
|
<SelectValue placeholder="Izberi..." />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem :value="null">None</SelectItem>
|
|
<SelectItem value="tls">TLS</SelectItem>
|
|
<SelectItem value="ssl">SSL</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-username">Username</Label>
|
|
<Input id="create-username" v-model="form.username" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-password">Password</Label>
|
|
<Input
|
|
id="create-password"
|
|
v-model="form.password"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-from-address">From naslov</Label>
|
|
<Input id="create-from-address" v-model="form.from_address" type="email" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-from-name">From ime</Label>
|
|
<Input id="create-from-name" v-model="form.from_name" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="create-priority">Prioriteta</Label>
|
|
<Input id="create-priority" v-model.number="form.priority" type="number" />
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<DialogFooter>
|
|
<Button variant="outline" @click="closeCreate" :disabled="form.processing"
|
|
>Prekliči</Button
|
|
>
|
|
<Button @click="submitCreate" :disabled="form.processing">Shrani</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
<Dialog :open="editOpen" @update:open="(val) => (editOpen = val)">
|
|
<DialogContent class="max-w-2xl">
|
|
<DialogHeader>
|
|
<DialogTitle>Uredi Mail profil</DialogTitle>
|
|
<DialogDescription>Posodobite nastavitve SMTP profila</DialogDescription>
|
|
</DialogHeader>
|
|
<form @submit.prevent="submitEdit" class="space-y-4">
|
|
<div class="grid gap-4 grid-cols-2">
|
|
<div class="space-y-2">
|
|
<Label for="edit-name">Ime</Label>
|
|
<Input id="edit-name" v-model="form.name" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-host">Host</Label>
|
|
<Input id="edit-host" v-model="form.host" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-port">Port</Label>
|
|
<Input id="edit-port" v-model.number="form.port" type="number" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-encryption">Encryption</Label>
|
|
<Select v-model="form.encryption">
|
|
<SelectTrigger id="edit-encryption">
|
|
<SelectValue placeholder="Izberi..." />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem :value="null">None</SelectItem>
|
|
<SelectItem value="tls">TLS</SelectItem>
|
|
<SelectItem value="ssl">SSL</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-username">Username</Label>
|
|
<Input id="edit-username" v-model="form.username" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-password">Password (nova, če spreminjaš)</Label>
|
|
<Input
|
|
id="edit-password"
|
|
v-model="form.password"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
/>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-from-address">From naslov</Label>
|
|
<Input id="edit-from-address" v-model="form.from_address" type="email" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-from-name">From ime</Label>
|
|
<Input id="edit-from-name" v-model="form.from_name" type="text" />
|
|
</div>
|
|
<div class="space-y-2">
|
|
<Label for="edit-priority">Prioriteta</Label>
|
|
<Input id="edit-priority" v-model.number="form.priority" type="number" />
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-muted-foreground">
|
|
Pusti geslo prazno, če želiš obdržati obstoječe.
|
|
</p>
|
|
</form>
|
|
<DialogFooter>
|
|
<Button variant="outline" @click="closeEdit" :disabled="form.processing"
|
|
>Prekliči</Button
|
|
>
|
|
<Button @click="submitEdit" :disabled="form.processing">Shrani</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</AdminLayout>
|
|
</template>
|
|
|
|
<style scoped></style>
|