Merge pull request #372 from Coffey-Labs/refactor/lib-domain-folders
Group the admin and calendar modules, and stop calling screenshots docs
This commit is contained in:
72 files changed
+108
-108
No files matched your search
@@ -36,9 +36,9 @@ settings included, belongs to Stalwart, so the container is disposable.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Inbox & conversation (dark)**  | **Inbox & conversation (light)**  |
|
||||
| **Composer**  | **Calendar**  |
|
||||
| **Contacts**  | **Sieve filter builder**  |
|
||||
| **Inbox & conversation (dark)**  | **Inbox & conversation (light)**  |
|
||||
| **Composer**  | **Calendar**  |
|
||||
| **Contacts**  | **Sieve filter builder**  |
|
||||
|
||||
Taken against the built-in mock with sample data. More, including the phone
|
||||
layout, on [ihasmail.org](https://ihasmail.org/#screenshots).
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -5,8 +5,8 @@
|
||||
* images already use rather than whatever a window happens to be.
|
||||
*
|
||||
* npm run dev:mock # in another terminal
|
||||
* node docs/screenshots.mjs docs/screenshots
|
||||
* node docs/screenshots-light.mjs docs/screenshots
|
||||
* node scripts/screenshots.mjs screenshots
|
||||
* node scripts/screenshots-light.mjs screenshots
|
||||
*
|
||||
* Restart the mock before a run. The filters shot creates rules, so a second
|
||||
* run against the same mock shows them twice.
|
||||
@@ -30,7 +30,7 @@
|
||||
* flip --bg to #f6f8fa. The compositor simply does not repaint everything a
|
||||
* CSS-variable change touches while metrics are overridden. Launching Chrome
|
||||
* at --window-size and never calling setDeviceMetricsOverride renders it
|
||||
* correctly, which is what docs/screenshots-light.mjs does.
|
||||
* correctly, which is what scripts/screenshots-light.mjs does.
|
||||
*
|
||||
* assertTheme() stays either way: without it this script wrote a dark
|
||||
* screenshot under a light caption and reported success, and that is how the
|
||||
@@ -226,7 +226,7 @@ try {
|
||||
await evaluate(`(() => { const c = [...document.querySelectorAll('button')].find(b => /close|discard/i.test(b.getAttribute('aria-label')||'')); if (c) c.click(); })()`);
|
||||
await sleep(800);
|
||||
|
||||
// (inbox-light is captured by docs/screenshots-light.mjs -- see the header)
|
||||
// (inbox-light is captured by scripts/screenshots-light.mjs -- see the header)
|
||||
|
||||
|
||||
// --- calendar ---
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describeRule as describeSieve } from "../sieve";
|
||||
import { describeRule as describeRecurrence, weekdayOptions } from "../recurrence";
|
||||
import { describeRule as describeRecurrence, weekdayOptions } from "../calendar/recurrence";
|
||||
import { setUiLanguageForFormatting } from "../datetime";
|
||||
import { setCatalog } from "../i18n";
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/adminAccess";
|
||||
import { ADMIN_BASELINE, adminSections, can, dashboardCards, canGrantRole, generatePassword, hasAdministration, outranks, permissionSet, resolveRoles, type RoleDef } from "@/lib/admin/adminAccess";
|
||||
|
||||
const set = (...p: string[]) => permissionSet(p);
|
||||
const everything = set(...ADMIN_BASELINE, "sysTenantGet", "jmapEmailGet", "impersonate");
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client, JmapMethodError } from "@/jmap/client";
|
||||
import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/adminDashboard";
|
||||
import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/admin/adminDashboard";
|
||||
|
||||
const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp });
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client } from "@/jmap/client";
|
||||
import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/adminDirectory";
|
||||
import { aliasList, describeDirectoryError, DirectoryError, hasPassword, passwordPatch, queryAccounts, quotasWithDisk } from "@/lib/admin/adminDirectory";
|
||||
|
||||
describe("setting a password", () => {
|
||||
it("writes into the existing password credential, keeping its place", () => {
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains";
|
||||
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/admin/adminDomains";
|
||||
|
||||
/**
|
||||
* Written the way Stalwart's BIND serializer writes it (dns-update's
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client } from "@/jmap/client";
|
||||
import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/adminGroups";
|
||||
import { countMembers, createGroup, destroyGroup, groupRoleKey, groupRolesFromKey, membershipPatch } from "@/lib/admin/adminGroups";
|
||||
|
||||
describe("group membership", () => {
|
||||
it("is a patch to each member, one pointer each, so no other membership moves", () => {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client } from "@/jmap/client";
|
||||
import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/adminLists";
|
||||
import { createList, parseAddresses, queryLists, recipientsPatch } from "@/lib/admin/adminLists";
|
||||
|
||||
describe("a mailing list's recipients", () => {
|
||||
it("are saved as what was added and removed, one pointer each", () => {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { permissionSet } from "@/lib/adminAccess";
|
||||
import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/adminRoles";
|
||||
import { permissionSet } from "@/lib/admin/adminAccess";
|
||||
import { canBuildOn, effectivePermissions, inherited, roleOutranks, setPatch, type DirectoryRole } from "@/lib/admin/adminRoles";
|
||||
|
||||
const flags = (...n: string[]) => Object.fromEntries(n.map((x) => [x, true]));
|
||||
const roles = new Map<string, DirectoryRole>([
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client } from "@/jmap/client";
|
||||
import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/adminTenants";
|
||||
import { countTenantMembers, drawableLogo, quotasPatch, setDomainTenant } from "@/lib/admin/adminTenants";
|
||||
|
||||
describe("a tenant's limits", () => {
|
||||
it("change one pointer each, leaving the quotas ihasmail does not offer alone", () => {
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
import { client } from "@/jmap/client";
|
||||
import { t } from "@/lib/i18n";
|
||||
import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/adminAccess";
|
||||
import type { PermissionsMode, RoleDef, UserRoles } from "@/lib/admin/adminAccess";
|
||||
|
||||
/**
|
||||
* Stalwart 0.16's directory, over the ordinary JMAP proxy.
|
||||
@@ -1,6 +1,6 @@
|
||||
import { client } from "@/jmap/client";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { DirectoryError } from "@/lib/adminDirectory";
|
||||
import { DirectoryError } from "@/lib/admin/adminDirectory";
|
||||
|
||||
/**
|
||||
* Stalwart 0.16's domains, over the same proxy as accounts.
|
||||
@@ -1,7 +1,7 @@
|
||||
import { client } from "@/jmap/client";
|
||||
import { t } from "@/lib/i18n";
|
||||
import type { PermissionsMode, UserRoles } from "@/lib/adminAccess";
|
||||
import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/adminDirectory";
|
||||
import type { PermissionsMode, UserRoles } from "@/lib/admin/adminAccess";
|
||||
import { DirectoryError, DISK_QUOTA, queryAccounts, type EmailAlias } from "@/lib/admin/adminDirectory";
|
||||
|
||||
/**
|
||||
* Groups, from Stalwart 0.16's directory.
|
||||
@@ -1,6 +1,6 @@
|
||||
import { client } from "@/jmap/client";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { DirectoryError, type EmailAlias } from "@/lib/adminDirectory";
|
||||
import { DirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory";
|
||||
|
||||
/**
|
||||
* Mailing lists, from Stalwart 0.16's directory.
|
||||
@@ -1,8 +1,8 @@
|
||||
import { apiFetch, client } from "@/jmap/client";
|
||||
import { t } from "@/lib/i18n";
|
||||
import type { Permissions, RoleDef } from "@/lib/adminAccess";
|
||||
import { DirectoryError } from "@/lib/adminDirectory";
|
||||
import { DomainError } from "@/lib/adminDomains";
|
||||
import type { Permissions, RoleDef } from "@/lib/admin/adminAccess";
|
||||
import { DirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { DomainError } from "@/lib/admin/adminDomains";
|
||||
import type { PermissionInfo } from "@/lib/permissionLabels";
|
||||
|
||||
/**
|
||||
@@ -1,7 +1,7 @@
|
||||
import { client } from "@/jmap/client";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { DirectoryError } from "@/lib/adminDirectory";
|
||||
import { DomainError } from "@/lib/adminDomains";
|
||||
import { DirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { DomainError } from "@/lib/admin/adminDomains";
|
||||
|
||||
/**
|
||||
* Tenants, from Stalwart 0.16's directory.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appointmentDraft, nextHalfHour } from "@/lib/appointment";
|
||||
import { appointmentDraft, nextHalfHour } from "@/lib/calendar/appointment";
|
||||
import type { Email, EmailBodyPart } from "@/jmap/types";
|
||||
|
||||
/**
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { availabilityWindow } from "@/lib/availabilityWindow";
|
||||
import { availabilityWindow } from "@/lib/calendar/availabilityWindow";
|
||||
|
||||
const at = (s: string) => new Date(s);
|
||||
const hours = (w: { ticks: { time: Date }[] }) => w.ticks.map((t) => `${t.time.getDate()}@${t.time.getHours()}`);
|
||||
+1
-1
@@ -13,7 +13,7 @@ import {
|
||||
dayDelta,
|
||||
resizePatch,
|
||||
SNAP_MINUTES,
|
||||
} from "@/lib/eventDrag";
|
||||
} from "@/lib/calendar/eventDrag";
|
||||
import { BIRTHDAY_ID_PREFIX } from "@/lib/birthdays";
|
||||
import type { CalendarEvent } from "@/jmap/types";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/ics";
|
||||
import { looksLikeCalendar, parseIcs, parseIcsDuration, parseDateValue, parseLine, unescapeText, unfold } from "@/lib/calendar/ics";
|
||||
|
||||
const cal = (body: string) => `BEGIN:VCALENDAR\r\nVERSION:2.0\r\n${body}\r\nEND:VCALENDAR\r\n`;
|
||||
const event = (props: string) => `BEGIN:VEVENT\r\n${props}\r\nEND:VEVENT`;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { toIcs, parseIcs } from "@/lib/ics";
|
||||
import { toIcs, parseIcs } from "@/lib/calendar/ics";
|
||||
import type { JSCalendarEvent } from "@/jmap/types";
|
||||
|
||||
/*
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Email, EmailAddress } from "@/jmap/types";
|
||||
import { useCalendar, type EventDraft } from "@/store/calendar";
|
||||
import { useMail } from "@/store/mail";
|
||||
import { uniqueAddresses } from "./address";
|
||||
import { toLocalDateOnly } from "./dates";
|
||||
import { htmlToText } from "./text";
|
||||
import { uniqueAddresses } from "../address";
|
||||
import { toLocalDateOnly } from "../dates";
|
||||
import { htmlToText } from "../text";
|
||||
|
||||
/**
|
||||
* How much of a message body is copied into an event description.
|
||||
File renamed without changes.
@@ -10,8 +10,8 @@
|
||||
* question — given an event and a gesture, what are the new start and end —
|
||||
* and the caller decides whether it is allowed to save that.
|
||||
*/
|
||||
import { addMinutes } from "./dates";
|
||||
import { isBirthdayEvent } from "./birthdays";
|
||||
import { addMinutes } from "../dates";
|
||||
import { isBirthdayEvent } from "../birthdays";
|
||||
import type { CalendarEvent } from "@/jmap/types";
|
||||
|
||||
/**
|
||||
File renamed without changes.
@@ -1,5 +1,5 @@
|
||||
import type { JSCalendarRecurrenceRule, JSCalendarNDay } from "@/jmap/types";
|
||||
import { formatList, weekdayName, weekdayNames } from "./datetime";
|
||||
import { formatList, weekdayName, weekdayNames } from "../datetime";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
|
||||
/**
|
||||
@@ -5,7 +5,7 @@ import { toUTCDate, toLocalDateTime, zonedToDate, parseDuration, DAY_MS, browser
|
||||
import { t } from "@/lib/i18n";
|
||||
import { useContacts } from "./contacts";
|
||||
import { BIRTHDAY_CALENDAR_ID, birthdaysInRange, isBirthdayEvent, type Birthday } from "@/lib/birthdays";
|
||||
import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/ics";
|
||||
import { looksLikeCalendar, parseIcs, toIcs, type IcsEvent } from "@/lib/calendar/ics";
|
||||
import { withBase } from "@/lib/basePath";
|
||||
import { settings, useSettings } from "./settings";
|
||||
import { useSession } from "./session";
|
||||
|
||||
@@ -22,7 +22,7 @@ import { formatSize } from "@/lib/format";
|
||||
import { collectShare } from "@/lib/shareTarget";
|
||||
import { TranslateBoundary } from "@/ui/TranslateBoundary";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { hasAdministration } from "@/lib/adminAccess";
|
||||
import { hasAdministration } from "@/lib/admin/adminAccess";
|
||||
import { usePermissions } from "./admin/usePermissions";
|
||||
import { AdminNav } from "./admin/AdminNav";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
generatePassword,
|
||||
outranks,
|
||||
type UserRoles,
|
||||
} from "@/lib/adminAccess";
|
||||
} from "@/lib/admin/adminAccess";
|
||||
import {
|
||||
aliasList,
|
||||
createAccount,
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
DISK_QUOTA,
|
||||
type DirectoryAccount,
|
||||
type EmailAlias,
|
||||
} from "@/lib/adminDirectory";
|
||||
} from "@/lib/admin/adminDirectory";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { t, tNode } from "@/lib/i18n";
|
||||
import { Link } from "wouter";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useLocation } from "wouter";
|
||||
import { ChevronLeft, ChevronRight, Search, UserPlus, Users } from "lucide-react";
|
||||
import { useSession } from "@/store/session";
|
||||
import { STALWART_CAP } from "@/jmap/client";
|
||||
import { can, type RoleDef } from "@/lib/adminAccess";
|
||||
import { can, type RoleDef } from "@/lib/admin/adminAccess";
|
||||
import {
|
||||
describeDirectoryError,
|
||||
getAccounts,
|
||||
@@ -14,14 +14,14 @@ import {
|
||||
DISK_QUOTA,
|
||||
type DirectoryAccount,
|
||||
type DirectoryDomain,
|
||||
} from "@/lib/adminDirectory";
|
||||
} from "@/lib/admin/adminDirectory";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Avatar, Empty, Spinner } from "@/ui/misc";
|
||||
import { usePermissions } from "./usePermissions";
|
||||
import { isSelf, roleName, type DirectoryContext } from "./directoryContext";
|
||||
import { AccountSheet } from "./AccountSheet";
|
||||
import { listTenantNames } from "@/lib/adminTenants";
|
||||
import { listTenantNames } from "@/lib/admin/adminTenants";
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
import { Link } from "wouter";
|
||||
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react";
|
||||
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/adminAccess";
|
||||
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/adminDashboard";
|
||||
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
|
||||
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
|
||||
import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Link, useLocation } from "wouter";
|
||||
import { Building2, Globe, LayoutDashboard, List, ShieldCheck, User, UsersRound } from "lucide-react";
|
||||
import { adminSections, type AdminSection } from "@/lib/adminAccess";
|
||||
import { adminSections, type AdminSection } from "@/lib/admin/adminAccess";
|
||||
import { t } from "@/lib/i18n";
|
||||
import { usePermissions } from "./usePermissions";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Redirect } from "wouter";
|
||||
import { adminSections, type AdminSection } from "@/lib/adminAccess";
|
||||
import { adminSections, type AdminSection } from "@/lib/admin/adminAccess";
|
||||
import { AccountsAdmin } from "./AccountsAdmin";
|
||||
import { AdminDashboard } from "./AdminDashboard";
|
||||
import { DomainsAdmin } from "./DomainsAdmin";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Copy, Globe, Plus, Trash2, X } from "lucide-react";
|
||||
import { can } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/adminDirectory";
|
||||
import { can } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import {
|
||||
createDomain,
|
||||
describeLinked,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
type DirectoryDomainFull,
|
||||
type DkimKey,
|
||||
type Managed,
|
||||
} from "@/lib/adminDomains";
|
||||
} from "@/lib/admin/adminDomains";
|
||||
import { formatFullDate } from "@/lib/format";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Dialog } from "@/ui/dialog";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { ChevronLeft, ChevronRight, Globe, Plus, Search } from "lucide-react";
|
||||
import { can } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/adminDirectory";
|
||||
import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/adminDomains";
|
||||
import { can } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { countAccounts, getDomains, namesOf, queryDomains, type DirectoryDomainFull } from "@/lib/admin/adminDomains";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Empty, Spinner } from "@/ui/misc";
|
||||
import { usePermissions } from "./usePermissions";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Search, Trash2, UserMinus, UserPlus, X } from "lucide-react";
|
||||
import { can, canGrantRole } from "@/lib/adminAccess";
|
||||
import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/adminDirectory";
|
||||
import { can, canGrantRole } from "@/lib/admin/adminAccess";
|
||||
import { aliasList, describeDirectoryError, quotasWithDisk, updateAccount, DISK_QUOTA, type EmailAlias } from "@/lib/admin/adminDirectory";
|
||||
import {
|
||||
createGroup,
|
||||
destroyGroup,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
setMembership,
|
||||
type DirectoryGroup,
|
||||
type GroupMember,
|
||||
} from "@/lib/adminGroups";
|
||||
} from "@/lib/admin/adminGroups";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Avatar, Spinner } from "@/ui/misc";
|
||||
|
||||
@@ -3,9 +3,9 @@ import { useLocation } from "wouter";
|
||||
import { ChevronLeft, ChevronRight, Plus, Search, UsersRound } from "lucide-react";
|
||||
import { useSession } from "@/store/session";
|
||||
import { STALWART_CAP } from "@/jmap/client";
|
||||
import { can, type RoleDef } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/adminDirectory";
|
||||
import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/adminGroups";
|
||||
import { can, type RoleDef } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError, listDomains, listRoles, type DirectoryDomain } from "@/lib/admin/adminDirectory";
|
||||
import { countMembers, getGroups, queryGroups, type DirectoryGroup } from "@/lib/admin/adminGroups";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Avatar, Empty, Spinner } from "@/ui/misc";
|
||||
import { usePermissions } from "./usePermissions";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Plus, Search, Trash2, X } from "lucide-react";
|
||||
import { can } from "@/lib/adminAccess";
|
||||
import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/adminDirectory";
|
||||
import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/adminLists";
|
||||
import { can } from "@/lib/admin/adminAccess";
|
||||
import { aliasList, describeDirectoryError, type EmailAlias } from "@/lib/admin/adminDirectory";
|
||||
import { createList, destroyList, parseAddresses, recipientsPatch, updateList, type DirectoryList } from "@/lib/admin/adminLists";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Dialog } from "@/ui/dialog";
|
||||
import { toast } from "@/ui/toast";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { ChevronLeft, ChevronRight, List, Plus, Search } from "lucide-react";
|
||||
import { can } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/adminDirectory";
|
||||
import { getLists, queryLists, type DirectoryList } from "@/lib/adminLists";
|
||||
import { can } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError, listDomains, type DirectoryDomain } from "@/lib/admin/adminDirectory";
|
||||
import { getLists, queryLists, type DirectoryList } from "@/lib/admin/adminLists";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Empty, Spinner } from "@/ui/misc";
|
||||
import { useSession } from "@/store/session";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Lock, Search, Trash2, X } from "lucide-react";
|
||||
import { can, canGrantRole } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/adminDirectory";
|
||||
import { describeLinked, DomainError } from "@/lib/adminDomains";
|
||||
import { can, canGrantRole } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { describeLinked, DomainError } from "@/lib/admin/adminDomains";
|
||||
import {
|
||||
canBuildOn,
|
||||
createRole,
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type DirectoryRole,
|
||||
type PermissionState,
|
||||
type RoleDefaults,
|
||||
} from "@/lib/adminRoles";
|
||||
} from "@/lib/admin/adminRoles";
|
||||
import type { PermissionEntry } from "@/lib/permissionLabels";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Dialog } from "@/ui/dialog";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { Plus, Search, ShieldCheck } from "lucide-react";
|
||||
import { can } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/adminDirectory";
|
||||
import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/adminRoles";
|
||||
import { can } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { effectivePermissions, listAllRoles, loadPermissionList, loadRoleDefaults, type DirectoryRole, type RoleDefaults } from "@/lib/admin/adminRoles";
|
||||
import { describePermissions, loadPermissionCatalog, type PermissionEntry } from "@/lib/permissionLabels";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
import { Empty, Spinner } from "@/ui/misc";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Globe, Plus, Trash2, X } from "lucide-react";
|
||||
import { can, canGrantRole, type RoleDef } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/adminDirectory";
|
||||
import { describeLinked, DomainError } from "@/lib/adminDomains";
|
||||
import { can, canGrantRole, type RoleDef } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError } from "@/lib/admin/adminDirectory";
|
||||
import { describeLinked, DomainError } from "@/lib/admin/adminDomains";
|
||||
import {
|
||||
countTenantMembers,
|
||||
createTenant,
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
type TenantMemberKind,
|
||||
type TenantQuota,
|
||||
type TenantRoles,
|
||||
} from "@/lib/adminTenants";
|
||||
} from "@/lib/admin/adminTenants";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { proxiedImageUrl } from "@/lib/html";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "wouter";
|
||||
import { Building2, ChevronLeft, ChevronRight, Plus, Search } from "lucide-react";
|
||||
import { can, type RoleDef } from "@/lib/adminAccess";
|
||||
import { describeDirectoryError, listRoles } from "@/lib/adminDirectory";
|
||||
import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/adminTenants";
|
||||
import { can, type RoleDef } from "@/lib/admin/adminAccess";
|
||||
import { describeDirectoryError, listRoles } from "@/lib/admin/adminDirectory";
|
||||
import { drawableLogo, getTenants, queryTenants, type DirectoryTenant } from "@/lib/admin/adminTenants";
|
||||
import { formatSize } from "@/lib/format";
|
||||
import { proxiedImageUrl } from "@/lib/html";
|
||||
import { plural, t } from "@/lib/i18n";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Router } from "wouter";
|
||||
import { memoryLocation } from "wouter/memory-location";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { DirectoryAccount } from "@/lib/adminDirectory";
|
||||
import type { DirectoryAccount } from "@/lib/admin/adminDirectory";
|
||||
import { AccountSheet } from "../AccountSheet";
|
||||
import type { DirectoryContext } from "../directoryContext";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { memoryLocation } from "wouter/memory-location";
|
||||
import { JmapMethodError } from "@/jmap/client";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { MetricRecord } from "@/lib/adminDashboard";
|
||||
import type { MetricRecord } from "@/lib/admin/adminDashboard";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
@@ -15,8 +15,8 @@ const feeds = vi.hoisted(() => ({
|
||||
metrics: (): Promise<MetricRecord[]> => Promise.resolve([]),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/adminDashboard", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminDashboard")>()),
|
||||
vi.mock("@/lib/admin/adminDashboard", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminDashboard")>()),
|
||||
countObjects: vi.fn(async (object: string) => feeds.counts[object]),
|
||||
loadMetrics: vi.fn(() => feeds.metrics()),
|
||||
}));
|
||||
|
||||
@@ -17,8 +17,8 @@ const domain = {
|
||||
dnsZoneFile: 'example.com. IN MX 10 mail.example.com.\nexample.com. IN TXT "v=spf1 mx -all"\n',
|
||||
};
|
||||
|
||||
vi.mock("@/lib/adminDomains", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminDomains")>()),
|
||||
vi.mock("@/lib/admin/adminDomains", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminDomains")>()),
|
||||
getDomains: vi.fn(async () => [domain]),
|
||||
listDkimKeys: vi.fn(async () => [{ id: "k1", "@type": "Dkim1Ed25519Sha256", selector: "v1-ed25519", stage: "active" }]),
|
||||
}));
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { DirectoryGroup } from "@/lib/adminGroups";
|
||||
import type { DirectoryGroup } from "@/lib/admin/adminGroups";
|
||||
import type { DirectoryContext } from "../directoryContext";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
@@ -17,8 +17,8 @@ const api = vi.hoisted(() => ({
|
||||
destroyGroup: vi.fn(async () => {}),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/adminGroups", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminGroups")>()),
|
||||
vi.mock("@/lib/admin/adminGroups", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminGroups")>()),
|
||||
listMembers: vi.fn(async () => ({ members: api.members, total: api.members.length })),
|
||||
searchUsers: vi.fn(async () => []),
|
||||
setMembership: api.setMembership,
|
||||
|
||||
@@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { DirectoryList } from "@/lib/adminLists";
|
||||
import type { DirectoryList } from "@/lib/admin/adminLists";
|
||||
import type { DirectoryContext } from "../directoryContext";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const api = vi.hoisted(() => ({ updateList: vi.fn(async () => {}) }));
|
||||
vi.mock("@/lib/adminLists", async (original) => ({ ...(await original<typeof import("@/lib/adminLists")>()), updateList: api.updateList }));
|
||||
vi.mock("@/lib/admin/adminLists", async (original) => ({ ...(await original<typeof import("@/lib/admin/adminLists")>()), updateList: api.updateList }));
|
||||
|
||||
const { ListSheet } = await import("../ListSheet");
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { DirectoryRole } from "@/lib/adminRoles";
|
||||
import type { DirectoryRole } from "@/lib/admin/adminRoles";
|
||||
import type { PermissionEntry } from "@/lib/permissionLabels";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const api = vi.hoisted(() => ({ updateRole: vi.fn(async () => {}) }));
|
||||
vi.mock("@/lib/adminRoles", async (original) => ({ ...(await original<typeof import("@/lib/adminRoles")>()), updateRole: api.updateRole }));
|
||||
vi.mock("@/lib/admin/adminRoles", async (original) => ({ ...(await original<typeof import("@/lib/admin/adminRoles")>()), updateRole: api.updateRole }));
|
||||
|
||||
const { RoleSheet } = await import("../RoleSheet");
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { DirectoryTenant } from "@/lib/adminTenants";
|
||||
import type { DirectoryTenant } from "@/lib/admin/adminTenants";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
@@ -13,8 +13,8 @@ const api = vi.hoisted(() => ({
|
||||
updateTenant: vi.fn(async () => {}),
|
||||
setDomainTenant: vi.fn(async () => {}),
|
||||
}));
|
||||
vi.mock("@/lib/adminTenants", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminTenants")>()),
|
||||
vi.mock("@/lib/admin/adminTenants", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminTenants")>()),
|
||||
countTenantMembers: vi.fn(async () => api.counts),
|
||||
tenantDomains: vi.fn(async () => ({ inTenant: [{ id: "d3", name: "old-brand.example" }], unassigned: [{ id: "d4", name: "spare.example" }] })),
|
||||
updateTenant: api.updateTenant,
|
||||
|
||||
@@ -9,8 +9,8 @@ import type { JmapSession } from "@/jmap/types";
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const api = vi.hoisted(() => ({ queryTenants: vi.fn(async () => ({ ids: ["t1"], total: 1 })) }));
|
||||
vi.mock("@/lib/adminTenants", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminTenants")>()),
|
||||
vi.mock("@/lib/admin/adminTenants", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminTenants")>()),
|
||||
queryTenants: api.queryTenants,
|
||||
getTenants: vi.fn(async () => [{ id: "t1", name: "Acme Corp", quotas: {}, usedDiskQuota: 0 }]),
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RoleDef } from "@/lib/adminAccess";
|
||||
import type { DirectoryAccount, DirectoryDomain } from "@/lib/adminDirectory";
|
||||
import type { RoleDef } from "@/lib/admin/adminAccess";
|
||||
import type { DirectoryAccount, DirectoryDomain } from "@/lib/admin/adminDirectory";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
export interface DirectoryContext {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { useSession } from "@/store/session";
|
||||
import { permissionSet, type Permissions } from "@/lib/adminAccess";
|
||||
import { permissionSet, type Permissions } from "@/lib/admin/adminAccess";
|
||||
|
||||
/**
|
||||
* The signed-in account's permissions, as a set, stable between renders.
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { Anchor } from "@/ui/popover";
|
||||
import { CalendarContextMenu, eventColor, type CalendarContext } from "./CalendarContextMenu";
|
||||
import { toast } from "@/ui/toast";
|
||||
import { askEditScope, droppedMessage, runScoped } from "./scope";
|
||||
import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/eventDrag";
|
||||
import { canDragEvent, dayDelta, moveByDaysPatch, movePatch, pixelsToMinutes, resizePatch, snap, type DragPatch } from "@/lib/calendar/eventDrag";
|
||||
import { t as translate } from "@/lib/i18n";
|
||||
|
||||
type View = "month" | "week" | "day" | "agenda";
|
||||
|
||||
@@ -13,9 +13,9 @@ import { RecipientInput } from "../compose/RecipientInput";
|
||||
import { DateField, DateTimeField } from "@/ui/datefield";
|
||||
import { browserTimeZone, dateToZonedLocal, formatDuration, fromInputDateTime, listTimeZones, parseDuration, toInputDateTime, toLocalDateOnly, zonedToDate, DAY_MS, humanDuration } from "@/lib/dates";
|
||||
import { formatClock, formatNumericDate, formatWeekday, formatWeekdayDate } from "@/lib/datetime";
|
||||
import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/recurrence";
|
||||
import { WEEKDAY_KEYS, weekdayOptions, describeRule, presetFor, ruleFromPreset, type RecurrencePreset } from "@/lib/calendar/recurrence";
|
||||
import { newKey } from "@/lib/contacts";
|
||||
import { availabilityWindow } from "@/lib/availabilityWindow";
|
||||
import { availabilityWindow } from "@/lib/calendar/availabilityWindow";
|
||||
import { askEditScope, droppedMessage, runScoped } from "./scope";
|
||||
import { plural, t as translate } from "@/lib/i18n";
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { confirmDialog } from "@/ui/dialog";
|
||||
import { toast } from "@/ui/toast";
|
||||
import { askDeleteScope, runScoped } from "./scope";
|
||||
import { formatTimeRange, humanDuration, parseDuration } from "@/lib/dates";
|
||||
import { describeRule } from "@/lib/recurrence";
|
||||
import { describeRule } from "@/lib/calendar/recurrence";
|
||||
import { useCompose } from "@/store/compose";
|
||||
import { useSettings } from "@/store/settings";
|
||||
import { categoryOf, eventColor } from "./CalendarContextMenu";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { rowClick } from "@/lib/listSelection";
|
||||
import { MenuItem, MenuSep, MenuTitle, Popover, useMenu } from "@/ui/popover";
|
||||
import { useCompose } from "@/store/compose";
|
||||
import { useCalendar } from "@/store/calendar";
|
||||
import { startAppointment } from "@/lib/appointment";
|
||||
import { startAppointment } from "@/lib/calendar/appointment";
|
||||
import { toast } from "@/ui/toast";
|
||||
import { haptic, usePullToRefresh, useTouchRow, PULL_TRIGGER } from "@/lib/touch";
|
||||
import { describeSwipe, type SwipeAction, type SwipeDescriptor, type SwipeIcon } from "@/lib/swipe";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useSettings } from "@/store/settings";
|
||||
import { draftFromMailto, useCompose } from "@/store/compose";
|
||||
import { useContacts } from "@/store/contacts";
|
||||
import { useCalendar } from "@/store/calendar";
|
||||
import { startAppointment } from "@/lib/appointment";
|
||||
import { startAppointment } from "@/lib/calendar/appointment";
|
||||
import { client } from "@/jmap/client";
|
||||
import { SignatureBanner } from "./SignatureBanner";
|
||||
import { useSignature } from "@/lib/smime/useSignature";
|
||||
|
||||
Reference in new issue
Block a user