mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-07-24 04:52:05 +00:00
apply review comments
This commit is contained in:
@@ -106,7 +106,9 @@ export interface User {
|
||||
is_active: boolean;
|
||||
force_change: boolean;
|
||||
account_sid: string | null;
|
||||
account_name?: string | null;
|
||||
service_provider_sid: string | null;
|
||||
service_provider_name?: string | null;
|
||||
initial_password?: string;
|
||||
permissions?: UserPermissions[];
|
||||
}
|
||||
@@ -142,6 +144,7 @@ export interface UserJWT {
|
||||
account_sid?: string | null;
|
||||
service_provider_sid?: string | null;
|
||||
permissions: UserPermissions[];
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ServiceProvider {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Menu,
|
||||
List,
|
||||
Edit,
|
||||
User,
|
||||
Plus,
|
||||
Grid,
|
||||
Phone,
|
||||
@@ -53,6 +54,7 @@ export const Icons: IconMap = {
|
||||
Menu,
|
||||
List,
|
||||
Edit,
|
||||
User,
|
||||
Plus,
|
||||
Grid,
|
||||
Phone,
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import React, { useState } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Link, Outlet } from "react-router-dom";
|
||||
import { Button, Icon, classNames } from "jambonz-ui";
|
||||
|
||||
import { Navi } from "./navi";
|
||||
import { Icons } from "src/components";
|
||||
import { toastSuccess } from "src/store";
|
||||
import { toastSuccess, useSelectState } from "src/store";
|
||||
import { useAuth } from "src/router/auth";
|
||||
import { useMobileMedia } from "src/utils";
|
||||
import { MSG_LOGGED_OUT } from "src/constants";
|
||||
|
||||
import "./styles.scss";
|
||||
import { ROUTE_INTERNAL_USERS } from "src/router/routes";
|
||||
import { useApiData } from "src/api";
|
||||
import { User } from "src/api/types";
|
||||
|
||||
export const Layout = () => {
|
||||
const user = useSelectState("user");
|
||||
const [userData] = useApiData<User>("Users/me");
|
||||
const [active, setActive] = useState(false);
|
||||
const { signout } = useAuth();
|
||||
const mobile = useMobileMedia();
|
||||
@@ -43,6 +48,22 @@ export const Layout = () => {
|
||||
<Icon subStyle="dark" onClick={handleMenu}>
|
||||
<Icons.Menu />
|
||||
</Icon>
|
||||
<div className="user">
|
||||
<Icons.User className="user--icon" />
|
||||
<div className="item__info">
|
||||
<div className="user--txt">
|
||||
<Link
|
||||
to={`${ROUTE_INTERNAL_USERS}/${user?.user_sid}/edit`}
|
||||
title="Edit user"
|
||||
>
|
||||
<strong>{userData?.name}</strong>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Scope:</strong> <code>{user?.scope}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
small
|
||||
mainStyle="hollow"
|
||||
|
||||
@@ -114,7 +114,7 @@ export const Alerts = () => {
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<M>No data</M>
|
||||
<M>No data.</M>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
AccountSelect,
|
||||
ApplicationSelect,
|
||||
} from "src/components/forms";
|
||||
import { ScopedAccess } from "src/components/scoped-access";
|
||||
import { MSG_REQUIRED_FIELDS } from "src/constants";
|
||||
import { ROUTE_INTERNAL_CARRIERS } from "src/router/routes";
|
||||
import { toastError, toastSuccess, useSelectState } from "src/store";
|
||||
@@ -52,6 +53,7 @@ import type {
|
||||
Smpp,
|
||||
Application,
|
||||
} from "src/api/types";
|
||||
import { Scope } from "src/store/types";
|
||||
|
||||
type CarrierFormProps = {
|
||||
carrier?: UseApiDataMap<Carrier>;
|
||||
@@ -746,117 +748,122 @@ export const CarrierForm = ({
|
||||
</>
|
||||
)}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<Checkzone
|
||||
hidden
|
||||
name="sip_credentials"
|
||||
label="Outbound Authentication"
|
||||
initialCheck={initialRegister}
|
||||
handleChecked={(e) => {
|
||||
if (!e.target.checked) {
|
||||
setSipUser("");
|
||||
setSipPass("");
|
||||
setSipRealm("");
|
||||
setSipRegister(false);
|
||||
setFromUser("");
|
||||
setFromDomain("");
|
||||
setRegPublicIpInContact(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MS>
|
||||
Does your carrier require authentication on outbound calls?
|
||||
</MS>
|
||||
<label htmlFor="sip_username">
|
||||
Username {sipPass || sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<input
|
||||
id="sip_username"
|
||||
name="sip_username"
|
||||
type="text"
|
||||
value={sipUser}
|
||||
placeholder="SIP username for authenticating outbound calls"
|
||||
required={sipRegister || sipPass.length > 0}
|
||||
onChange={(e) => {
|
||||
setSipUser(e.target.value);
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<fieldset>
|
||||
<Checkzone
|
||||
hidden
|
||||
name="sip_credentials"
|
||||
label="Outbound Authentication"
|
||||
initialCheck={initialRegister}
|
||||
handleChecked={(e) => {
|
||||
if (!e.target.checked) {
|
||||
setSipUser("");
|
||||
setSipPass("");
|
||||
setSipRealm("");
|
||||
setSipRegister(false);
|
||||
setFromUser("");
|
||||
setFromDomain("");
|
||||
setRegPublicIpInContact(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sip_password">
|
||||
Password
|
||||
{sipUser || sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<Passwd
|
||||
id="sip_password"
|
||||
name="sip_password"
|
||||
value={sipPass}
|
||||
placeholder="SIP password for authenticating outbound calls"
|
||||
required={sipRegister || sipUser.length > 0}
|
||||
onChange={(e) => {
|
||||
setSipPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sip_register" className="chk">
|
||||
>
|
||||
<MS>
|
||||
Does your carrier require authentication on outbound calls?
|
||||
</MS>
|
||||
<label htmlFor="sip_username">
|
||||
Username {sipPass || sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<input
|
||||
id="sip_register"
|
||||
name="sip_register"
|
||||
type="checkbox"
|
||||
checked={sipRegister}
|
||||
onChange={(e) => setSipRegister(e.target.checked)}
|
||||
id="sip_username"
|
||||
name="sip_username"
|
||||
type="text"
|
||||
value={sipUser}
|
||||
placeholder="SIP username for authenticating outbound calls"
|
||||
required={sipRegister || sipPass.length > 0}
|
||||
onChange={(e) => {
|
||||
setSipUser(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<div>Require SIP Register</div>
|
||||
</label>
|
||||
{sipRegister && (
|
||||
<>
|
||||
<MS>
|
||||
Carrier requires SIP Register before sending outbound
|
||||
calls.
|
||||
</MS>
|
||||
<label htmlFor="sip_realm">
|
||||
SIP Realm{sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<label htmlFor="sip_password">
|
||||
Password
|
||||
{sipUser || sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<Passwd
|
||||
id="sip_password"
|
||||
name="sip_password"
|
||||
value={sipPass}
|
||||
placeholder="SIP password for authenticating outbound calls"
|
||||
required={sipRegister || sipUser.length > 0}
|
||||
onChange={(e) => {
|
||||
setSipPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="sip_register" className="chk">
|
||||
<input
|
||||
id="sip_realm"
|
||||
name="sip_realm"
|
||||
type="text"
|
||||
value={sipRealm}
|
||||
placeholder="SIP realm for registration"
|
||||
required={sipRegister}
|
||||
onChange={(e) => setSipRealm(e.target.value)}
|
||||
id="sip_register"
|
||||
name="sip_register"
|
||||
type="checkbox"
|
||||
checked={sipRegister}
|
||||
onChange={(e) => setSipRegister(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="from_user">SIP From User</label>
|
||||
<input
|
||||
id="from_user"
|
||||
name="from_user"
|
||||
type="text"
|
||||
value={fromUser}
|
||||
placeholder="Optional: specify user part of SIP From header"
|
||||
onChange={(e) => setFromUser(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="from_domain">SIP From Domain</label>
|
||||
<input
|
||||
id="from_domain"
|
||||
name="from_domain"
|
||||
type="text"
|
||||
value={fromDomain}
|
||||
placeholder="Optional: specify host part of SIP From header"
|
||||
onChange={(e) => setFromDomain(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="reg_public_ip_in_contact" className="chk">
|
||||
<div>Require SIP Register</div>
|
||||
</label>
|
||||
{sipRegister && (
|
||||
<>
|
||||
<MS>
|
||||
Carrier requires SIP Register before sending outbound
|
||||
calls.
|
||||
</MS>
|
||||
<label htmlFor="sip_realm">
|
||||
SIP Realm{sipRegister ? <span>*</span> : ""}
|
||||
</label>
|
||||
<input
|
||||
id="reg_public_ip_in_contact"
|
||||
name="reg_public_ip_in_contact"
|
||||
type="checkbox"
|
||||
checked={regPublicIpInContact}
|
||||
onChange={(e) =>
|
||||
setRegPublicIpInContact(e.target.checked)
|
||||
}
|
||||
id="sip_realm"
|
||||
name="sip_realm"
|
||||
type="text"
|
||||
value={sipRealm}
|
||||
placeholder="SIP realm for registration"
|
||||
required={sipRegister}
|
||||
onChange={(e) => setSipRealm(e.target.value)}
|
||||
/>
|
||||
<div>Use Public IP in Contact</div>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</Checkzone>
|
||||
</fieldset>
|
||||
<label htmlFor="from_user">SIP From User</label>
|
||||
<input
|
||||
id="from_user"
|
||||
name="from_user"
|
||||
type="text"
|
||||
value={fromUser}
|
||||
placeholder="Optional: specify user part of SIP From header"
|
||||
onChange={(e) => setFromUser(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="from_domain">SIP From Domain</label>
|
||||
<input
|
||||
id="from_domain"
|
||||
name="from_domain"
|
||||
type="text"
|
||||
value={fromDomain}
|
||||
placeholder="Optional: specify host part of SIP From header"
|
||||
onChange={(e) => setFromDomain(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="reg_public_ip_in_contact" className="chk">
|
||||
<input
|
||||
id="reg_public_ip_in_contact"
|
||||
name="reg_public_ip_in_contact"
|
||||
type="checkbox"
|
||||
checked={regPublicIpInContact}
|
||||
onChange={(e) =>
|
||||
setRegPublicIpInContact(e.target.checked)
|
||||
}
|
||||
/>
|
||||
<div>Use Public IP in Contact</div>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</Checkzone>
|
||||
</fieldset>
|
||||
</ScopedAccess>
|
||||
<fieldset>
|
||||
<Checkzone
|
||||
hidden
|
||||
@@ -1021,49 +1028,61 @@ export const CarrierForm = ({
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btnty"
|
||||
title="Delete SIP Gateway"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSipMessage("");
|
||||
|
||||
if (sipGateways.length === 1) {
|
||||
setSipMessage(
|
||||
"You must provide at least one SIP Gateway."
|
||||
);
|
||||
} else {
|
||||
handleSipGatewayDelete(
|
||||
sipGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSipGateways(
|
||||
sipGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}
|
||||
}}
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={
|
||||
accountSid ? Scope.account : Scope.service_provider
|
||||
}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
<button
|
||||
className="btnty"
|
||||
title="Delete SIP Gateway"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSipMessage("");
|
||||
|
||||
if (sipGateways.length === 1) {
|
||||
setSipMessage(
|
||||
"You must provide at least one SIP Gateway."
|
||||
);
|
||||
} else {
|
||||
handleSipGatewayDelete(
|
||||
sipGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSipGateways(
|
||||
sipGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
</ScopedAccess>
|
||||
</div>
|
||||
))}
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
title="Add SIP Gateway"
|
||||
onClick={() => {
|
||||
setSipMessage("");
|
||||
addSipGateway();
|
||||
}}
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
title="Add SIP Gateway"
|
||||
onClick={() => {
|
||||
setSipMessage("");
|
||||
addSipGateway();
|
||||
}}
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
</ScopedAccess>
|
||||
</fieldset>
|
||||
</Tab>
|
||||
<Tab id="smpp" label="SMS">
|
||||
@@ -1085,27 +1104,32 @@ export const CarrierForm = ({
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="outbound_smpp">Outbound SMPP</label>
|
||||
<label htmlFor="outbound_id">System ID</label>
|
||||
<input
|
||||
id="outbound_id"
|
||||
name="outbound_id"
|
||||
type="text"
|
||||
value={smppSystemId}
|
||||
placeholder="SMPP system ID to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppSystemId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="outbound_pass">Password</label>
|
||||
<Passwd
|
||||
id="outbound_pass"
|
||||
name="outbound_pass"
|
||||
value={smppPass}
|
||||
placeholder="SMPP password to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<label htmlFor="outbound_id">System ID</label>
|
||||
<input
|
||||
id="outbound_id"
|
||||
name="outbound_id"
|
||||
type="text"
|
||||
value={smppSystemId}
|
||||
placeholder="SMPP system ID to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppSystemId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="outbound_pass">Password</label>
|
||||
<Passwd
|
||||
id="outbound_pass"
|
||||
name="outbound_pass"
|
||||
value={smppPass}
|
||||
placeholder="SMPP password to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ScopedAccess>
|
||||
<label htmlFor="outbound_smpp">
|
||||
Carrier SMPP Gateways
|
||||
<span>{smppSystemId || smppPass ? "*" : ""}</span>
|
||||
@@ -1179,86 +1203,103 @@ export const CarrierForm = ({
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Delete Outbound SMPP Gateway"
|
||||
type="button"
|
||||
className="btnty"
|
||||
onClick={() => {
|
||||
setSmppOutboundMessage("");
|
||||
|
||||
if (
|
||||
hasLength(smpps) &&
|
||||
smppGateways.filter((g) => g.outbound).length <=
|
||||
1 &&
|
||||
(smppSystemId || smppPass)
|
||||
) {
|
||||
setSmppOutboundMessage(
|
||||
"You must provide at least one Outbound Gateway."
|
||||
);
|
||||
} else {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}
|
||||
}}
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={
|
||||
accountSid ? Scope.account : Scope.service_provider
|
||||
}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
<button
|
||||
title="Delete Outbound SMPP Gateway"
|
||||
type="button"
|
||||
className="btnty"
|
||||
onClick={() => {
|
||||
setSmppOutboundMessage("");
|
||||
|
||||
if (
|
||||
hasLength(smpps) &&
|
||||
smppGateways.filter((g) => g.outbound).length <=
|
||||
1 &&
|
||||
(smppSystemId || smppPass)
|
||||
) {
|
||||
setSmppOutboundMessage(
|
||||
"You must provide at least one Outbound Gateway."
|
||||
);
|
||||
} else {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
</ScopedAccess>
|
||||
</div>
|
||||
) : null;
|
||||
})}
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
onClick={() => addSmppGateway({ inbound: 0, outbound: 1 })}
|
||||
title="Add Outbound SMPP Gateway"
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
onClick={() => addSmppGateway({ inbound: 0, outbound: 1 })}
|
||||
title="Add Outbound SMPP Gateway"
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
</ScopedAccess>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="inbound_smpp">Inbound SMPP</label>
|
||||
<label htmlFor="inbound_id">System ID</label>
|
||||
<input
|
||||
id="inbound_id"
|
||||
name="inbound_id"
|
||||
type="text"
|
||||
value={smppInboundSystemId}
|
||||
placeholder="SMPP system ID to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppInboundSystemId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="inbound_pass">
|
||||
Password
|
||||
<span>{!hasEmptySmppGateways("inbound") ? "*" : ""}</span>
|
||||
</label>
|
||||
<MXS>
|
||||
<em>
|
||||
Password is required if whitelisting carrier IP address(es)
|
||||
below.
|
||||
</em>
|
||||
</MXS>
|
||||
<Passwd
|
||||
id="inbound_pass"
|
||||
name="inbound_pass"
|
||||
value={smppInboundPass}
|
||||
placeholder="SMPP password for authenticating inbound messages"
|
||||
required={!hasEmptySmppGateways("inbound")}
|
||||
onChange={(e) => {
|
||||
setSmppInboundPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<label htmlFor="inbound_id">System ID</label>
|
||||
<input
|
||||
id="inbound_id"
|
||||
name="inbound_id"
|
||||
type="text"
|
||||
value={smppInboundSystemId}
|
||||
placeholder="SMPP system ID to authenticate with"
|
||||
onChange={(e) => {
|
||||
setSmppInboundSystemId(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="inbound_pass">
|
||||
Password
|
||||
<span>{!hasEmptySmppGateways("inbound") ? "*" : ""}</span>
|
||||
</label>
|
||||
<MXS>
|
||||
<em>
|
||||
Password is required if whitelisting carrier IP address(es)
|
||||
below.
|
||||
</em>
|
||||
</MXS>
|
||||
<Passwd
|
||||
id="inbound_pass"
|
||||
name="inbound_pass"
|
||||
value={smppInboundPass}
|
||||
placeholder="SMPP password for authenticating inbound messages"
|
||||
required={!hasEmptySmppGateways("inbound")}
|
||||
onChange={(e) => {
|
||||
setSmppInboundPass(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ScopedAccess>
|
||||
<label htmlFor="inbound_smpp">
|
||||
Carrier IP Address(es) to whitelist
|
||||
</label>
|
||||
@@ -1304,39 +1345,51 @@ export const CarrierForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btnty"
|
||||
title="Delete Inbound SMPP Gateway"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}}
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={
|
||||
accountSid ? Scope.account : Scope.service_provider
|
||||
}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
<button
|
||||
className="btnty"
|
||||
title="Delete Inbound SMPP Gateway"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSmppGatewayDelete(
|
||||
smppGateways.find((g2, i2) => i2 === i)
|
||||
);
|
||||
|
||||
setSmppGateways(
|
||||
smppGateways.filter((g2, i2) => i2 !== i)
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Trash2 />
|
||||
</Icon>
|
||||
</button>
|
||||
</ScopedAccess>
|
||||
</div>
|
||||
) : null;
|
||||
})}
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
onClick={() => addSmppGateway({ outbound: 0, inbound: 1 })}
|
||||
title="Add Inbound SMPP Gateway"
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
<ScopedAccess
|
||||
user={user}
|
||||
scope={accountSid ? Scope.account : Scope.service_provider}
|
||||
>
|
||||
<ButtonGroup left>
|
||||
<button
|
||||
className="btnty"
|
||||
type="button"
|
||||
onClick={() => addSmppGateway({ outbound: 0, inbound: 1 })}
|
||||
title="Add Inbound SMPP Gateway"
|
||||
>
|
||||
<Icon subStyle="teal">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
</button>
|
||||
</ButtonGroup>
|
||||
</ScopedAccess>
|
||||
</fieldset>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -125,7 +125,7 @@ export const RecentCalls = () => {
|
||||
) : hasLength(calls) ? (
|
||||
calls.map((call) => <DetailsItem key={call.call_sid} call={call} />)
|
||||
) : (
|
||||
<M>No data</M>
|
||||
<M>No data.</M>
|
||||
)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -5,12 +5,14 @@ import { withSelectState } from "src/utils";
|
||||
import { ApiKeys } from "src/containers/internal/api-keys";
|
||||
import ServiceProviderSettings from "./service-provider-settings";
|
||||
import AdminSettings from "./admin-settings";
|
||||
import { ScopedAccess } from "src/components/scoped-access";
|
||||
import type { ServiceProvider } from "src/api/types";
|
||||
import { Section } from "src/components";
|
||||
|
||||
import { USER_ADMIN } from "src/api/constants";
|
||||
import { MSG_REQUIRED_FIELDS } from "src/constants";
|
||||
import { ScopedAccess } from "src/components/scoped-access";
|
||||
import { Scope } from "src/store/types";
|
||||
import { useSelectState } from "src/store";
|
||||
import { Scope } from "src/store/types";
|
||||
|
||||
type SettingsProps = {
|
||||
currentServiceProvider: ServiceProvider;
|
||||
@@ -38,9 +40,11 @@ export const Settings = ({ currentServiceProvider }: SettingsProps) => {
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</ScopedAccess>
|
||||
<ScopedAccess scope={Scope.service_provider} user={user}>
|
||||
<ServiceProviderSettings />
|
||||
</ScopedAccess>
|
||||
{user?.scope !== USER_ADMIN && (
|
||||
<ScopedAccess scope={Scope.service_provider} user={user}>
|
||||
<ServiceProviderSettings />
|
||||
</ScopedAccess>
|
||||
)}
|
||||
</form>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -575,7 +575,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
}}
|
||||
checked={useCustomTts}
|
||||
/>
|
||||
<div>Use for custom text-to-speech</div>
|
||||
<div>Use for custom voice</div>
|
||||
</label>
|
||||
<label htmlFor="use_custom_tts">
|
||||
Custom voice endpoint{useCustomTts && <span>*</span>}
|
||||
@@ -611,7 +611,7 @@ export const SpeechServiceForm = ({ credential }: SpeechServiceFormProps) => {
|
||||
}}
|
||||
checked={useCustomStt}
|
||||
/>
|
||||
<div>Use for custom speech-to-text</div>
|
||||
<div>Use for custom speech model</div>
|
||||
</label>
|
||||
<label htmlFor="use_custom_stt">
|
||||
Custom speech endpoint id{useCustomStt && <span>*</span>}
|
||||
|
||||
@@ -52,7 +52,9 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [initialPassword, setInitialPassword] = useState("");
|
||||
const [scope, setScope] = useState<UserScopes>();
|
||||
const [scope, setScope] = useState<UserScopes | null>(
|
||||
currentUser?.scope || null
|
||||
);
|
||||
const [isActive, setIsActive] = useState(true);
|
||||
const [forceChange, setForceChange] = useState(true);
|
||||
const [modal, setModal] = useState(false);
|
||||
@@ -184,7 +186,7 @@ export const UserForm = ({ user }: UserFormProps) => {
|
||||
<Selector
|
||||
id="scope"
|
||||
name="scope"
|
||||
value={scope}
|
||||
value={scope || currentUser?.scope}
|
||||
options={
|
||||
currentUser?.scope === USER_SP
|
||||
? USER_SCOPE_SELECTION.filter(
|
||||
|
||||
@@ -18,7 +18,12 @@ import {
|
||||
AccountFilter,
|
||||
SelectFilter,
|
||||
} from "src/components";
|
||||
import { hasLength, hasValue, useFilteredResults } from "src/utils";
|
||||
import {
|
||||
hasLength,
|
||||
hasValue,
|
||||
sortAlphabetically,
|
||||
useFilteredResults,
|
||||
} from "src/utils";
|
||||
|
||||
import type { Account, User } from "src/api/types";
|
||||
import { useSelectState } from "src/store";
|
||||
@@ -33,6 +38,15 @@ export const Users = () => {
|
||||
const [accounts] = useServiceProviderData<Account[]>("Accounts");
|
||||
|
||||
const usersFiltered = useMemo(() => {
|
||||
//find and add account/sp names to user to improve fuzzy search
|
||||
users?.forEach((user) => {
|
||||
user.account_name =
|
||||
accounts?.find((acct) => acct.account_sid === user.account_sid)?.name ||
|
||||
null;
|
||||
user.service_provider_name =
|
||||
user.scope === USER_ADMIN ? null : currentServiceProvider?.name || null;
|
||||
});
|
||||
|
||||
const serviceProviderUsers = users?.filter((e) => {
|
||||
return (
|
||||
e.scope === USER_ADMIN ||
|
||||
@@ -60,7 +74,9 @@ export const Users = () => {
|
||||
return [];
|
||||
}, [accountSid, scopeFilter, users, accounts, currentServiceProvider]);
|
||||
|
||||
const filteredUsers = useFilteredResults<User>(filter, usersFiltered);
|
||||
const filteredUsers = useFilteredResults<User>(filter, usersFiltered)?.sort(
|
||||
(a, b) => sortAlphabetically(a, b)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -99,11 +115,12 @@ export const Users = () => {
|
||||
</section>
|
||||
|
||||
<Section slim>
|
||||
<div className="grid grid--col4">
|
||||
<div className="grid grid--col5">
|
||||
<div className="grid__row grid__th">
|
||||
<div>User Name</div>
|
||||
<div>Email</div>
|
||||
<div>Scope</div>
|
||||
<div>Access</div>
|
||||
<div> </div>
|
||||
</div>
|
||||
{!hasValue(users) ? (
|
||||
@@ -115,6 +132,11 @@ export const Users = () => {
|
||||
<div>{user.name}</div>
|
||||
<div>{user.email}</div>
|
||||
<div>{user.scope}</div>
|
||||
<div>
|
||||
{user.scope === USER_ADMIN
|
||||
? null
|
||||
: user.account_name || user.service_provider_name}
|
||||
</div>
|
||||
<div className="item__actions">
|
||||
<Link
|
||||
to={`${ROUTE_INTERNAL_USERS}/${user.user_sid}/edit`}
|
||||
|
||||
@@ -99,17 +99,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
&--col4 {
|
||||
&--col5 {
|
||||
.grid__row {
|
||||
grid-template-columns: [col] 30% [col] 30% [col] 30% [col] 10%;
|
||||
grid-template-columns: [col] 30% [col] 20% [col] 20% [col] 28% [col] 2%;
|
||||
grid-template-rows: [row] auto [row] auto [row] [row] auto;
|
||||
display: grid;
|
||||
justify-content: space-between;
|
||||
|
||||
> div:last-child {
|
||||
text-align: right;
|
||||
text-align: left;
|
||||
padding-right: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
@use "./vars";
|
||||
@use "./mixins";
|
||||
@use "jambonz-ui/src/styles/vars" as ui-vars;
|
||||
@use "jambonz-ui/src/styles/mixins" as ui-mixins;
|
||||
|
||||
.user {
|
||||
padding: ui-vars.$px03;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: ui-vars.$mxs-size;
|
||||
line-height: 0.4rem;
|
||||
|
||||
&--icon {
|
||||
color: ui-vars.$jambonz;
|
||||
width: ui-vars.$h3-size;
|
||||
height: ui-vars.$h3-size;
|
||||
padding-right: ui-vars.$px00;
|
||||
}
|
||||
&--txt {
|
||||
padding-top: ui-vars.$px01;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
@use "./inpbtn";
|
||||
@use "./filters";
|
||||
@use "./grid";
|
||||
@use "./user";
|
||||
@use "jambonz-ui/src/styles/index";
|
||||
|
||||
@use "./vars";
|
||||
|
||||
@@ -148,6 +148,20 @@ export const getUserScope = (user: User): UserScopes => {
|
||||
}
|
||||
};
|
||||
|
||||
export const sortAlphabetically = (a: User, b: User) => {
|
||||
const nameA = a.name.toUpperCase(); // ignore upper and lowercase
|
||||
const nameB = b.name.toUpperCase(); // ignore upper and lowercase
|
||||
if (nameA < nameB) {
|
||||
return -1;
|
||||
}
|
||||
if (nameA > nameB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// names must be equal
|
||||
return 0;
|
||||
};
|
||||
|
||||
export {
|
||||
withSuspense,
|
||||
useMobileMedia,
|
||||
|
||||
Reference in New Issue
Block a user