mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 07:41:23 +00:00
feat: HA scheduler leadership + templated email log
HA scheduler: the job scheduler and alert evaluator hold session-scoped PostgreSQL advisory locks on pinned pool connections — replicas scan for due work exactly once, failover verified live with two replicas. Manual and hook-triggered runs stay available on any replica. Email surface: mailer renders registered templates (invitation); every send is logged to core.email_log with status and error text. New GET /projects/:id/emails endpoint; Members page shows an Email log card. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
45a1621909
commit
f747b33b46
24 files changed
+669
-16
No files matched your search
@@ -1722,6 +1722,8 @@ export default function App() {
|
||||
|
||||
<Show when={activeView() === "members"}>
|
||||
<MembersPage
|
||||
projectID={activeProject()?.id}
|
||||
demoMode={isDemo}
|
||||
organizationMembers={organizationMembers()}
|
||||
organizationInvitations={organizationInvitations()}
|
||||
projectMembers={projectMembers()}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { For, Show, createMemo, createSignal } from "solid-js";
|
||||
import type {
|
||||
OrganizationInvitation,
|
||||
OrganizationMember,
|
||||
ProjectMember,
|
||||
import { For, Show, createEffect, createMemo, createSignal } from "solid-js";
|
||||
import {
|
||||
EmailService,
|
||||
type EmailLogEntry,
|
||||
type OrganizationInvitation,
|
||||
type OrganizationMember,
|
||||
type ProjectMember,
|
||||
} from "@primora/api-client";
|
||||
import { Badge } from "../components/Badge";
|
||||
import { Modal, ModalFooter } from "../components/Modal";
|
||||
@@ -22,6 +24,8 @@ interface InvitationInput {
|
||||
}
|
||||
|
||||
interface MembersPageProps {
|
||||
projectID?: string;
|
||||
demoMode?: boolean;
|
||||
organizationMembers?: OrganizationMember[];
|
||||
organizationInvitations?: OrganizationInvitation[];
|
||||
projectMembers?: ProjectMember[];
|
||||
@@ -80,6 +84,15 @@ export function MembersPage(props: MembersPageProps) {
|
||||
const [tab, setTab] = createSignal<"org" | "project">("org");
|
||||
const [inviteOpen, setInviteOpen] = createSignal(false);
|
||||
const [query, setQuery] = createSignal("");
|
||||
const [emails, setEmails] = createSignal<EmailLogEntry[]>([]);
|
||||
|
||||
createEffect(() => {
|
||||
const pid = props.projectID;
|
||||
if (!pid || props.demoMode) return;
|
||||
void EmailService.listEmailLog({ projectId: pid, limit: 20 })
|
||||
.then((res) => setEmails(res.items ?? []))
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
const filteredOrgMembers = createMemo(() => {
|
||||
const q = query().toLowerCase();
|
||||
@@ -368,6 +381,49 @@ export function MembersPage(props: MembersPageProps) {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={emails().length > 0}>
|
||||
<div class="card card-flush">
|
||||
<div class="card-header" style="padding:1.25rem 1.25rem 1rem;margin-bottom:0;border-bottom:1px solid var(--border)">
|
||||
<span class="card-header-title">Email log</span>
|
||||
<span class="card-header-description">
|
||||
Transactional email sent for this project — every send is recorded, including failures.
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>To</th>
|
||||
<th>Subject</th>
|
||||
<th>Template</th>
|
||||
<th>Status</th>
|
||||
<th>Sent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={emails()}>
|
||||
{(mail) => (
|
||||
<tr>
|
||||
<td class="font-medium text-text-1">{mail.to_email}</td>
|
||||
<td class="text-text-2" style="max-width:18rem;overflow:hidden;text-overflow:ellipsis" title={mail.error || mail.subject}>
|
||||
{mail.subject}
|
||||
</td>
|
||||
<td>
|
||||
<Badge variant="neutral">{mail.template}</Badge>
|
||||
</td>
|
||||
<td>
|
||||
<Badge variant={mail.status === "sent" ? "success" : "error"}>{mail.status}</Badge>
|
||||
</td>
|
||||
<td class="text-text-3">{formatDate(mail.created_at)}</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Invite modal */}
|
||||
<Modal
|
||||
open={inviteOpen()}
|
||||
|
||||
Reference in new issue
Block a user