client/web: button, link, and other small UI updates

Makes the following changes:
* Use “link” class in various spots
* Remove button appearance on Exit Node dropdown in readonly mode
* Update `-stone-` colors to `-gray-` (couple spots missed by
  original color config commit)
* Pull full ui/button component from admin panel, and update
  buttons throughout UI to use this component
* Remove various buttons in readonly view to match mocks
* Add route (and “pending approval”) highlights to Subnet router
  settings card
* Delete legacy client button styles from index.css
* Fix overflow of IPv6 address on device details view

Updates #10261

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
Sonia Appasamy
2023-12-04 15:20:38 -05:00
committed by Sonia Appasamy
parent 64a26b221b
commit 95e9d22a16
14 changed files with 545 additions and 333 deletions
@@ -19,10 +19,11 @@ export default function SubnetRouterView({
node: NodeData
nodeUpdaters: NodeUpdaters
}) {
const advertisedRoutes = useMemo(
() => node.AdvertisedRoutes || [],
[node.AdvertisedRoutes]
)
const [advertisedRoutes, hasUnapprovedRoutes] = useMemo(() => {
const routes = node.AdvertisedRoutes || []
return [routes, routes.find((r) => !r.Approved)]
}, [node.AdvertisedRoutes])
const [inputOpen, setInputOpen] = useState<boolean>(
advertisedRoutes.length === 0 && !readonly
)
@@ -42,42 +43,49 @@ export default function SubnetRouterView({
Learn more &rarr;
</a>
</p>
{inputOpen ? (
<div className="-mx-5 card shadow">
<p className="font-medium leading-snug mb-3">Advertise new routes</p>
<Input
type="text"
className="text-sm"
placeholder="192.168.0.0/24"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
/>
<p className="my-2 h-6 text-gray-500 text-sm leading-tight">
Add multiple routes by providing a comma-separated list.
</p>
{!readonly &&
(inputOpen ? (
<div className="-mx-5 card shadow">
<p className="font-medium leading-snug mb-3">
Advertise new routes
</p>
<Input
type="text"
className="text-sm"
placeholder="192.168.0.0/24"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
/>
<p className="my-2 h-6 text-gray-500 text-sm leading-tight">
Add multiple routes by providing a comma-separated list.
</p>
<Button
intent="primary"
onClick={() =>
nodeUpdaters
.postSubnetRoutes([
...advertisedRoutes.map((r) => r.Route),
...inputText.split(","),
])
.then(() => {
setInputText("")
setInputOpen(false)
})
}
disabled={!inputText}
>
Advertise routes
</Button>
</div>
) : (
<Button
onClick={() =>
nodeUpdaters
.postSubnetRoutes([
...advertisedRoutes.map((r) => r.Route),
...inputText.split(","),
])
.then(() => {
setInputText("")
setInputOpen(false)
})
}
disabled={readonly || !inputText}
intent="primary"
prefixIcon={<Plus />}
onClick={() => setInputOpen(true)}
>
Advertise routes
Advertise new route
</Button>
</div>
) : (
<Button onClick={() => setInputOpen(true)} disabled={readonly}>
<Plus />
Advertise new route
</Button>
)}
))}
<div className="-mx-5 mt-10">
{advertisedRoutes.length > 0 ? (
<>
@@ -96,7 +104,7 @@ export default function SubnetRouterView({
<Clock className="w-4 h-4" />
)}
{r.Approved ? (
<div className="text-emerald-800 text-sm leading-tight">
<div className="text-green-500 text-sm leading-tight">
Approved
</div>
) : (
@@ -105,37 +113,39 @@ export default function SubnetRouterView({
</div>
)}
</div>
<Button
intent="secondary"
className="text-sm font-medium"
onClick={() =>
nodeUpdaters.postSubnetRoutes(
advertisedRoutes
.map((it) => it.Route)
.filter((it) => it !== r.Route)
)
}
disabled={readonly}
>
Stop advertising
</Button>
{!readonly && (
<Button
sizeVariant="small"
onClick={() =>
nodeUpdaters.postSubnetRoutes(
advertisedRoutes
.map((it) => it.Route)
.filter((it) => it !== r.Route)
)
}
>
Stop advertising
</Button>
)}
</div>
</div>
))}
</div>
<Control.AdminContainer
className="mt-3 w-full text-center text-gray-500 text-sm leading-tight"
node={node}
>
To approve routes, in the admin console go to{" "}
<Control.AdminLink node={node} path={`/machines/${node.IP}`}>
the machines route settings
</Control.AdminLink>
.
</Control.AdminContainer>
{hasUnapprovedRoutes && (
<Control.AdminContainer
className="mt-3 w-full text-center text-gray-500 text-sm leading-tight"
node={node}
>
To approve routes, in the admin console go to{" "}
<Control.AdminLink node={node} path={`/machines/${node.IP}`}>
the machines route settings
</Control.AdminLink>
.
</Control.AdminContainer>
)}
</>
) : (
<div className="px-5 py-4 bg-stone-50 rounded-lg border border-gray-200 text-center text-gray-500">
<div className="px-5 py-4 bg-gray-50 rounded-lg border border-gray-200 text-center text-gray-500">
Not advertising any routes
</div>
)}