Feature admin only numbers & carrier (#587)

* Hide create carrier/number controls if ADMIN_CARRIER env var is set

* hide add caririer button

* add to example .env

* hide delete phone number
This commit is contained in:
Sam Machin
2026-01-07 13:01:12 +00:00
committed by GitHub
parent c33eb46ce0
commit c9fcdb08eb
5 changed files with 74 additions and 40 deletions

2
.env
View File

@@ -32,3 +32,5 @@
#VITE_APP_AWS_REGION=us-west-2 #VITE_APP_AWS_REGION=us-west-2
## enable lazy loading for phone numbers (improves performance when managing large quantities) ## enable lazy loading for phone numbers (improves performance when managing large quantities)
# VITE_APP_ENABLE_PHONE_NUMBER_LAZY_LOAD=true # VITE_APP_ENABLE_PHONE_NUMBER_LAZY_LOAD=true
# hides controlls to add Carrier and Phone number from non Admin/SP Users (also need to set flag on API server to block API calls)
#VITE_ADMIN_CARRIER=1

View File

@@ -18,7 +18,7 @@ DISABLE_CALL_RECORDING=${DISABLE_CALL_RECORDING:-false}
# Serialize window global to provide the API URL to static frontend dist # Serialize window global to provide the API URL to static frontend dist
# This is declared and utilized in the web app: src/api/constants.ts # This is declared and utilized in the web app: src/api/constants.ts
SCRIPT_TAG="<script>window.JAMBONZ = {API_BASE_URL: \"${API_BASE_URL}\",DISABLE_LCR: \"${DISABLE_LCR}\",DISABLE_JAEGER_TRACING: \"${DISABLE_JAEGER_TRACING}\",DISABLE_CUSTOM_SPEECH: \"${DISABLE_CUSTOM_SPEECH}\",ENABLE_FORGOT_PASSWORD: \"${ENABLE_FORGOT_PASSWORD}\",DISABLE_CALL_RECORDING: \"${DISABLE_CALL_RECORDING}\"};</script>" SCRIPT_TAG="<script>window.JAMBONZ = {API_BASE_URL: \"${API_BASE_URL}\",DISABLE_LCR: \"${DISABLE_LCR}\",DISABLE_JAEGER_TRACING: \"${DISABLE_JAEGER_TRACING}\",DISABLE_CUSTOM_SPEECH: \"${DISABLE_CUSTOM_SPEECH}\",ENABLE_FORGOT_PASSWORD: \"${ENABLE_FORGOT_PASSWORD}\",DISABLE_CALL_RECORDING: \"${DISABLE_CALL_RECORDING}\",ADMIN_CARRIER: \"${ADMIN_CARRIER}\"};</script>"
sed -i -e "\@</head>@i\ $SCRIPT_TAG" ./dist/index.html sed -i -e "\@</head>@i\ $SCRIPT_TAG" ./dist/index.html
# Start the frontend web app static server # Start the frontend web app static server

View File

@@ -35,6 +35,7 @@ interface JambonzWindowObject {
DISABLE_ADDITIONAL_SPEECH_VENDORS: string; DISABLE_ADDITIONAL_SPEECH_VENDORS: string;
AWS_REGION: string; AWS_REGION: string;
ENABLE_PHONE_NUMBER_LAZY_LOAD: string; ENABLE_PHONE_NUMBER_LAZY_LOAD: string;
ADMIN_CARRIER: string;
} }
declare global { declare global {
@@ -110,6 +111,8 @@ export const STRIPE_PUBLISHABLE_KEY: string =
window.JAMBONZ?.STRIPE_PUBLISHABLE_KEY || window.JAMBONZ?.STRIPE_PUBLISHABLE_KEY ||
import.meta.env.VITE_APP_STRIPE_PUBLISHABLE_KEY; import.meta.env.VITE_APP_STRIPE_PUBLISHABLE_KEY;
export const ADMIN_CARRIER: string =
window.JAMBONZ?.ADMIN_CARRIER || import.meta.env.VITE_ADMIN_CARRIER || "0";
/** TCP Max Port */ /** TCP Max Port */
export const TCP_MAX_PORT = 65535; export const TCP_MAX_PORT = 65535;

View File

@@ -31,6 +31,9 @@ import {
ENABLE_HOSTED_SYSTEM, ENABLE_HOSTED_SYSTEM,
PER_PAGE_SELECTION, PER_PAGE_SELECTION,
USER_ACCOUNT, USER_ACCOUNT,
ADMIN_CARRIER,
USER_ADMIN,
USER_SP,
} from "src/api/constants"; } from "src/api/constants";
import { DeleteCarrier } from "./delete"; import { DeleteCarrier } from "./delete";
@@ -202,13 +205,16 @@ export const Carriers = () => {
</M> </M>
)} )}
</div> </div>
{((ADMIN_CARRIER === "1" &&
<Link to={`${ROUTE_INTERNAL_CARRIERS}/add`} title="Add a Carrier"> (user?.scope === USER_ADMIN || user?.scope === USER_SP)) ||
{" "} ADMIN_CARRIER === "0") && (
<Icon> <Link to={`${ROUTE_INTERNAL_CARRIERS}/add`} title="Add a Carrier">
<Icons.Plus /> {" "}
</Icon> <Icon>
</Link> <Icons.Plus />
</Icon>
</Link>
)}
</section> </section>
<section className="filters filters--multi"> <section className="filters filters--multi">
<SearchFilter <SearchFilter
@@ -325,11 +331,15 @@ export const Carriers = () => {
)} )}
</div> </div>
</Section> </Section>
<Section clean> {((ADMIN_CARRIER === "1" &&
<Button small as={Link} to={`${ROUTE_INTERNAL_CARRIERS}/add`}> (user?.scope === USER_ADMIN || user?.scope === USER_SP)) ||
Add carrier ADMIN_CARRIER === "0") && (
</Button> <Section clean>
</Section> <Button small as={Link} to={`${ROUTE_INTERNAL_CARRIERS}/add`}>
Add carrier
</Button>
</Section>
)}
<footer> <footer>
<ButtonGroup> <ButtonGroup>
<MS> <MS>

View File

@@ -28,7 +28,13 @@ import { hasLength, hasValue, formatPhoneNumber } from "src/utils";
import { DeletePhoneNumber } from "./delete"; import { DeletePhoneNumber } from "./delete";
import type { Account, PhoneNumber, Carrier, Application } from "src/api/types"; import type { Account, PhoneNumber, Carrier, Application } from "src/api/types";
import { PER_PAGE_SELECTION, USER_ACCOUNT } from "src/api/constants"; import {
PER_PAGE_SELECTION,
USER_ACCOUNT,
USER_ADMIN,
ADMIN_CARRIER,
USER_SP,
} from "src/api/constants";
import { ScopedAccess } from "src/components/scoped-access"; import { ScopedAccess } from "src/components/scoped-access";
import { Scope } from "src/store/types"; import { Scope } from "src/store/types";
import { getAccountFilter, setLocation } from "src/store/localStore"; import { getAccountFilter, setLocation } from "src/store/localStore";
@@ -185,16 +191,20 @@ export const PhoneNumbers = () => {
<> <>
<section className="mast"> <section className="mast">
<H1 className="h2">Phone numbers</H1> <H1 className="h2">Phone numbers</H1>
{hasLength(accounts) && hasLength(carriers) && ( {hasLength(accounts) &&
<Link hasLength(carriers) &&
to={`${ROUTE_INTERNAL_PHONE_NUMBERS}/add`} ((ADMIN_CARRIER === "1" &&
title="Add a phone number" (user?.scope === USER_ADMIN || user?.scope === USER_SP)) ||
> ADMIN_CARRIER === "0") && (
<Icon> <Link
<Icons.Plus /> to={`${ROUTE_INTERNAL_PHONE_NUMBERS}/add`}
</Icon> title="Add a phone number"
</Link> >
)} <Icon>
<Icons.Plus />
</Icon>
</Link>
)}
</section> </section>
<section className="filters filters--multi"> <section className="filters filters--multi">
<SearchFilter <SearchFilter
@@ -368,14 +378,19 @@ export const PhoneNumbers = () => {
> >
<Icons.Edit3 /> <Icons.Edit3 />
</Link> </Link>
<button {((ADMIN_CARRIER === "1" &&
type="button" (user?.scope === USER_ADMIN ||
title="Delete phone number" user?.scope === USER_SP)) ||
onClick={() => setPhoneNumber(phoneNumber)} ADMIN_CARRIER === "0") && (
className="btnty" <button
> type="button"
<Icons.Trash /> title="Delete phone number"
</button> onClick={() => setPhoneNumber(phoneNumber)}
className="btnty"
>
<Icons.Trash />
</button>
)}
</div> </div>
</div> </div>
); );
@@ -404,13 +419,17 @@ export const PhoneNumbers = () => {
)} )}
</div> </div>
</Section> </Section>
<Section clean> {((ADMIN_CARRIER === "1" &&
{hasLength(accounts) && hasLength(carriers) && ( (user?.scope === USER_ADMIN || user?.scope === USER_SP)) ||
<Button small as={Link} to={`${ROUTE_INTERNAL_PHONE_NUMBERS}/add`}> ADMIN_CARRIER === "0") && (
Add phone number <Section clean>
</Button> {hasLength(accounts) && hasLength(carriers) && (
)} <Button small as={Link} to={`${ROUTE_INTERNAL_PHONE_NUMBERS}/add`}>
</Section> Add phone number
</Button>
)}
</Section>
)}
<footer> <footer>
<ButtonGroup> <ButtonGroup>
<MS> <MS>