@fonderie/vue-admin-screens 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +535 -367
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -3
- package/dist/index.d.ts +68 -3
- package/dist/index.js +523 -356
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
ModulesScreen: () => ModulesScreen,
|
|
32
32
|
OperatorsScreen: () => OperatorsScreen,
|
|
33
33
|
RoutesScreen: () => RoutesScreen,
|
|
34
|
+
SubscriberBilling: () => SubscriberBilling,
|
|
34
35
|
SubscriberScreen: () => SubscriberScreen,
|
|
35
36
|
TokensScreen: () => TokensScreen,
|
|
36
37
|
UsersScreen: () => UsersScreen
|
|
@@ -40,7 +41,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
41
|
// src/screens/AdminShell.ts
|
|
41
42
|
var import_vue_config_admin_screens = require("@fonderie/vue-config-admin-screens");
|
|
42
43
|
var import_vue_courier_admin_screens = require("@fonderie/vue-courier-admin-screens");
|
|
43
|
-
var
|
|
44
|
+
var import_vue18 = require("vue");
|
|
44
45
|
|
|
45
46
|
// src/styles.ts
|
|
46
47
|
var t = {
|
|
@@ -596,6 +597,12 @@ function empty(title, body, iconName = "check") {
|
|
|
596
597
|
body ? (0, import_vue.h)("div", body) : null
|
|
597
598
|
]);
|
|
598
599
|
}
|
|
600
|
+
function refreshButton(onClick, busy, label = "Refresh") {
|
|
601
|
+
return (0, import_vue.h)("button", { type: "button", style: styles.button, onClick, disabled: busy }, [
|
|
602
|
+
icon("refresh", 14),
|
|
603
|
+
busy ? "Working\u2026" : label
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
599
606
|
function methodChip(method) {
|
|
600
607
|
const tone = method === "GET" ? "ok" : method === "DELETE" ? "bad" : method === "POST" ? "info" : "warn";
|
|
601
608
|
const color = toneColor[tone];
|
|
@@ -647,7 +654,7 @@ function table(headers, rows) {
|
|
|
647
654
|
]);
|
|
648
655
|
}
|
|
649
656
|
var td = (children, extra = {}) => (0, import_vue2.h)("td", { style: { ...styles.td, ...extra } }, children);
|
|
650
|
-
var
|
|
657
|
+
var refreshButton2 = (label, disabled, onClick) => (0, import_vue2.h)("button", { type: "button", style: styles.button, disabled, onClick }, [
|
|
651
658
|
icon("refresh", 14),
|
|
652
659
|
disabled ? "Working\u2026" : label
|
|
653
660
|
]);
|
|
@@ -673,7 +680,7 @@ var AdminLogScreen = (0, import_vue3.defineComponent)({
|
|
|
673
680
|
pageHeader(
|
|
674
681
|
"Admin log",
|
|
675
682
|
"Every request made through this surface, newest first \u2014 refused ones included.",
|
|
676
|
-
[
|
|
683
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
677
684
|
),
|
|
678
685
|
error.value ? (0, import_vue3.h)(
|
|
679
686
|
"p",
|
|
@@ -814,7 +821,7 @@ var AttentionScreen = (0, import_vue4.defineComponent)({
|
|
|
814
821
|
)
|
|
815
822
|
];
|
|
816
823
|
},
|
|
817
|
-
[
|
|
824
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
818
825
|
{
|
|
819
826
|
lead: a ? `What needs you on this deployment \xB7 checked ${new Date(a.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment"
|
|
820
827
|
}
|
|
@@ -994,7 +1001,7 @@ var DoctorScreen = (0, import_vue6.defineComponent)({
|
|
|
994
1001
|
report.value.ok ? "ok" : "bad",
|
|
995
1002
|
report.value.ok ? "all checks pass" : "a check failed"
|
|
996
1003
|
) : null,
|
|
997
|
-
|
|
1004
|
+
refreshButton2("Run again", isLoading.value, () => void refresh())
|
|
998
1005
|
],
|
|
999
1006
|
{
|
|
1000
1007
|
loadingText: "Running the checks\u2026",
|
|
@@ -1197,7 +1204,7 @@ var MigrationsScreen = (0, import_vue9.defineComponent)({
|
|
|
1197
1204
|
...behind.map((m) => section(m, r.everApplied))
|
|
1198
1205
|
].filter(Boolean);
|
|
1199
1206
|
},
|
|
1200
|
-
[
|
|
1207
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
1201
1208
|
{
|
|
1202
1209
|
lead: "Schema changes each module ships, and whether this database has them.",
|
|
1203
1210
|
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
@@ -1706,29 +1713,234 @@ var TokensScreen = (0, import_vue11.defineComponent)({
|
|
|
1706
1713
|
|
|
1707
1714
|
// src/screens/UsersScreen.ts
|
|
1708
1715
|
var import_client = require("@fonderie/client");
|
|
1709
|
-
var
|
|
1716
|
+
var import_vue_admin11 = require("@fonderie/vue-admin");
|
|
1717
|
+
var import_vue14 = require("vue");
|
|
1718
|
+
|
|
1719
|
+
// src/billing.ts
|
|
1710
1720
|
var import_vue12 = require("vue");
|
|
1711
|
-
|
|
1721
|
+
function periodEnd(s) {
|
|
1722
|
+
if (!s.currentPeriodEnd) return null;
|
|
1723
|
+
const date = new Date(s.currentPeriodEnd).toLocaleDateString();
|
|
1724
|
+
if (s.status === "canceled")
|
|
1725
|
+
return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
|
|
1726
|
+
if (s.cancelAtPeriodEnd) return { label: "Ends", date };
|
|
1727
|
+
return { label: "Renews", date };
|
|
1728
|
+
}
|
|
1729
|
+
var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
|
|
1730
|
+
function useSubscriptionIndex(client) {
|
|
1731
|
+
const index = (0, import_vue12.ref)(null);
|
|
1732
|
+
if (!client) return index;
|
|
1733
|
+
void (async () => {
|
|
1734
|
+
const map = /* @__PURE__ */ new Map();
|
|
1735
|
+
let cursor;
|
|
1736
|
+
for (let page2 = 0; page2 < 20; page2++) {
|
|
1737
|
+
const { result } = await client.listSubscriptions({
|
|
1738
|
+
limit: 100,
|
|
1739
|
+
...cursor ? { cursor } : {}
|
|
1740
|
+
});
|
|
1741
|
+
for (const s of result.subscriptions) {
|
|
1742
|
+
const key = `${s.subscriberType}/${s.subscriberId}`;
|
|
1743
|
+
if (!map.has(key)) map.set(key, s);
|
|
1744
|
+
}
|
|
1745
|
+
if (!result.nextCursor) break;
|
|
1746
|
+
cursor = result.nextCursor;
|
|
1747
|
+
}
|
|
1748
|
+
index.value = map;
|
|
1749
|
+
})().catch(() => {
|
|
1750
|
+
index.value = /* @__PURE__ */ new Map();
|
|
1751
|
+
});
|
|
1752
|
+
return index;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/screens/SubscriberBilling.ts
|
|
1756
|
+
var import_vue_admin10 = require("@fonderie/vue-admin");
|
|
1757
|
+
var import_vue13 = require("vue");
|
|
1758
|
+
var SubscriberBilling = (0, import_vue13.defineComponent)({
|
|
1759
|
+
name: "FonderieSubscriberBilling",
|
|
1760
|
+
props: {
|
|
1761
|
+
client: { type: Object, required: true },
|
|
1762
|
+
subscriberType: { type: String, required: true },
|
|
1763
|
+
subscriberId: { type: String, required: true }
|
|
1764
|
+
},
|
|
1765
|
+
setup(props) {
|
|
1766
|
+
const subscriber = (0, import_vue13.computed)(() => ({ type: props.subscriberType, id: props.subscriberId }));
|
|
1767
|
+
const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_vue_admin10.useAdminSubscriber)(props.client, subscriber, { limit: 20 });
|
|
1768
|
+
const amount = (0, import_vue13.ref)("");
|
|
1769
|
+
const note = (0, import_vue13.ref)("");
|
|
1770
|
+
const granted = (0, import_vue13.ref)(null);
|
|
1771
|
+
return () => {
|
|
1772
|
+
const s = subscription.value;
|
|
1773
|
+
const w = wallet.value;
|
|
1774
|
+
if (isLoading.value && !w && !s) return (0, import_vue13.h)("p", { style: styles.status }, "Loading billing\u2026");
|
|
1775
|
+
const end = s ? periodEnd(s) : null;
|
|
1776
|
+
return (0, import_vue13.h)("div", [
|
|
1777
|
+
error.value ? (0, import_vue13.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1778
|
+
(0, import_vue13.h)("div", { style: { ...styles.grid, marginBottom: "16px" } }, [
|
|
1779
|
+
(0, import_vue13.h)("div", { style: styles.card }, [
|
|
1780
|
+
(0, import_vue13.h)("div", { style: styles.statLabel }, "Plan"),
|
|
1781
|
+
(0, import_vue13.h)(
|
|
1782
|
+
"div",
|
|
1783
|
+
{
|
|
1784
|
+
style: {
|
|
1785
|
+
display: "flex",
|
|
1786
|
+
alignItems: "center",
|
|
1787
|
+
gap: "8px",
|
|
1788
|
+
marginTop: "6px",
|
|
1789
|
+
flexWrap: "wrap"
|
|
1790
|
+
}
|
|
1791
|
+
},
|
|
1792
|
+
[
|
|
1793
|
+
(0, import_vue13.h)("strong", { style: { fontSize: "18px" } }, s ? s.plan : "Free"),
|
|
1794
|
+
...s ? [
|
|
1795
|
+
(0, import_vue13.h)("span", { style: styles.muted }, `\xB7 ${s.interval}`),
|
|
1796
|
+
pill(statusTone(s.status), s.status)
|
|
1797
|
+
] : [(0, import_vue13.h)("span", { style: styles.muted }, "no subscription")]
|
|
1798
|
+
]
|
|
1799
|
+
),
|
|
1800
|
+
end ? (0, import_vue13.h)("div", { style: styles.statHint }, `${end.label} ${end.date}`) : null,
|
|
1801
|
+
s?.providerSubscriptionId ? (0, import_vue13.h)(
|
|
1802
|
+
"div",
|
|
1803
|
+
{ style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" } },
|
|
1804
|
+
s.providerSubscriptionId
|
|
1805
|
+
) : null
|
|
1806
|
+
]),
|
|
1807
|
+
(0, import_vue13.h)("div", { style: styles.card }, [
|
|
1808
|
+
(0, import_vue13.h)("div", { style: styles.statLabel }, "Credits"),
|
|
1809
|
+
...w ? [
|
|
1810
|
+
(0, import_vue13.h)("div", { style: styles.statValue }, [
|
|
1811
|
+
`${w.balance} `,
|
|
1812
|
+
(0, import_vue13.h)("span", { style: { fontSize: "14px", fontWeight: 500 } }, w.currency)
|
|
1813
|
+
]),
|
|
1814
|
+
(0, import_vue13.h)(
|
|
1815
|
+
"div",
|
|
1816
|
+
{ style: styles.statHint },
|
|
1817
|
+
`${w.purchased ?? "0"} permanent \xB7 ${w.granted ?? "0"} plan allowance${w.grantedExpiresAt ? ` (expires ${new Date(w.grantedExpiresAt).toLocaleDateString()})` : ""}`
|
|
1818
|
+
)
|
|
1819
|
+
] : [
|
|
1820
|
+
(0, import_vue13.h)(
|
|
1821
|
+
"div",
|
|
1822
|
+
{ style: styles.statHint },
|
|
1823
|
+
"No wallet \u2014 billing has no wallet configuration."
|
|
1824
|
+
)
|
|
1825
|
+
]
|
|
1826
|
+
])
|
|
1827
|
+
]),
|
|
1828
|
+
w ? (0, import_vue13.h)(
|
|
1829
|
+
"form",
|
|
1830
|
+
{
|
|
1831
|
+
style: {
|
|
1832
|
+
...styles.card,
|
|
1833
|
+
display: "flex",
|
|
1834
|
+
gap: "8px",
|
|
1835
|
+
alignItems: "center",
|
|
1836
|
+
flexWrap: "wrap",
|
|
1837
|
+
marginBottom: "16px"
|
|
1838
|
+
},
|
|
1839
|
+
onSubmit: (e) => {
|
|
1840
|
+
e.preventDefault();
|
|
1841
|
+
const key = `admin-grant-${props.subscriberType}-${props.subscriberId}-${Date.now()}`;
|
|
1842
|
+
const a = amount.value.trim();
|
|
1843
|
+
void grant({
|
|
1844
|
+
amount: a,
|
|
1845
|
+
...note.value ? { description: note.value } : {},
|
|
1846
|
+
idempotencyKey: key
|
|
1847
|
+
}).then(() => {
|
|
1848
|
+
granted.value = `Granted ${a} ${w.currency}.`;
|
|
1849
|
+
amount.value = "";
|
|
1850
|
+
note.value = "";
|
|
1851
|
+
}).catch(() => {
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
[
|
|
1856
|
+
(0, import_vue13.h)("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Grant credits"),
|
|
1857
|
+
(0, import_vue13.h)("input", {
|
|
1858
|
+
value: amount.value,
|
|
1859
|
+
onInput: (e) => amount.value = e.target.value,
|
|
1860
|
+
placeholder: `amount (${w.currency})`,
|
|
1861
|
+
inputmode: "numeric",
|
|
1862
|
+
style: { ...styles.input, width: "150px" },
|
|
1863
|
+
"aria-label": "Amount"
|
|
1864
|
+
}),
|
|
1865
|
+
(0, import_vue13.h)("input", {
|
|
1866
|
+
value: note.value,
|
|
1867
|
+
onInput: (e) => note.value = e.target.value,
|
|
1868
|
+
placeholder: "reason (optional)",
|
|
1869
|
+
style: { ...styles.input, flex: 1, minWidth: "180px" },
|
|
1870
|
+
"aria-label": "Reason"
|
|
1871
|
+
}),
|
|
1872
|
+
(0, import_vue13.h)(
|
|
1873
|
+
"button",
|
|
1874
|
+
{
|
|
1875
|
+
type: "submit",
|
|
1876
|
+
style: styles.buttonPrimary,
|
|
1877
|
+
disabled: !/^\d+$/.test(amount.value.trim())
|
|
1878
|
+
},
|
|
1879
|
+
[icon("plus", 14), "Grant"]
|
|
1880
|
+
),
|
|
1881
|
+
granted.value ? (0, import_vue13.h)("span", { style: styles.ok }, granted.value) : null
|
|
1882
|
+
]
|
|
1883
|
+
) : null,
|
|
1884
|
+
w ? ledger.value.length === 0 ? empty("No credit movements yet", void 0, "subscriber") : (0, import_vue13.h)("div", [
|
|
1885
|
+
table(
|
|
1886
|
+
["When", "Kind", "Amount", "Note"],
|
|
1887
|
+
ledger.value.map(
|
|
1888
|
+
(t2) => (0, import_vue13.h)("tr", { key: t2.id }, [
|
|
1889
|
+
td(new Date(t2.createdAt).toLocaleString(), {
|
|
1890
|
+
...styles.muted,
|
|
1891
|
+
whiteSpace: "nowrap"
|
|
1892
|
+
}),
|
|
1893
|
+
td(t2.type),
|
|
1894
|
+
td(t2.amount, styles.mono),
|
|
1895
|
+
td(t2.description ?? "", styles.muted)
|
|
1896
|
+
])
|
|
1897
|
+
)
|
|
1898
|
+
),
|
|
1899
|
+
hasMoreLedger.value ? loadMoreButton(() => void loadMoreLedger()) : null
|
|
1900
|
+
]) : null
|
|
1901
|
+
]);
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
// src/screens/UsersScreen.ts
|
|
1907
|
+
var UsersScreen = (0, import_vue14.defineComponent)({
|
|
1712
1908
|
name: "FonderieUsersScreen",
|
|
1713
1909
|
props: {
|
|
1714
1910
|
client: { type: Object, required: true },
|
|
1715
|
-
pageSize: { type: Number, default: 50 }
|
|
1911
|
+
pageSize: { type: Number, default: 50 },
|
|
1912
|
+
// Given ⇒ a Plan column, and each user's plan, credits and grant form on
|
|
1913
|
+
// their page. Everyone has a wallet; no subscription means the free tier.
|
|
1914
|
+
billingClient: { type: Object, default: void 0 },
|
|
1915
|
+
// Open this user on arrival (e.g. from the Subscriptions list).
|
|
1916
|
+
openUserId: { type: String, default: void 0 }
|
|
1716
1917
|
},
|
|
1717
1918
|
setup(props) {
|
|
1718
|
-
const input = (0,
|
|
1719
|
-
const email = (0,
|
|
1720
|
-
const selectedId = (0,
|
|
1721
|
-
const
|
|
1722
|
-
const
|
|
1919
|
+
const input = (0, import_vue14.ref)("");
|
|
1920
|
+
const email = (0, import_vue14.ref)("");
|
|
1921
|
+
const selectedId = (0, import_vue14.ref)(props.openUserId ?? "");
|
|
1922
|
+
const plans = useSubscriptionIndex(props.billingClient);
|
|
1923
|
+
const list = (0, import_vue_admin11.useAdminUsers)(props.client, { limit: props.pageSize });
|
|
1924
|
+
const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_vue_admin11.useAdminUser)(
|
|
1723
1925
|
props.client,
|
|
1724
1926
|
{ email, id: selectedId }
|
|
1725
1927
|
);
|
|
1726
|
-
const userId = (0,
|
|
1727
|
-
const sessions = (0,
|
|
1728
|
-
const history = (0,
|
|
1729
|
-
const showList = (0,
|
|
1928
|
+
const userId = (0, import_vue14.computed)(() => user.value?.id ?? null);
|
|
1929
|
+
const sessions = (0, import_vue_admin11.useAdminUserSessions)(props.client, userId);
|
|
1930
|
+
const history = (0, import_vue_admin11.useAdminLoginHistory)(props.client, userId, { limit: 20 });
|
|
1931
|
+
const showList = (0, import_vue14.computed)(() => !email.value && !selectedId.value);
|
|
1730
1932
|
const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
|
|
1731
|
-
const row = (k, v) => (0,
|
|
1933
|
+
const row = (k, v) => (0, import_vue14.h)("tr", [td(k), td(v)]);
|
|
1934
|
+
const planCell = (id) => {
|
|
1935
|
+
if (!plans.value) return (0, import_vue14.h)("span", { style: styles.muted }, "\u2026");
|
|
1936
|
+
const sub = plans.value.get(`user/${id}`);
|
|
1937
|
+
if (!sub || sub.status === "canceled") return (0, import_vue14.h)("span", { style: styles.muted }, "free");
|
|
1938
|
+
return [
|
|
1939
|
+
(0, import_vue14.h)("strong", sub.plan),
|
|
1940
|
+
" ",
|
|
1941
|
+
sub.status !== "active" ? pill(statusTone(sub.status), sub.status) : null
|
|
1942
|
+
];
|
|
1943
|
+
};
|
|
1732
1944
|
const clear = () => {
|
|
1733
1945
|
email.value = "";
|
|
1734
1946
|
selectedId.value = "";
|
|
@@ -1736,12 +1948,12 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1736
1948
|
};
|
|
1737
1949
|
return () => {
|
|
1738
1950
|
const u = user.value;
|
|
1739
|
-
return (0,
|
|
1951
|
+
return (0, import_vue14.h)("div", { style: styles.container }, [
|
|
1740
1952
|
pageHeader(
|
|
1741
1953
|
"Users",
|
|
1742
1954
|
"Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
|
|
1743
1955
|
),
|
|
1744
|
-
(0,
|
|
1956
|
+
(0, import_vue14.h)(
|
|
1745
1957
|
"form",
|
|
1746
1958
|
{
|
|
1747
1959
|
style: styles.toolbar,
|
|
@@ -1752,7 +1964,7 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1752
1964
|
}
|
|
1753
1965
|
},
|
|
1754
1966
|
[
|
|
1755
|
-
(0,
|
|
1967
|
+
(0, import_vue14.h)("input", {
|
|
1756
1968
|
type: "email",
|
|
1757
1969
|
value: input.value,
|
|
1758
1970
|
onInput: (e) => input.value = e.target.value,
|
|
@@ -1760,22 +1972,22 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1760
1972
|
style: { ...styles.input, minWidth: "280px" },
|
|
1761
1973
|
"aria-label": "Email"
|
|
1762
1974
|
}),
|
|
1763
|
-
(0,
|
|
1975
|
+
(0, import_vue14.h)(
|
|
1764
1976
|
"button",
|
|
1765
1977
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1766
1978
|
[icon("search", 14), "Look up"]
|
|
1767
1979
|
),
|
|
1768
|
-
showList.value ?
|
|
1980
|
+
showList.value ? refreshButton2("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
|
|
1769
1981
|
]
|
|
1770
1982
|
),
|
|
1771
|
-
showList.value ? (0,
|
|
1772
|
-
list.error.value ? (0,
|
|
1983
|
+
showList.value ? (0, import_vue14.h)("div", [
|
|
1984
|
+
list.error.value ? (0, import_vue14.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1773
1985
|
list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
|
|
1774
|
-
["Email", "Name", "Created", "Status"],
|
|
1986
|
+
props.billingClient ? ["Email", "Name", "Plan", "Created", "Status"] : ["Email", "Name", "Created", "Status"],
|
|
1775
1987
|
list.users.value.map(
|
|
1776
|
-
(u2) => (0,
|
|
1988
|
+
(u2) => (0, import_vue14.h)("tr", { key: u2.id }, [
|
|
1777
1989
|
td(
|
|
1778
|
-
(0,
|
|
1990
|
+
(0, import_vue14.h)(
|
|
1779
1991
|
"button",
|
|
1780
1992
|
{
|
|
1781
1993
|
type: "button",
|
|
@@ -1786,8 +1998,9 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1786
1998
|
)
|
|
1787
1999
|
),
|
|
1788
2000
|
td(
|
|
1789
|
-
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || (0,
|
|
2001
|
+
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || (0, import_vue14.h)("span", { style: styles.muted }, "\u2014")
|
|
1790
2002
|
),
|
|
2003
|
+
props.billingClient ? td(planCell(u2.id)) : null,
|
|
1791
2004
|
td(new Date(u2.createdAt).toLocaleDateString()),
|
|
1792
2005
|
td(
|
|
1793
2006
|
u2.suspended ? pill("warn", "suspended") : u2.deletedAt ? pill("neutral", "deleted") : pill("ok", "active")
|
|
@@ -1795,24 +2008,42 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1795
2008
|
])
|
|
1796
2009
|
)
|
|
1797
2010
|
),
|
|
1798
|
-
list.isLoading.value ? (0,
|
|
2011
|
+
list.isLoading.value ? (0, import_vue14.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1799
2012
|
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
1800
2013
|
]) : null,
|
|
1801
|
-
error.value ? (0,
|
|
2014
|
+
error.value && !(error.value.status === 404 && selectedId.value) ? (0, import_vue14.h)(
|
|
1802
2015
|
"p",
|
|
1803
2016
|
{ style: styles.error, role: "alert" },
|
|
1804
2017
|
error.value.status === 404 ? "No user with that email." : error.value.explanation
|
|
1805
2018
|
) : null,
|
|
2019
|
+
// Opened by id (e.g. from Subscriptions) and the account is gone —
|
|
2020
|
+
// deleted or purged — while its billing rows remain. Say so, and
|
|
2021
|
+
// still show the money.
|
|
2022
|
+
error.value?.status === 404 && selectedId.value ? [
|
|
2023
|
+
(0, import_vue14.h)("div", { style: styles.notice, role: "status" }, [
|
|
2024
|
+
(0, import_vue14.h)("strong", "No account with id "),
|
|
2025
|
+
(0, import_vue14.h)("code", { style: styles.code }, selectedId.value),
|
|
2026
|
+
(0, import_vue14.h)("strong", "."),
|
|
2027
|
+
" It was deleted, or never existed here. Its billing records remain."
|
|
2028
|
+
]),
|
|
2029
|
+
props.billingClient ? (0, import_vue14.h)("h2", { style: styles.subtitle }, "Plan & credits") : null,
|
|
2030
|
+
props.billingClient ? (0, import_vue14.h)(SubscriberBilling, {
|
|
2031
|
+
key: `missing-${selectedId.value}`,
|
|
2032
|
+
client: props.billingClient,
|
|
2033
|
+
subscriberType: "user",
|
|
2034
|
+
subscriberId: selectedId.value
|
|
2035
|
+
}) : null
|
|
2036
|
+
] : null,
|
|
1806
2037
|
u ? [
|
|
1807
|
-
(0,
|
|
2038
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, [
|
|
1808
2039
|
u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
|
|
1809
2040
|
" ",
|
|
1810
2041
|
u.suspended ? pill("warn", "suspended") : null,
|
|
1811
2042
|
u.deletedAt ? pill("neutral", "deleted") : null
|
|
1812
2043
|
]),
|
|
1813
|
-
(0,
|
|
1814
|
-
(0,
|
|
1815
|
-
row("id", (0,
|
|
2044
|
+
(0, import_vue14.h)("table", { style: styles.table }, [
|
|
2045
|
+
(0, import_vue14.h)("tbody", [
|
|
2046
|
+
row("id", (0, import_vue14.h)("span", { style: styles.mono }, u.id)),
|
|
1816
2047
|
row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
|
|
1817
2048
|
row("MFA", yesNo(u.mfaEnabled)),
|
|
1818
2049
|
row(
|
|
@@ -1821,12 +2052,12 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1821
2052
|
),
|
|
1822
2053
|
row(
|
|
1823
2054
|
"last login",
|
|
1824
|
-
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0,
|
|
2055
|
+
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0, import_vue14.h)("span", { style: styles.muted }, "never")
|
|
1825
2056
|
),
|
|
1826
2057
|
row("created", new Date(u.createdAt).toLocaleString())
|
|
1827
2058
|
])
|
|
1828
2059
|
]),
|
|
1829
|
-
(0,
|
|
2060
|
+
(0, import_vue14.h)("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
|
|
1830
2061
|
u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
|
|
1831
2062
|
"Suspend",
|
|
1832
2063
|
isLoading.value,
|
|
@@ -1839,19 +2070,26 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1839
2070
|
() => void revokeSessions().then(() => sessions.refresh())
|
|
1840
2071
|
)
|
|
1841
2072
|
]),
|
|
1842
|
-
(0,
|
|
1843
|
-
|
|
2073
|
+
props.billingClient ? (0, import_vue14.h)("h2", { style: styles.subtitle }, "Plan & credits") : null,
|
|
2074
|
+
props.billingClient ? (0, import_vue14.h)(SubscriberBilling, {
|
|
2075
|
+
key: u.id,
|
|
2076
|
+
client: props.billingClient,
|
|
2077
|
+
subscriberType: "user",
|
|
2078
|
+
subscriberId: u.id
|
|
2079
|
+
}) : null,
|
|
2080
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, "Live sessions"),
|
|
2081
|
+
sessions.sessions.value.length === 0 ? (0, import_vue14.h)("p", { style: styles.muted }, "None.") : (0, import_vue14.h)(
|
|
1844
2082
|
"ul",
|
|
1845
2083
|
{ style: styles.list },
|
|
1846
2084
|
sessions.sessions.value.map(
|
|
1847
|
-
(s) => (0,
|
|
1848
|
-
(0,
|
|
2085
|
+
(s) => (0, import_vue14.h)("li", { key: s.id, style: styles.row }, [
|
|
2086
|
+
(0, import_vue14.h)("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
|
|
1849
2087
|
" ",
|
|
1850
|
-
(0,
|
|
2088
|
+
(0, import_vue14.h)("span", { style: styles.muted }, (0, import_client.describeLocation)(s.location) ?? ""),
|
|
1851
2089
|
" ",
|
|
1852
|
-
(0,
|
|
2090
|
+
(0, import_vue14.h)("span", { style: styles.muted }, s.userAgent ?? ""),
|
|
1853
2091
|
" ",
|
|
1854
|
-
(0,
|
|
2092
|
+
(0, import_vue14.h)(
|
|
1855
2093
|
"span",
|
|
1856
2094
|
{ style: styles.muted },
|
|
1857
2095
|
`since ${new Date(s.createdAt).toLocaleString()}`
|
|
@@ -1859,22 +2097,22 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1859
2097
|
])
|
|
1860
2098
|
)
|
|
1861
2099
|
),
|
|
1862
|
-
(0,
|
|
1863
|
-
history.events.value.length === 0 && !history.isLoading.value ? (0,
|
|
2100
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, "Recent sign-ins"),
|
|
2101
|
+
history.events.value.length === 0 && !history.isLoading.value ? (0, import_vue14.h)("p", { style: styles.muted }, "None recorded.") : (0, import_vue14.h)(
|
|
1864
2102
|
"ul",
|
|
1865
2103
|
{ style: styles.list },
|
|
1866
2104
|
history.events.value.map(
|
|
1867
|
-
(e) => (0,
|
|
2105
|
+
(e) => (0, import_vue14.h)("li", { key: e.id, style: styles.row }, [
|
|
1868
2106
|
pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
|
|
1869
2107
|
" ",
|
|
1870
|
-
(0,
|
|
2108
|
+
(0, import_vue14.h)("span", { style: styles.muted }, e.method),
|
|
1871
2109
|
" ",
|
|
1872
|
-
(0,
|
|
2110
|
+
(0, import_vue14.h)("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
|
|
1873
2111
|
" ",
|
|
1874
|
-
(0,
|
|
2112
|
+
(0, import_vue14.h)("span", { style: styles.muted }, (0, import_client.describeLocation)(e.location) ?? ""),
|
|
1875
2113
|
e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
|
|
1876
2114
|
" ",
|
|
1877
|
-
(0,
|
|
2115
|
+
(0, import_vue14.h)("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
|
|
1878
2116
|
])
|
|
1879
2117
|
)
|
|
1880
2118
|
),
|
|
@@ -1886,27 +2124,27 @@ var UsersScreen = (0, import_vue12.defineComponent)({
|
|
|
1886
2124
|
});
|
|
1887
2125
|
|
|
1888
2126
|
// src/screens/CatalogScreen.ts
|
|
1889
|
-
var
|
|
1890
|
-
var
|
|
1891
|
-
var CatalogScreen = (0,
|
|
2127
|
+
var import_vue_admin12 = require("@fonderie/vue-admin");
|
|
2128
|
+
var import_vue15 = require("vue");
|
|
2129
|
+
var CatalogScreen = (0, import_vue15.defineComponent)({
|
|
1892
2130
|
name: "FonderieCatalogScreen",
|
|
1893
2131
|
props: { client: { type: Object, required: true } },
|
|
1894
2132
|
setup(props) {
|
|
1895
|
-
const { catalog, isLoading, error, refresh, deletePlan } = (0,
|
|
2133
|
+
const { catalog, isLoading, error, refresh, deletePlan } = (0, import_vue_admin12.useAdminCatalog)(props.client);
|
|
1896
2134
|
const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
|
|
1897
2135
|
return () => {
|
|
1898
2136
|
const c = catalog.value;
|
|
1899
|
-
return (0,
|
|
2137
|
+
return (0, import_vue15.h)("div", { style: styles.container }, [
|
|
1900
2138
|
pageHeader(
|
|
1901
2139
|
"Catalog",
|
|
1902
2140
|
"What you sell: plans as configured in code, and as stored in the database, side by side.",
|
|
1903
|
-
[
|
|
2141
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
1904
2142
|
),
|
|
1905
|
-
error.value ? (0,
|
|
1906
|
-
isLoading.value && !c ? (0,
|
|
2143
|
+
error.value ? (0, import_vue15.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2144
|
+
isLoading.value && !c ? (0, import_vue15.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1907
2145
|
c ? [
|
|
1908
|
-
(0,
|
|
1909
|
-
(0,
|
|
2146
|
+
(0, import_vue15.h)("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
|
|
2147
|
+
(0, import_vue15.h)(
|
|
1910
2148
|
"pre",
|
|
1911
2149
|
{
|
|
1912
2150
|
style: {
|
|
@@ -1920,7 +2158,7 @@ var CatalogScreen = (0, import_vue13.defineComponent)({
|
|
|
1920
2158
|
},
|
|
1921
2159
|
JSON.stringify(c.configured, null, 2)
|
|
1922
2160
|
),
|
|
1923
|
-
(0,
|
|
2161
|
+
(0, import_vue15.h)("h2", { style: styles.subtitle }, "Stored (database)"),
|
|
1924
2162
|
c.stored.length === 0 ? empty(
|
|
1925
2163
|
"No stored plans",
|
|
1926
2164
|
"Plans are configured in code; nothing has been written to the database.",
|
|
@@ -1928,10 +2166,10 @@ var CatalogScreen = (0, import_vue13.defineComponent)({
|
|
|
1928
2166
|
) : table(
|
|
1929
2167
|
["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
|
|
1930
2168
|
c.stored.map(
|
|
1931
|
-
(p) => (0,
|
|
2169
|
+
(p) => (0, import_vue15.h)("tr", { key: p.id }, [
|
|
1932
2170
|
td([
|
|
1933
|
-
(0,
|
|
1934
|
-
p.description ? (0,
|
|
2171
|
+
(0, import_vue15.h)("strong", p.name),
|
|
2172
|
+
p.description ? (0, import_vue15.h)("div", { style: styles.muted }, p.description) : ""
|
|
1935
2173
|
]),
|
|
1936
2174
|
td(String(p.tier)),
|
|
1937
2175
|
td(p.seats == null ? "\u221E" : String(p.seats)),
|
|
@@ -1939,7 +2177,7 @@ var CatalogScreen = (0, import_vue13.defineComponent)({
|
|
|
1939
2177
|
td(money(p.pricing?.yearly)),
|
|
1940
2178
|
td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
|
|
1941
2179
|
td(
|
|
1942
|
-
(0,
|
|
2180
|
+
(0, import_vue15.h)(
|
|
1943
2181
|
"button",
|
|
1944
2182
|
{
|
|
1945
2183
|
type: "button",
|
|
@@ -1963,293 +2201,205 @@ var CatalogScreen = (0, import_vue13.defineComponent)({
|
|
|
1963
2201
|
});
|
|
1964
2202
|
|
|
1965
2203
|
// src/screens/SubscriberScreen.ts
|
|
1966
|
-
var
|
|
1967
|
-
var
|
|
1968
|
-
var
|
|
2204
|
+
var import_vue_admin13 = require("@fonderie/vue-admin");
|
|
2205
|
+
var import_vue16 = require("vue");
|
|
2206
|
+
var FILTERS = [
|
|
2207
|
+
{ label: "All", match: () => true },
|
|
2208
|
+
{ label: "Active", match: (s) => s === "active" },
|
|
2209
|
+
{ label: "Trialing", match: (s) => s === "trialing" },
|
|
2210
|
+
{ label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
|
|
2211
|
+
{ label: "Canceled", match: (s) => s === "canceled" }
|
|
2212
|
+
];
|
|
2213
|
+
var SubscriberScreen = (0, import_vue16.defineComponent)({
|
|
1969
2214
|
name: "FonderieSubscriberScreen",
|
|
1970
2215
|
props: {
|
|
1971
2216
|
client: { type: Object, required: true },
|
|
1972
|
-
pageSize: { type: Number, default: 50 }
|
|
2217
|
+
pageSize: { type: Number, default: 50 },
|
|
2218
|
+
/**
|
|
2219
|
+
* A user subscriber opens on the Users page, where their plan and credits
|
|
2220
|
+
* live beside their account. Omit to open everything here.
|
|
2221
|
+
*/
|
|
2222
|
+
onOpenUser: { type: Function, default: void 0 }
|
|
1973
2223
|
},
|
|
1974
2224
|
setup(props) {
|
|
1975
|
-
const
|
|
1976
|
-
const
|
|
1977
|
-
const
|
|
1978
|
-
const
|
|
1979
|
-
const
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
idInput.value = id;
|
|
1984
|
-
subscriber.value = { type: t2, id };
|
|
2225
|
+
const list = (0, import_vue_admin13.useAdminSubscribers)(props.client, { limit: props.pageSize });
|
|
2226
|
+
const filter = (0, import_vue16.ref)("All");
|
|
2227
|
+
const open = (0, import_vue16.ref)(null);
|
|
2228
|
+
const type = (0, import_vue16.ref)("workspace");
|
|
2229
|
+
const idInput = (0, import_vue16.ref)("");
|
|
2230
|
+
const openRow = (t2, id) => {
|
|
2231
|
+
if (t2 === "user" && props.onOpenUser) props.onOpenUser(id);
|
|
2232
|
+
else open.value = { type: t2, id };
|
|
1985
2233
|
};
|
|
1986
|
-
const amount = (0, import_vue14.ref)("");
|
|
1987
|
-
const note = (0, import_vue14.ref)("");
|
|
1988
|
-
const granted = (0, import_vue14.ref)(null);
|
|
1989
|
-
const input = (model, placeholder, extra = {}) => (0, import_vue14.h)("input", {
|
|
1990
|
-
value: model.value,
|
|
1991
|
-
onInput: (e) => model.value = e.target.value,
|
|
1992
|
-
placeholder,
|
|
1993
|
-
style: { ...styles.input, ...extra },
|
|
1994
|
-
"aria-label": placeholder
|
|
1995
|
-
});
|
|
1996
2234
|
return () => {
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
"Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
|
|
2003
|
-
),
|
|
2004
|
-
(0, import_vue14.h)(
|
|
2005
|
-
"form",
|
|
2006
|
-
{
|
|
2007
|
-
style: styles.toolbar,
|
|
2008
|
-
onSubmit: (e) => {
|
|
2009
|
-
e.preventDefault();
|
|
2010
|
-
granted.value = null;
|
|
2011
|
-
subscriber.value = idInput.value.trim() ? { type: type.value, id: idInput.value.trim() } : null;
|
|
2012
|
-
}
|
|
2013
|
-
},
|
|
2014
|
-
[
|
|
2015
|
-
(0, import_vue14.h)(
|
|
2016
|
-
"select",
|
|
2017
|
-
{
|
|
2018
|
-
value: type.value,
|
|
2019
|
-
onChange: (e) => type.value = e.target.value,
|
|
2020
|
-
style: styles.input,
|
|
2021
|
-
"aria-label": "Type"
|
|
2022
|
-
},
|
|
2023
|
-
[
|
|
2024
|
-
(0, import_vue14.h)("option", { value: "user" }, "user"),
|
|
2025
|
-
(0, import_vue14.h)("option", { value: "workspace" }, "workspace")
|
|
2026
|
-
]
|
|
2027
|
-
),
|
|
2028
|
-
input(idInput, "subscriber id", { minWidth: "280px" }),
|
|
2029
|
-
(0, import_vue14.h)(
|
|
2235
|
+
const o = open.value;
|
|
2236
|
+
if (o) {
|
|
2237
|
+
return (0, import_vue16.h)("div", { style: styles.container }, [
|
|
2238
|
+
pageHeader(`${o.type}/${o.id}`, "Plan, credits and what moved.", [
|
|
2239
|
+
(0, import_vue16.h)(
|
|
2030
2240
|
"button",
|
|
2031
|
-
{ type: "
|
|
2032
|
-
[icon("
|
|
2033
|
-
),
|
|
2034
|
-
subscriber.value ? (0, import_vue14.h)(
|
|
2035
|
-
"button",
|
|
2036
|
-
{
|
|
2037
|
-
type: "button",
|
|
2038
|
-
style: styles.button,
|
|
2039
|
-
onClick: () => {
|
|
2040
|
-
subscriber.value = null;
|
|
2041
|
-
idInput.value = "";
|
|
2042
|
-
granted.value = null;
|
|
2043
|
-
}
|
|
2044
|
-
},
|
|
2045
|
-
[icon("back", 14), "All subscribers"]
|
|
2046
|
-
) : (0, import_vue14.h)(
|
|
2047
|
-
"button",
|
|
2048
|
-
{
|
|
2049
|
-
type: "button",
|
|
2050
|
-
style: styles.button,
|
|
2051
|
-
disabled: list.isLoading.value,
|
|
2052
|
-
onClick: () => void list.refresh()
|
|
2053
|
-
},
|
|
2054
|
-
"Refresh"
|
|
2241
|
+
{ type: "button", style: styles.buttonGhost, onClick: () => open.value = null },
|
|
2242
|
+
[icon("back", 14), "All subscriptions"]
|
|
2055
2243
|
)
|
|
2056
|
-
]
|
|
2244
|
+
]),
|
|
2245
|
+
(0, import_vue16.h)(SubscriberBilling, {
|
|
2246
|
+
key: `${o.type}/${o.id}`,
|
|
2247
|
+
client: props.client,
|
|
2248
|
+
subscriberType: o.type,
|
|
2249
|
+
subscriberId: o.id
|
|
2250
|
+
})
|
|
2251
|
+
]);
|
|
2252
|
+
}
|
|
2253
|
+
const match = FILTERS.find((f) => f.label === filter.value)?.match ?? (() => true);
|
|
2254
|
+
const rows = list.subscriptions.value.filter((s) => match(s.status));
|
|
2255
|
+
return (0, import_vue16.h)("div", { style: styles.container }, [
|
|
2256
|
+
pageHeader(
|
|
2257
|
+
"Subscriptions",
|
|
2258
|
+
"Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
|
|
2259
|
+
[refreshButton(() => void list.refresh(), list.isLoading.value)]
|
|
2057
2260
|
),
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : (0, import_vue14.h)("span", { style: styles.muted }, "\u2014")
|
|
2089
|
-
)
|
|
2090
|
-
])
|
|
2091
|
-
)
|
|
2261
|
+
(0, import_vue16.h)("div", { style: { ...styles.toolbar, justifyContent: "space-between" } }, [
|
|
2262
|
+
(0, import_vue16.h)(
|
|
2263
|
+
"fieldset",
|
|
2264
|
+
{
|
|
2265
|
+
style: {
|
|
2266
|
+
display: "flex",
|
|
2267
|
+
gap: "6px",
|
|
2268
|
+
flexWrap: "wrap",
|
|
2269
|
+
border: "none",
|
|
2270
|
+
margin: 0,
|
|
2271
|
+
padding: 0,
|
|
2272
|
+
minWidth: 0
|
|
2273
|
+
},
|
|
2274
|
+
"aria-label": "Filter by status"
|
|
2275
|
+
},
|
|
2276
|
+
FILTERS.map((f) => {
|
|
2277
|
+
const n = list.subscriptions.value.filter((s) => f.match(s.status)).length;
|
|
2278
|
+
const on = filter.value === f.label;
|
|
2279
|
+
return (0, import_vue16.h)(
|
|
2280
|
+
"button",
|
|
2281
|
+
{
|
|
2282
|
+
key: f.label,
|
|
2283
|
+
type: "button",
|
|
2284
|
+
"aria-pressed": on,
|
|
2285
|
+
onClick: () => filter.value = f.label,
|
|
2286
|
+
style: { ...on ? styles.buttonPrimary : styles.button, height: "28px" }
|
|
2287
|
+
},
|
|
2288
|
+
[f.label, (0, import_vue16.h)("span", { style: { opacity: 0.7 } }, String(n))]
|
|
2289
|
+
);
|
|
2290
|
+
})
|
|
2092
2291
|
),
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
"button",
|
|
2292
|
+
(0, import_vue16.h)(
|
|
2293
|
+
"form",
|
|
2096
2294
|
{
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2295
|
+
style: { display: "flex", gap: "6px" },
|
|
2296
|
+
onSubmit: (e) => {
|
|
2297
|
+
e.preventDefault();
|
|
2298
|
+
if (idInput.value.trim()) openRow(type.value, idInput.value.trim());
|
|
2299
|
+
}
|
|
2100
2300
|
},
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
2143
|
-
}
|
|
2144
|
-
},
|
|
2145
|
-
w.balance
|
|
2146
|
-
),
|
|
2147
|
-
` ${w.currency} `,
|
|
2148
|
-
(0, import_vue14.h)(
|
|
2149
|
-
"span",
|
|
2150
|
-
{ style: styles.muted },
|
|
2151
|
-
`(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
|
|
2152
|
-
)
|
|
2153
|
-
]
|
|
2154
|
-
),
|
|
2155
|
-
(0, import_vue14.h)(
|
|
2156
|
-
"form",
|
|
2157
|
-
{
|
|
2158
|
-
style: styles.toolbar,
|
|
2159
|
-
onSubmit: (e) => {
|
|
2160
|
-
e.preventDefault();
|
|
2161
|
-
const sub = subscriber.value;
|
|
2162
|
-
if (!sub) return;
|
|
2163
|
-
const key = `admin-grant-${sub.type}-${sub.id}-${Date.now()}`;
|
|
2164
|
-
const amt = amount.value.trim();
|
|
2165
|
-
void grant({
|
|
2166
|
-
amount: amt,
|
|
2167
|
-
...note.value ? { description: note.value } : {},
|
|
2168
|
-
idempotencyKey: key
|
|
2169
|
-
}).then(() => {
|
|
2170
|
-
granted.value = `Granted ${amt} ${w.currency}.`;
|
|
2171
|
-
amount.value = "";
|
|
2172
|
-
note.value = "";
|
|
2173
|
-
}).catch(() => {
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2176
|
-
},
|
|
2177
|
-
[
|
|
2178
|
-
input(amount, "amount (minor units)"),
|
|
2179
|
-
input(note, "reason (optional)", { minWidth: "220px" }),
|
|
2180
|
-
(0, import_vue14.h)(
|
|
2301
|
+
[
|
|
2302
|
+
(0, import_vue16.h)(
|
|
2303
|
+
"select",
|
|
2304
|
+
{
|
|
2305
|
+
value: type.value,
|
|
2306
|
+
onChange: (e) => type.value = e.target.value,
|
|
2307
|
+
style: styles.input,
|
|
2308
|
+
"aria-label": "Type"
|
|
2309
|
+
},
|
|
2310
|
+
[
|
|
2311
|
+
(0, import_vue16.h)("option", { value: "workspace" }, "workspace"),
|
|
2312
|
+
(0, import_vue16.h)("option", { value: "user" }, "user")
|
|
2313
|
+
]
|
|
2314
|
+
),
|
|
2315
|
+
(0, import_vue16.h)("input", {
|
|
2316
|
+
value: idInput.value,
|
|
2317
|
+
onInput: (e) => idInput.value = e.target.value,
|
|
2318
|
+
placeholder: "subscriber id",
|
|
2319
|
+
style: { ...styles.input, width: "220px" },
|
|
2320
|
+
"aria-label": "Subscriber id"
|
|
2321
|
+
}),
|
|
2322
|
+
(0, import_vue16.h)(
|
|
2323
|
+
"button",
|
|
2324
|
+
{ type: "submit", style: styles.button, disabled: !idInput.value.trim() },
|
|
2325
|
+
[icon("search", 14), "Open"]
|
|
2326
|
+
)
|
|
2327
|
+
]
|
|
2328
|
+
)
|
|
2329
|
+
]),
|
|
2330
|
+
list.error.value ? (0, import_vue16.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
2331
|
+
rows.length === 0 && !list.isLoading.value ? empty(
|
|
2332
|
+
filter.value === "All" ? "No subscriptions yet" : `No ${filter.value.toLowerCase()} subscriptions`,
|
|
2333
|
+
"Subscriptions appear here once someone checks out.",
|
|
2334
|
+
"subscriber"
|
|
2335
|
+
) : table(
|
|
2336
|
+
["Subscriber", "Plan", "Status", "Renews / ends"],
|
|
2337
|
+
rows.map((s) => {
|
|
2338
|
+
const end = periodEnd(s);
|
|
2339
|
+
return (0, import_vue16.h)("tr", { key: s.id }, [
|
|
2340
|
+
td(
|
|
2341
|
+
(0, import_vue16.h)(
|
|
2181
2342
|
"button",
|
|
2182
2343
|
{
|
|
2183
|
-
type: "
|
|
2184
|
-
style: styles.
|
|
2185
|
-
|
|
2344
|
+
type: "button",
|
|
2345
|
+
style: { ...styles.link, ...styles.mono },
|
|
2346
|
+
onClick: () => openRow(s.subscriberType, s.subscriberId)
|
|
2186
2347
|
},
|
|
2187
|
-
|
|
2188
|
-
)
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
(
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2348
|
+
`${s.subscriberType}/${s.subscriberId}`
|
|
2349
|
+
)
|
|
2350
|
+
),
|
|
2351
|
+
td([
|
|
2352
|
+
(0, import_vue16.h)("strong", s.plan),
|
|
2353
|
+
" ",
|
|
2354
|
+
(0, import_vue16.h)("span", { style: styles.muted }, `\xB7 ${s.interval}`)
|
|
2355
|
+
]),
|
|
2356
|
+
td([
|
|
2357
|
+
pill(statusTone(s.status), s.status),
|
|
2358
|
+
s.cancelAtPeriodEnd && s.status !== "canceled" ? (0, import_vue16.h)("span", [" ", pill("warn", "cancels", false)]) : null
|
|
2359
|
+
]),
|
|
2360
|
+
td(
|
|
2361
|
+
end ? [(0, import_vue16.h)("span", { style: styles.muted }, end.label), ` ${end.date}`] : (0, import_vue16.h)("span", { style: styles.muted }, "\u2014"),
|
|
2362
|
+
{ whiteSpace: "nowrap" }
|
|
2202
2363
|
)
|
|
2203
|
-
)
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
2209
|
-
onClick: () => void loadMoreLedger()
|
|
2210
|
-
},
|
|
2211
|
-
"Load more"
|
|
2212
|
-
) : null
|
|
2213
|
-
] : (0, import_vue14.h)(
|
|
2214
|
-
"p",
|
|
2215
|
-
{ style: styles.muted },
|
|
2216
|
-
"No wallet \u2014 billing has no wallet configuration."
|
|
2217
|
-
)
|
|
2218
|
-
] : null
|
|
2364
|
+
]);
|
|
2365
|
+
})
|
|
2366
|
+
),
|
|
2367
|
+
list.isLoading.value ? (0, import_vue16.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2368
|
+
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
2219
2369
|
]);
|
|
2220
2370
|
};
|
|
2221
2371
|
}
|
|
2222
2372
|
});
|
|
2223
2373
|
|
|
2224
2374
|
// src/screens/AuditScreen.ts
|
|
2225
|
-
var
|
|
2226
|
-
var
|
|
2227
|
-
var AuditScreen = (0,
|
|
2375
|
+
var import_vue_admin14 = require("@fonderie/vue-admin");
|
|
2376
|
+
var import_vue17 = require("vue");
|
|
2377
|
+
var AuditScreen = (0, import_vue17.defineComponent)({
|
|
2228
2378
|
name: "FonderieAuditScreen",
|
|
2229
2379
|
props: {
|
|
2230
2380
|
client: { type: Object, required: true },
|
|
2231
2381
|
pageSize: { type: Number, default: 50 }
|
|
2232
2382
|
},
|
|
2233
2383
|
setup(props) {
|
|
2234
|
-
const draft = (0,
|
|
2235
|
-
const query = (0,
|
|
2236
|
-
const { events, hasMore, isLoading, error, refresh, loadMore } = (0,
|
|
2384
|
+
const draft = (0, import_vue17.ref)({ workspaceId: "", type: "", actorId: "" });
|
|
2385
|
+
const query = (0, import_vue17.ref)({ limit: props.pageSize });
|
|
2386
|
+
const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_vue_admin14.useAdminAudit)(
|
|
2237
2387
|
props.client,
|
|
2238
2388
|
query
|
|
2239
2389
|
);
|
|
2240
|
-
const field = (key, placeholder) => (0,
|
|
2390
|
+
const field = (key, placeholder) => (0, import_vue17.h)("input", {
|
|
2241
2391
|
value: draft.value[key],
|
|
2242
2392
|
onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
|
|
2243
2393
|
placeholder,
|
|
2244
2394
|
style: styles.input,
|
|
2245
2395
|
"aria-label": placeholder
|
|
2246
2396
|
});
|
|
2247
|
-
return () => (0,
|
|
2397
|
+
return () => (0, import_vue17.h)("div", { style: styles.container }, [
|
|
2248
2398
|
pageHeader(
|
|
2249
2399
|
"Audit",
|
|
2250
2400
|
"What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
|
|
2251
2401
|
),
|
|
2252
|
-
(0,
|
|
2402
|
+
(0, import_vue17.h)(
|
|
2253
2403
|
"form",
|
|
2254
2404
|
{
|
|
2255
2405
|
style: styles.toolbar,
|
|
@@ -2268,19 +2418,19 @@ var AuditScreen = (0, import_vue15.defineComponent)({
|
|
|
2268
2418
|
field("workspaceId", "workspace id (all if empty)"),
|
|
2269
2419
|
field("type", "event type"),
|
|
2270
2420
|
field("actorId", "actor id"),
|
|
2271
|
-
(0,
|
|
2421
|
+
(0, import_vue17.h)(
|
|
2272
2422
|
"button",
|
|
2273
2423
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
2274
2424
|
[icon("search", 14), "Filter"]
|
|
2275
2425
|
),
|
|
2276
|
-
|
|
2426
|
+
refreshButton2("Refresh", isLoading.value, () => void refresh())
|
|
2277
2427
|
]
|
|
2278
2428
|
),
|
|
2279
|
-
error.value ? (0,
|
|
2429
|
+
error.value ? (0, import_vue17.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2280
2430
|
events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
|
|
2281
2431
|
["When", "Type", "Workspace", "Actor", "Request"],
|
|
2282
2432
|
events.value.map(
|
|
2283
|
-
(e) => (0,
|
|
2433
|
+
(e) => (0, import_vue17.h)("tr", { key: e.id }, [
|
|
2284
2434
|
td(new Date(e.createdAt).toLocaleString(), styles.muted),
|
|
2285
2435
|
td(e.type, styles.mono),
|
|
2286
2436
|
td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
|
|
@@ -2289,7 +2439,7 @@ var AuditScreen = (0, import_vue15.defineComponent)({
|
|
|
2289
2439
|
])
|
|
2290
2440
|
)
|
|
2291
2441
|
),
|
|
2292
|
-
isLoading.value ? (0,
|
|
2442
|
+
isLoading.value ? (0, import_vue17.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2293
2443
|
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
2294
2444
|
]);
|
|
2295
2445
|
}
|
|
@@ -2312,7 +2462,7 @@ var NAV = [
|
|
|
2312
2462
|
group: "Money",
|
|
2313
2463
|
items: [
|
|
2314
2464
|
{ page: "catalog", label: "Catalog", needs: "billing" },
|
|
2315
|
-
{ page: "subscriber", label: "
|
|
2465
|
+
{ page: "subscriber", label: "Subscriptions", needs: "billing" }
|
|
2316
2466
|
]
|
|
2317
2467
|
},
|
|
2318
2468
|
{ group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
|
|
@@ -2346,7 +2496,7 @@ var ICON = {
|
|
|
2346
2496
|
migrations: "migrations"
|
|
2347
2497
|
};
|
|
2348
2498
|
var NARROW = "(max-width: 820px)";
|
|
2349
|
-
var AdminShell = (0,
|
|
2499
|
+
var AdminShell = (0, import_vue18.defineComponent)({
|
|
2350
2500
|
name: "FonderieAdminShell",
|
|
2351
2501
|
props: {
|
|
2352
2502
|
client: { type: Object, required: true },
|
|
@@ -2380,60 +2530,74 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2380
2530
|
emits: { navigate: (_page) => true },
|
|
2381
2531
|
setup(props, { emit, slots }) {
|
|
2382
2532
|
const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
|
|
2383
|
-
const narrow = (0,
|
|
2384
|
-
const drawer = (0,
|
|
2533
|
+
const narrow = (0, import_vue18.ref)(query?.matches ?? false);
|
|
2534
|
+
const drawer = (0, import_vue18.ref)(false);
|
|
2385
2535
|
const onMedia = (e) => narrow.value = e.matches;
|
|
2386
|
-
(0,
|
|
2387
|
-
(0,
|
|
2388
|
-
const own = (0,
|
|
2389
|
-
const current = (0,
|
|
2390
|
-
const editing = (0,
|
|
2536
|
+
(0, import_vue18.onMounted)(() => query?.addEventListener("change", onMedia));
|
|
2537
|
+
(0, import_vue18.onBeforeUnmount)(() => query?.removeEventListener("change", onMedia));
|
|
2538
|
+
const own = (0, import_vue18.ref)("attention");
|
|
2539
|
+
const current = (0, import_vue18.computed)(() => props.page ?? own.value);
|
|
2540
|
+
const editing = (0, import_vue18.ref)(null);
|
|
2541
|
+
const openUser = (0, import_vue18.ref)(void 0);
|
|
2391
2542
|
const go = (p) => {
|
|
2392
2543
|
editing.value = null;
|
|
2393
2544
|
emit("navigate", p);
|
|
2394
2545
|
if (props.page === void 0) own.value = p;
|
|
2395
2546
|
drawer.value = false;
|
|
2396
2547
|
};
|
|
2397
|
-
const back = (label) => (0,
|
|
2398
|
-
(0,
|
|
2548
|
+
const back = (label) => (0, import_vue18.h)("div", { style: { padding: "24px 40px 0" } }, [
|
|
2549
|
+
(0, import_vue18.h)(
|
|
2399
2550
|
"button",
|
|
2400
2551
|
{ type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
|
|
2401
2552
|
[icon("back", 14), label]
|
|
2402
2553
|
)
|
|
2403
2554
|
]);
|
|
2404
|
-
const missing = (text) => (0,
|
|
2555
|
+
const missing = (text) => (0, import_vue18.h)("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
|
|
2405
2556
|
const body = () => {
|
|
2406
2557
|
const c = props.client;
|
|
2407
2558
|
switch (current.value) {
|
|
2408
2559
|
case "attention":
|
|
2409
|
-
return (0,
|
|
2560
|
+
return (0, import_vue18.h)(AttentionScreen, { client: c });
|
|
2410
2561
|
case "modules":
|
|
2411
|
-
return (0,
|
|
2562
|
+
return (0, import_vue18.h)(ModulesScreen, { client: c });
|
|
2412
2563
|
case "doctor":
|
|
2413
|
-
return (0,
|
|
2564
|
+
return (0, import_vue18.h)(DoctorScreen, { client: c });
|
|
2414
2565
|
case "environment":
|
|
2415
|
-
return (0,
|
|
2566
|
+
return (0, import_vue18.h)(EnvironmentScreen, { client: c });
|
|
2416
2567
|
case "routes":
|
|
2417
|
-
return (0,
|
|
2568
|
+
return (0, import_vue18.h)(RoutesScreen, { client: c });
|
|
2418
2569
|
case "tokens":
|
|
2419
|
-
return (0,
|
|
2570
|
+
return (0, import_vue18.h)(TokensScreen, { client: c });
|
|
2420
2571
|
case "operators":
|
|
2421
|
-
return (0,
|
|
2572
|
+
return (0, import_vue18.h)(OperatorsScreen, {
|
|
2422
2573
|
client: c,
|
|
2423
2574
|
...props.currentOperator ? { me: props.currentOperator } : {}
|
|
2424
2575
|
});
|
|
2425
2576
|
case "migrations":
|
|
2426
|
-
return (0,
|
|
2577
|
+
return (0, import_vue18.h)(MigrationsScreen, { client: c });
|
|
2427
2578
|
case "log":
|
|
2428
|
-
return (0,
|
|
2579
|
+
return (0, import_vue18.h)(AdminLogScreen, { client: c });
|
|
2429
2580
|
case "catalog":
|
|
2430
|
-
return props.billingClient ? (0,
|
|
2581
|
+
return props.billingClient ? (0, import_vue18.h)(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
|
|
2431
2582
|
case "subscriber":
|
|
2432
|
-
return props.billingClient ? (0,
|
|
2583
|
+
return props.billingClient ? (0, import_vue18.h)(SubscriberScreen, {
|
|
2584
|
+
client: props.billingClient,
|
|
2585
|
+
...props.authClient ? {
|
|
2586
|
+
onOpenUser: (id) => {
|
|
2587
|
+
openUser.value = id;
|
|
2588
|
+
go("users");
|
|
2589
|
+
}
|
|
2590
|
+
} : {}
|
|
2591
|
+
}) : missing("Pass a BillingAdminClient to look up subscribers here.");
|
|
2433
2592
|
case "audit":
|
|
2434
|
-
return props.auditClient ? (0,
|
|
2593
|
+
return props.auditClient ? (0, import_vue18.h)(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
|
|
2435
2594
|
case "users":
|
|
2436
|
-
return props.authClient ? (0,
|
|
2595
|
+
return props.authClient ? (0, import_vue18.h)(UsersScreen, {
|
|
2596
|
+
key: openUser.value ?? "list",
|
|
2597
|
+
client: props.authClient,
|
|
2598
|
+
...props.billingClient ? { billingClient: props.billingClient } : {},
|
|
2599
|
+
...openUser.value ? { openUserId: openUser.value } : {}
|
|
2600
|
+
}) : missing("Pass an AuthAdminClient to look up users here.");
|
|
2437
2601
|
case "settings": {
|
|
2438
2602
|
const cc = props.configClient;
|
|
2439
2603
|
if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
|
|
@@ -2441,7 +2605,7 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2441
2605
|
if (e && e.kind !== "template")
|
|
2442
2606
|
return [
|
|
2443
2607
|
back("Config & secrets"),
|
|
2444
|
-
(0,
|
|
2608
|
+
(0, import_vue18.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
|
|
2445
2609
|
client: cc,
|
|
2446
2610
|
kind: e.kind,
|
|
2447
2611
|
configKey: e.key,
|
|
@@ -2449,7 +2613,7 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2449
2613
|
onSaved: () => editing.value = null
|
|
2450
2614
|
})
|
|
2451
2615
|
];
|
|
2452
|
-
return (0,
|
|
2616
|
+
return (0, import_vue18.h)(import_vue_config_admin_screens.ConfigListScreen, {
|
|
2453
2617
|
client: cc,
|
|
2454
2618
|
...props.environment !== void 0 ? { environment: props.environment } : {},
|
|
2455
2619
|
"onSelect-config": (key) => editing.value = { kind: "config", key },
|
|
@@ -2466,13 +2630,13 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2466
2630
|
if (e?.kind === "template")
|
|
2467
2631
|
return [
|
|
2468
2632
|
back("Templates"),
|
|
2469
|
-
(0,
|
|
2633
|
+
(0, import_vue18.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
|
|
2470
2634
|
client: kc,
|
|
2471
2635
|
type: e.type,
|
|
2472
2636
|
onSaved: () => editing.value = null
|
|
2473
2637
|
})
|
|
2474
2638
|
];
|
|
2475
|
-
return (0,
|
|
2639
|
+
return (0, import_vue18.h)(import_vue_courier_admin_screens.TemplateListScreen, {
|
|
2476
2640
|
client: kc,
|
|
2477
2641
|
"onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
|
|
2478
2642
|
});
|
|
@@ -2482,11 +2646,11 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2482
2646
|
const visible = (i) => !i.needs || (i.needs === "operators" ? props.operators : i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient);
|
|
2483
2647
|
const brand = () => {
|
|
2484
2648
|
const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
|
|
2485
|
-
return (0,
|
|
2486
|
-
(0,
|
|
2487
|
-
(0,
|
|
2488
|
-
(0,
|
|
2489
|
-
props.envLabel ? (0,
|
|
2649
|
+
return (0, import_vue18.h)("div", { style: styles.navBrand }, [
|
|
2650
|
+
(0, import_vue18.h)("div", { style: styles.navMark }, [icon("mark", 15)]),
|
|
2651
|
+
(0, import_vue18.h)("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
2652
|
+
(0, import_vue18.h)("div", { style: styles.navBrandTitle }, props.appName),
|
|
2653
|
+
props.envLabel ? (0, import_vue18.h)(
|
|
2490
2654
|
"span",
|
|
2491
2655
|
{
|
|
2492
2656
|
style: {
|
|
@@ -2503,7 +2667,7 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2503
2667
|
props.envLabel
|
|
2504
2668
|
) : null
|
|
2505
2669
|
]),
|
|
2506
|
-
narrow.value ? (0,
|
|
2670
|
+
narrow.value ? (0, import_vue18.h)(
|
|
2507
2671
|
"button",
|
|
2508
2672
|
{
|
|
2509
2673
|
type: "button",
|
|
@@ -2515,7 +2679,7 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2515
2679
|
) : null
|
|
2516
2680
|
]);
|
|
2517
2681
|
};
|
|
2518
|
-
const nav = () => (0,
|
|
2682
|
+
const nav = () => (0, import_vue18.h)(
|
|
2519
2683
|
"nav",
|
|
2520
2684
|
{
|
|
2521
2685
|
style: {
|
|
@@ -2526,17 +2690,17 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2526
2690
|
},
|
|
2527
2691
|
[
|
|
2528
2692
|
brand(),
|
|
2529
|
-
(0,
|
|
2693
|
+
(0, import_vue18.h)(
|
|
2530
2694
|
"div",
|
|
2531
2695
|
{ style: styles.navScroll },
|
|
2532
2696
|
NAV.map((g) => {
|
|
2533
2697
|
const items = g.items.filter(visible);
|
|
2534
2698
|
if (items.length === 0) return null;
|
|
2535
|
-
return (0,
|
|
2536
|
-
(0,
|
|
2699
|
+
return (0, import_vue18.h)("div", { key: g.group }, [
|
|
2700
|
+
(0, import_vue18.h)("div", { style: styles.navGroup }, g.group),
|
|
2537
2701
|
...items.map((i) => {
|
|
2538
2702
|
const active = current.value === i.page;
|
|
2539
|
-
return (0,
|
|
2703
|
+
return (0, import_vue18.h)(
|
|
2540
2704
|
"button",
|
|
2541
2705
|
{
|
|
2542
2706
|
key: i.page,
|
|
@@ -2544,7 +2708,10 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2544
2708
|
class: "fonderie-admin-nav-item",
|
|
2545
2709
|
style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
|
|
2546
2710
|
"aria-current": active ? "page" : void 0,
|
|
2547
|
-
onClick: () =>
|
|
2711
|
+
onClick: () => {
|
|
2712
|
+
openUser.value = void 0;
|
|
2713
|
+
go(i.page);
|
|
2714
|
+
}
|
|
2548
2715
|
},
|
|
2549
2716
|
[icon(ICON[i.page], 16, active ? styles.navIconActive : void 0), i.label]
|
|
2550
2717
|
);
|
|
@@ -2552,18 +2719,18 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2552
2719
|
]);
|
|
2553
2720
|
})
|
|
2554
2721
|
),
|
|
2555
|
-
slots["footer"] ? (0,
|
|
2722
|
+
slots["footer"] ? (0, import_vue18.h)("div", { style: styles.navFooter }, slots["footer"]()) : null
|
|
2556
2723
|
]
|
|
2557
2724
|
);
|
|
2558
2725
|
return () => {
|
|
2559
2726
|
const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
|
|
2560
|
-
return (0,
|
|
2727
|
+
return (0, import_vue18.h)(
|
|
2561
2728
|
"div",
|
|
2562
2729
|
{ style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
|
|
2563
2730
|
[
|
|
2564
2731
|
...narrow.value ? [
|
|
2565
|
-
(0,
|
|
2566
|
-
(0,
|
|
2732
|
+
(0, import_vue18.h)("div", { style: styles.topbar }, [
|
|
2733
|
+
(0, import_vue18.h)(
|
|
2567
2734
|
"button",
|
|
2568
2735
|
{
|
|
2569
2736
|
type: "button",
|
|
@@ -2574,19 +2741,19 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2574
2741
|
},
|
|
2575
2742
|
[icon("menu")]
|
|
2576
2743
|
),
|
|
2577
|
-
(0,
|
|
2578
|
-
props.envLabel ? (0,
|
|
2744
|
+
(0, import_vue18.h)("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
|
|
2745
|
+
props.envLabel ? (0, import_vue18.h)("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
|
|
2579
2746
|
]),
|
|
2580
|
-
drawer.value ? (0,
|
|
2747
|
+
drawer.value ? (0, import_vue18.h)("div", { style: styles.drawer }, [
|
|
2581
2748
|
nav(),
|
|
2582
|
-
(0,
|
|
2749
|
+
(0, import_vue18.h)("div", {
|
|
2583
2750
|
style: styles.scrim,
|
|
2584
2751
|
"aria-hidden": "true",
|
|
2585
2752
|
onClick: () => drawer.value = false
|
|
2586
2753
|
})
|
|
2587
2754
|
]) : null
|
|
2588
2755
|
] : [nav()],
|
|
2589
|
-
(0,
|
|
2756
|
+
(0, import_vue18.h)("main", { class: "fonderie-admin-main", style: styles.main }, body())
|
|
2590
2757
|
]
|
|
2591
2758
|
);
|
|
2592
2759
|
};
|
|
@@ -2605,6 +2772,7 @@ var AdminShell = (0, import_vue16.defineComponent)({
|
|
|
2605
2772
|
ModulesScreen,
|
|
2606
2773
|
OperatorsScreen,
|
|
2607
2774
|
RoutesScreen,
|
|
2775
|
+
SubscriberBilling,
|
|
2608
2776
|
SubscriberScreen,
|
|
2609
2777
|
TokensScreen,
|
|
2610
2778
|
UsersScreen
|