mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
9349 new controls page (#9431)
# Addresses #9349 # Implements https://www.loom.com/share/bbf8d6f97fe74e65a0c9a394f1bda3f1 - New Controls page, only visible to Global|Team Admins|Maintainers - Header for free users is 'Controls', for premium is a teams filter dropdown that defaults to 'No teams,' which filters via updating the URL query param "team_id" - Includes tabs macUpdates (default) and macSettings - Cleaned up how site nav items are conditionally included/excluded based on authorization – see `frontend/components/top_nav/SiteTopNav/navItems.ts` - Updated masthead styles: Removed icons from site nav links; updated colors and spacing; Updated default user avatar TBD in separate PR (waiting on guidance) # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/` - [x] Updated testing suite inventory - [x] Manual QA for all new/changed functionality Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
This commit is contained in:
parent
86c1916989
commit
8a5569cd1b
1
changes/9349-new-controls-page-and-style-updates
Normal file
1
changes/9349-new-controls-page-and-style-updates
Normal file
@ -0,0 +1 @@
|
||||
- Implemented the new Controls page and updated styling of the site-level navigation.
|
@ -21,6 +21,8 @@ interface ITeamsDropdownHeaderProps {
|
||||
buttons?: (ctx: ITeamsDropdownState) => JSX.Element | null;
|
||||
onChange: (ctx: ITeamsDropdownState) => void;
|
||||
description: (ctx: ITeamsDropdownState) => JSX.Element | string | null;
|
||||
includeNoTeams?: boolean;
|
||||
includeAll?: boolean;
|
||||
}
|
||||
|
||||
const TeamsDropdownHeader = ({
|
||||
@ -31,6 +33,8 @@ const TeamsDropdownHeader = ({
|
||||
buttons,
|
||||
description,
|
||||
onChange,
|
||||
includeNoTeams = false,
|
||||
includeAll = true,
|
||||
}: ITeamsDropdownHeaderProps): JSX.Element | null => {
|
||||
const teamId = parseInt(location?.query?.team_id || "", 10) || 0;
|
||||
|
||||
@ -50,9 +54,7 @@ const TeamsDropdownHeader = ({
|
||||
isAnyTeamAdmin,
|
||||
isTeamAdmin,
|
||||
isOnlyObserver,
|
||||
setAvailableTeams,
|
||||
setCurrentTeam,
|
||||
setCurrentUser,
|
||||
} = useContext(AppContext);
|
||||
|
||||
// The dropdownState is the context and local state made available to callback functions.
|
||||
@ -121,6 +123,7 @@ const TeamsDropdownHeader = ({
|
||||
onChange({ ...dropdownState, teamId: availableTeam?.id });
|
||||
}
|
||||
},
|
||||
// TODO: add missing deps to this array if doens't cause bugs
|
||||
[location, router]
|
||||
);
|
||||
|
||||
@ -188,6 +191,8 @@ const TeamsDropdownHeader = ({
|
||||
onChange={(newSelectedValue: number) =>
|
||||
handleTeamSelect(newSelectedValue)
|
||||
}
|
||||
includeNoTeams={includeNoTeams}
|
||||
includeAll={includeAll}
|
||||
/>
|
||||
)}
|
||||
{isPremiumTier &&
|
||||
|
@ -8,7 +8,8 @@ import { AppContext } from "context/app";
|
||||
|
||||
const generateDropdownOptions = (
|
||||
teams: ITeamSummary[] | undefined,
|
||||
includeAll: boolean
|
||||
includeAll: boolean,
|
||||
includeNoTeams?: boolean
|
||||
) => {
|
||||
if (!teams) {
|
||||
return [];
|
||||
@ -27,13 +28,20 @@ const generateDropdownOptions = (
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (includeNoTeams) {
|
||||
options.unshift({
|
||||
disabled: false,
|
||||
label: "No teams",
|
||||
value: 0,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
};
|
||||
interface ITeamsDropdownProps {
|
||||
currentUserTeams: ITeamSummary[];
|
||||
selectedTeamId?: number;
|
||||
includeAll?: boolean; // Include the "All Teams" option;
|
||||
includeNoTeams?: boolean;
|
||||
isDisabled?: boolean;
|
||||
onChange: (newSelectedValue: number) => void;
|
||||
onOpen?: () => void;
|
||||
@ -46,6 +54,7 @@ const TeamsDropdown = ({
|
||||
currentUserTeams,
|
||||
selectedTeamId,
|
||||
includeAll = true,
|
||||
includeNoTeams = false,
|
||||
isDisabled,
|
||||
onChange,
|
||||
onOpen,
|
||||
@ -55,8 +64,12 @@ const TeamsDropdown = ({
|
||||
|
||||
const teamOptions = useMemo(
|
||||
() =>
|
||||
generateDropdownOptions(currentUserTeams, includeAll && isOnGlobalTeam),
|
||||
[currentUserTeams, includeAll, isOnGlobalTeam]
|
||||
generateDropdownOptions(
|
||||
currentUserTeams,
|
||||
includeAll && isOnGlobalTeam,
|
||||
includeNoTeams
|
||||
),
|
||||
[currentUserTeams, includeAll, isOnGlobalTeam, includeNoTeams]
|
||||
);
|
||||
|
||||
const selectedValue = teamOptions.find(
|
||||
|
@ -12,11 +12,6 @@ import UserMenu from "components/top_nav/UserMenu";
|
||||
import OrgLogoIcon from "components/icons/OrgLogoIcon";
|
||||
|
||||
import navItems, { INavItem } from "./navItems";
|
||||
import HostsIcon from "../../../../assets/images/icon-main-hosts@2x-16x16@2x.png";
|
||||
import SoftwareIcon from "../../../../assets/images/icon-software-16x16@2x.png";
|
||||
import QueriesIcon from "../../../../assets/images/icon-main-queries@2x-16x16@2x.png";
|
||||
import PacksIcon from "../../../../assets/images/icon-main-packs@2x-16x16@2x.png";
|
||||
import PoliciesIcon from "../../../../assets/images/icon-main-policies-16x16@2x.png";
|
||||
|
||||
interface ISiteTopNavProps {
|
||||
onLogoutUser: () => void;
|
||||
@ -52,9 +47,7 @@ const SiteTopNav = ({
|
||||
[`${navItemBaseClass}--active`]: active,
|
||||
});
|
||||
|
||||
const iconClasses = classnames([`${navItemBaseClass}__icon`]);
|
||||
|
||||
if (iconName === "logo") {
|
||||
if (iconName && iconName === "logo") {
|
||||
return (
|
||||
<li className={navItemClasses} key={`nav-item-${name}`}>
|
||||
<Link
|
||||
@ -67,25 +60,6 @@ const SiteTopNav = ({
|
||||
);
|
||||
}
|
||||
|
||||
const iconImage = () => {
|
||||
switch (iconName) {
|
||||
case "hosts":
|
||||
return HostsIcon;
|
||||
case "software":
|
||||
return SoftwareIcon;
|
||||
case "queries":
|
||||
return QueriesIcon;
|
||||
case "packs":
|
||||
return PacksIcon;
|
||||
default:
|
||||
return PoliciesIcon;
|
||||
}
|
||||
};
|
||||
|
||||
const icon = (
|
||||
<img src={iconImage()} alt={`${iconName} icon`} className={iconClasses} />
|
||||
);
|
||||
|
||||
return (
|
||||
<li className={navItemClasses} key={`nav-item-${name}`}>
|
||||
{withContext ? (
|
||||
@ -93,7 +67,6 @@ const SiteTopNav = ({
|
||||
className={`${navItemBaseClass}__link`}
|
||||
to={navItem.location.pathname}
|
||||
>
|
||||
{icon}
|
||||
<span
|
||||
className={`${navItemBaseClass}__name`}
|
||||
data-text={navItem.name}
|
||||
@ -106,7 +79,6 @@ const SiteTopNav = ({
|
||||
className={`${navItemBaseClass}__link`}
|
||||
to={navItem.location.pathname}
|
||||
>
|
||||
{icon}
|
||||
<span
|
||||
className={`${navItemBaseClass}__name`}
|
||||
data-text={navItem.name}
|
||||
@ -130,7 +102,7 @@ const SiteTopNav = ({
|
||||
|
||||
const renderNavItems = () => {
|
||||
return (
|
||||
<div className="site-nav-container">
|
||||
<div className="site-nav-content">
|
||||
<ul className="site-nav-list">
|
||||
{userNavItems.map((navItem) => {
|
||||
return renderNavItem(navItem);
|
||||
|
@ -1,11 +1,15 @@
|
||||
.site-nav-item {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
transition: color 200ms ease-in-out;
|
||||
cursor: pointer;
|
||||
max-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $core-fleet-black;
|
||||
background-color: $site-nav-on-hover;
|
||||
}
|
||||
|
||||
&--multiple.site-nav-item--active {
|
||||
@ -50,9 +54,6 @@
|
||||
|
||||
&__link {
|
||||
color: $core-white;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 20px 17px;
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -66,11 +67,11 @@
|
||||
|
||||
&--active {
|
||||
border-bottom: 3px solid $core-vibrant-blue;
|
||||
background-color: $core-fleet-black;
|
||||
background-color: $site-nav-on-hover;
|
||||
height: 47px;
|
||||
|
||||
&:hover {
|
||||
background-color: $core-fleet-black;
|
||||
background-color: $site-nav-on-hover;
|
||||
}
|
||||
|
||||
.site-nav-item__name {
|
||||
@ -86,7 +87,7 @@
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.site-nav-container {
|
||||
.site-nav-content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -95,7 +96,10 @@
|
||||
|
||||
.site-nav-list {
|
||||
list-style: none;
|
||||
width: 671px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@ -3,14 +3,15 @@ import URL_PREFIX from "router/url_prefix";
|
||||
import { IUser } from "interfaces/user";
|
||||
|
||||
export interface INavItem {
|
||||
icon: string;
|
||||
name: string;
|
||||
iconName: string;
|
||||
icon?: string;
|
||||
iconName?: string;
|
||||
location: {
|
||||
regex: RegExp;
|
||||
pathname: string;
|
||||
};
|
||||
withContext?: boolean;
|
||||
exclude?: boolean;
|
||||
}
|
||||
|
||||
export default (
|
||||
@ -25,6 +26,12 @@ export default (
|
||||
return [];
|
||||
}
|
||||
|
||||
const isMaintainerOrAdmin =
|
||||
isGlobalMaintainer ||
|
||||
isAnyTeamMaintainer ||
|
||||
isGlobalAdmin ||
|
||||
isAnyTeamAdmin;
|
||||
|
||||
const logo = [
|
||||
{
|
||||
icon: "logo",
|
||||
@ -37,11 +44,9 @@ export default (
|
||||
},
|
||||
];
|
||||
|
||||
const userNavItems = [
|
||||
const navItems = [
|
||||
{
|
||||
icon: "hosts",
|
||||
name: "Hosts",
|
||||
iconName: "hosts",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/hosts/`),
|
||||
pathname: PATHS.MANAGE_HOSTS,
|
||||
@ -49,9 +54,15 @@ export default (
|
||||
withContext: true,
|
||||
},
|
||||
{
|
||||
icon: "software",
|
||||
name: "Controls",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/controls/`),
|
||||
pathname: PATHS.CONTROLS,
|
||||
},
|
||||
exclude: !isMaintainerOrAdmin,
|
||||
},
|
||||
{
|
||||
name: "Software",
|
||||
iconName: "software",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/software/`),
|
||||
pathname: PATHS.MANAGE_SOFTWARE,
|
||||
@ -59,21 +70,22 @@ export default (
|
||||
withContext: true,
|
||||
},
|
||||
{
|
||||
icon: "query",
|
||||
name: "Queries",
|
||||
iconName: "queries",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/queries/`),
|
||||
pathname: PATHS.MANAGE_QUERIES,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const policiesTab = [
|
||||
{
|
||||
icon: "policies",
|
||||
name: "Schedule",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/(schedule|packs)/`),
|
||||
pathname: PATHS.MANAGE_SCHEDULE,
|
||||
},
|
||||
exclude: !isMaintainerOrAdmin,
|
||||
},
|
||||
{
|
||||
name: "Policies",
|
||||
iconName: "policies",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/(policies)/`),
|
||||
pathname: PATHS.MANAGE_POLICIES,
|
||||
@ -81,34 +93,14 @@ export default (
|
||||
},
|
||||
];
|
||||
|
||||
const maintainerOrAdminNavItems = [
|
||||
{
|
||||
icon: "packs",
|
||||
name: "Schedule",
|
||||
iconName: "packs",
|
||||
location: {
|
||||
regex: new RegExp(`^${URL_PREFIX}/(schedule|packs)/`),
|
||||
pathname: PATHS.MANAGE_SCHEDULE,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (
|
||||
isGlobalMaintainer ||
|
||||
isAnyTeamMaintainer ||
|
||||
isGlobalAdmin ||
|
||||
isAnyTeamAdmin
|
||||
) {
|
||||
return [
|
||||
...logo,
|
||||
...userNavItems,
|
||||
...maintainerOrAdminNavItems,
|
||||
...policiesTab,
|
||||
];
|
||||
}
|
||||
|
||||
if (isNoAccess) {
|
||||
return [...logo];
|
||||
}
|
||||
return [...logo, ...userNavItems, ...policiesTab];
|
||||
|
||||
return [
|
||||
...logo,
|
||||
...navItems.filter((item) => {
|
||||
return !item.exclude;
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
@ -111,7 +111,7 @@ const CoreLayout = ({ children, router }: ICoreLayoutProps) => {
|
||||
<p>Please enlarge your browser or try again on a computer.</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="site-nav">
|
||||
<nav className="site-nav-container">
|
||||
<SiteTopNav
|
||||
config={config}
|
||||
onLogoutUser={onLogoutUser}
|
||||
|
@ -31,10 +31,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.site-nav {
|
||||
background: $gradients-dark-gradient;
|
||||
.site-nav-container {
|
||||
background: $core-fleet-black;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
|
164
frontend/pages/ControlsPage/ControlsWrapper.tsx
Normal file
164
frontend/pages/ControlsPage/ControlsWrapper.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React, { useContext } from "react";
|
||||
|
||||
import { Tab, Tabs, TabList } from "react-tabs";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import PATHS from "router/paths";
|
||||
import { AppContext } from "context/app";
|
||||
|
||||
import mdmAppleAPI from "services/entities/mdm_apple";
|
||||
|
||||
import TabsWrapper from "components/TabsWrapper";
|
||||
import MainContent from "components/MainContent";
|
||||
import TeamsDropdownHeader, {
|
||||
ITeamsDropdownState,
|
||||
} from "components/PageHeader/TeamsDropdownHeader";
|
||||
import EmptyTable from "components/EmptyTable";
|
||||
import Button from "components/buttons/Button";
|
||||
|
||||
import { IMdmApple } from "interfaces/mdm";
|
||||
|
||||
import { find } from "lodash";
|
||||
import { useQuery } from "react-query";
|
||||
import Spinner from "components/Spinner";
|
||||
|
||||
interface IControlsSubNavItem {
|
||||
name: string;
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
const controlsSubNav: IControlsSubNavItem[] = [
|
||||
{
|
||||
name: "macOS updates",
|
||||
pathname: PATHS.CONTROLS_MAC_UPDATES,
|
||||
},
|
||||
{
|
||||
name: "macOS settings",
|
||||
pathname: PATHS.CONTROLS_MAC_SETTINGS,
|
||||
},
|
||||
];
|
||||
|
||||
interface IControlsWrapperProp {
|
||||
children: JSX.Element;
|
||||
location: any; // no type in react-router v3
|
||||
router: InjectedRouter; // v3
|
||||
}
|
||||
|
||||
const getTabIndex = (path: string): number => {
|
||||
return controlsSubNav.findIndex((navItem) => {
|
||||
// tab stays highlighted for paths that start with same pathname
|
||||
return path.startsWith(navItem.pathname);
|
||||
});
|
||||
};
|
||||
|
||||
const baseClass = "controls-wrapper";
|
||||
|
||||
const ControlsWrapper = ({
|
||||
children,
|
||||
location,
|
||||
router,
|
||||
}: IControlsWrapperProp): JSX.Element => {
|
||||
const { availableTeams, isPremiumTier, setCurrentTeam } = useContext(
|
||||
AppContext
|
||||
);
|
||||
|
||||
const {
|
||||
data: mdmApple,
|
||||
isLoading: isLoadingMdmApple,
|
||||
error: errorMdmApple,
|
||||
} = useQuery<IMdmApple, Error, IMdmApple>(
|
||||
["mdmAppleAPI"],
|
||||
() => mdmAppleAPI.getAppleAPNInfo(),
|
||||
{
|
||||
enabled: isPremiumTier,
|
||||
staleTime: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
const navigateToNav = (i: number): void => {
|
||||
const navPath = controlsSubNav[i].pathname;
|
||||
router.push(navPath);
|
||||
};
|
||||
|
||||
const handleTeamSelect = (ctx: ITeamsDropdownState) => {
|
||||
const teamId = ctx.teamId;
|
||||
const queryString = teamId === undefined ? "" : `?team_id=${teamId}`;
|
||||
router.replace(location.pathname + queryString);
|
||||
const selectedTeam = find(availableTeams, ["id", teamId]);
|
||||
setCurrentTeam(selectedTeam);
|
||||
};
|
||||
|
||||
const renderHeader = () => (
|
||||
<div className={`${baseClass}__header`}>
|
||||
<div className={`${baseClass}__text`}>
|
||||
<div className={`${baseClass}__title`}>
|
||||
<TeamsDropdownHeader
|
||||
router={router}
|
||||
location={location}
|
||||
baseClass={baseClass}
|
||||
defaultTitle="Controls"
|
||||
onChange={handleTeamSelect}
|
||||
description={() => {
|
||||
return null;
|
||||
}}
|
||||
includeNoTeams
|
||||
includeAll={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const onConnectClick = () => router.push(PATHS.ADMIN_INTEGRATIONS_MDM);
|
||||
|
||||
const renderBody = () => {
|
||||
if (isLoadingMdmApple) {
|
||||
return <Spinner />;
|
||||
}
|
||||
return mdmApple ? (
|
||||
<div>
|
||||
<TabsWrapper>
|
||||
<Tabs
|
||||
selectedIndex={getTabIndex(location.pathname)}
|
||||
onSelect={(i) => navigateToNav(i)}
|
||||
>
|
||||
<TabList>
|
||||
{controlsSubNav.map((navItem) => {
|
||||
return (
|
||||
<Tab key={navItem.name} data-text={navItem.name}>
|
||||
{navItem.name}
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</TabsWrapper>
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyTable
|
||||
header="Manage your macOS hosts"
|
||||
info="Connect Fleet to the Apple Push Certificates Portal to get started."
|
||||
primaryButton={
|
||||
<Button
|
||||
variant="brand"
|
||||
onClick={onConnectClick}
|
||||
className={`${baseClass}__connectAPC-button`}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<MainContent className={baseClass}>
|
||||
<div className={`${baseClass}__wrapper`}>
|
||||
<div className={`${baseClass}__header-wrap`}>{renderHeader()}</div>
|
||||
{renderBody()}
|
||||
</div>
|
||||
</MainContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlsWrapper;
|
0
frontend/pages/ControlsPage/_styles.scss
Normal file
0
frontend/pages/ControlsPage/_styles.scss
Normal file
1
frontend/pages/ControlsPage/index.ts
Normal file
1
frontend/pages/ControlsPage/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./ControlsWrapper";
|
@ -1 +0,0 @@
|
||||
export { default } from "./AppSettingsPage";
|
@ -16,13 +16,13 @@ import Spinner from "components/Spinner";
|
||||
import SideNav from "../components/SideNav";
|
||||
import ORG_SETTINGS_NAV_ITEMS from "./OrgSettingsNavItems";
|
||||
|
||||
interface IAppSettingsPageProps {
|
||||
interface IOrgSettingsPageProps {
|
||||
params: Params;
|
||||
}
|
||||
|
||||
export const baseClass = "app-settings";
|
||||
export const baseClass = "org-settings";
|
||||
|
||||
const AppSettingsPage = ({ params }: IAppSettingsPageProps) => {
|
||||
const OrgSettingsPage = ({ params }: IOrgSettingsPageProps) => {
|
||||
const { section } = params;
|
||||
const DEFAULT_SETTINGS_SECTION = ORG_SETTINGS_NAV_ITEMS[0];
|
||||
|
||||
@ -152,4 +152,4 @@ const AppSettingsPage = ({ params }: IAppSettingsPageProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AppSettingsPage;
|
||||
export default OrgSettingsPage;
|
@ -1,5 +1,4 @@
|
||||
.app-settings {
|
||||
|
||||
.org-settings {
|
||||
&__page-description {
|
||||
font-size: $x-small;
|
||||
color: $core-fleet-black;
|
||||
@ -11,7 +10,6 @@
|
||||
}
|
||||
|
||||
&__side-nav {
|
||||
|
||||
.org-settings-form {
|
||||
.form-field__label {
|
||||
.buttons {
|
1
frontend/pages/admin/OrgSettingsPage/index.ts
Normal file
1
frontend/pages/admin/OrgSettingsPage/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./OrgSettingsPage";
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { IAppConfigFormProps } from "pages/admin/AppSettingsPage/cards/constants";
|
||||
import { IAppConfigFormProps } from "pages/admin/OrgSettingsPage/cards/constants";
|
||||
|
||||
import SideNavItem from "../SideNavItem";
|
||||
|
||||
|
@ -378,105 +378,3 @@
|
||||
width: $col-md;
|
||||
}
|
||||
}
|
||||
|
||||
.site-nav-item {
|
||||
position: relative;
|
||||
transition: color 200ms ease-in-out;
|
||||
cursor: pointer;
|
||||
max-height: 50px;
|
||||
|
||||
&:hover {
|
||||
background-color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&--multiple.site-nav-item--active {
|
||||
background-color: transparent;
|
||||
border-right: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
font-size: $large;
|
||||
margin-right: $pad-small;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
font-weight: $regular;
|
||||
font-size: $x-small;
|
||||
|
||||
// Bolding text when the button is active causes a layout shift
|
||||
// so we add a hidden pseudo element with the same text string
|
||||
&:before {
|
||||
content: attr(data-text);
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
color: $core-white;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 14px 20px 17px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
text-align: center;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-bottom: 3px solid $core-vibrant-blue;
|
||||
background-color: $core-fleet-black;
|
||||
height: 47px;
|
||||
|
||||
&:hover {
|
||||
background-color: $core-fleet-black;
|
||||
}
|
||||
|
||||
.site-nav-item__name {
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 48px;
|
||||
transform: scale(0.5);
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.site-nav-container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.site-nav-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Router,
|
||||
} from "react-router";
|
||||
|
||||
import AppSettingsPage from "pages/admin/AppSettingsPage";
|
||||
import OrgSettingsPage from "pages/admin/OrgSettingsPage";
|
||||
import AdminIntegrationsPage from "pages/admin/IntegrationsPage";
|
||||
import AdminUserManagementPage from "pages/admin/UserManagementPage";
|
||||
import AdminTeamManagementPage from "pages/admin/TeamManagementPage";
|
||||
@ -44,6 +44,7 @@ import Fleet403 from "pages/errors/Fleet403";
|
||||
import Fleet404 from "pages/errors/Fleet404";
|
||||
import UserSettingsPage from "pages/UserSettingsPage";
|
||||
import SettingsWrapper from "pages/admin/SettingsWrapper/SettingsWrapper";
|
||||
import ControlsWrapper from "pages/ControlsPage/ControlsWrapper";
|
||||
import MembersPage from "pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage";
|
||||
import AgentOptionsPage from "pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage";
|
||||
import PATHS from "router/paths";
|
||||
@ -74,6 +75,22 @@ const AppWrapper = ({ children, location }: IAppWrapperProps) => (
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
// TODO: Replace below elements with the real thing
|
||||
const MacUpdatesPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>MacUpdates!</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const MacSettingsPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>MacSettings!</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const routes = (
|
||||
<Router history={browserHistory}>
|
||||
<Route path={PATHS.ROOT} component={AppWrapper}>
|
||||
@ -108,10 +125,10 @@ const routes = (
|
||||
<IndexRedirect to={"organization"} />
|
||||
<Route component={SettingsWrapper}>
|
||||
<Route component={AuthGlobalAdminRoutes}>
|
||||
<Route path="organization" component={AppSettingsPage} />
|
||||
<Route path="organization" component={OrgSettingsPage} />
|
||||
<Route
|
||||
path="organization/:section"
|
||||
component={AppSettingsPage}
|
||||
component={OrgSettingsPage}
|
||||
/>
|
||||
<Route path="integrations" component={AdminIntegrationsPage} />
|
||||
<Route
|
||||
@ -157,6 +174,15 @@ const routes = (
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="controls" component={AuthAnyMaintainerAnyAdminRoutes}>
|
||||
<IndexRedirect to={"mac-updates"} />
|
||||
<Route component={ControlsWrapper}>
|
||||
<Route path="mac-updates" component={MacUpdatesPage} />
|
||||
<Route path="mac-settings" component={MacSettingsPage} />
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
<Route path="software">
|
||||
<IndexRedirect to={"manage"} />
|
||||
<Route path="manage" component={ManageSoftwarePage} />
|
||||
|
@ -5,6 +5,9 @@ import URL_PREFIX from "./url_prefix";
|
||||
|
||||
export default {
|
||||
ROOT: `${URL_PREFIX}/`,
|
||||
CONTROLS: `${URL_PREFIX}/controls`,
|
||||
CONTROLS_MAC_UPDATES: `${URL_PREFIX}/controls/mac-updates`,
|
||||
CONTROLS_MAC_SETTINGS: `${URL_PREFIX}/controls/mac-settings`,
|
||||
DASHBOARD: `${URL_PREFIX}/dashboard`,
|
||||
DASHBOARD_LINUX: `${URL_PREFIX}/dashboard/linux`,
|
||||
DASHBOARD_MAC: `${URL_PREFIX}/dashboard/mac`,
|
||||
|
@ -6,6 +6,7 @@ $core-vibrant-red: #ff5c83;
|
||||
$core-fleet-purple: #ae6ddf;
|
||||
$core-white: #ffffff;
|
||||
$core-dark-blue-grey: #506e92;
|
||||
$site-nav-on-hover: #0e1533;
|
||||
|
||||
// UI
|
||||
$ui-fleet-black-75: #515774;
|
||||
|
Loading…
Reference in New Issue
Block a user