mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
New card for preview self host policies (#2666)
This commit is contained in:
parent
623a38aa9d
commit
9f804e1858
31 changed files with 627 additions and 82 deletions
BIN
assets/images/icon-action-disable-red-16x16@2x.png
Normal file
BIN
assets/images/icon-action-disable-red-16x16@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 952 B |
BIN
assets/images/icon-check-circle-green-16x16@2x.png
Normal file
BIN
assets/images/icon-check-circle-green-16x16@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 508 B |
BIN
assets/images/icon-exclamation-circle-red-16x16@2x.png
Normal file
BIN
assets/images/icon-exclamation-circle-red-16x16@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 566 B |
BIN
assets/images/icon-refetch-white-12x12@2x.png
Normal file
BIN
assets/images/icon-refetch-white-12x12@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 437 B |
BIN
assets/images/laptop-mac.png
Normal file
BIN
assets/images/laptop-mac.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/images/slack-button-get-help.png
Normal file
BIN
assets/images/slack-button-get-help.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
1
changes/issue-2382-preview-host-home
Normal file
1
changes/issue-2382-preview-host-home
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Added new cards to home page to display preview mode host and its policies
|
||||
|
|
@ -15,6 +15,7 @@ type InitialStateType = {
|
|||
currentUser: IUser | null;
|
||||
currentTeam: ITeam | undefined;
|
||||
enrollSecret: IEnrollSecret[] | null;
|
||||
isPreviewMode: boolean | undefined;
|
||||
isFreeTier: boolean | undefined;
|
||||
isPremiumTier: boolean | undefined;
|
||||
isGlobalAdmin: boolean | undefined;
|
||||
|
|
@ -37,6 +38,7 @@ const initialState = {
|
|||
currentUser: null,
|
||||
currentTeam: undefined,
|
||||
enrollSecret: null,
|
||||
isPreviewMode: false,
|
||||
isFreeTier: undefined,
|
||||
isPremiumTier: undefined,
|
||||
isGlobalAdmin: undefined,
|
||||
|
|
@ -61,6 +63,10 @@ const actions = {
|
|||
SET_ENROLL_SECRET: "SET_ENROLL_SECRET",
|
||||
};
|
||||
|
||||
const detectPreview = () => {
|
||||
return window.location.origin === "http://localhost:1337";
|
||||
};
|
||||
|
||||
// helper function - this is run every
|
||||
// time currentUser, config, or teamId is changed
|
||||
const setPermissions = (user: IUser, config: IConfig, teamId = 0) => {
|
||||
|
|
@ -125,6 +131,7 @@ const AppProvider = ({ children }: Props) => {
|
|||
currentUser: state.currentUser,
|
||||
currentTeam: state.currentTeam,
|
||||
enrollSecret: state.enrollSecret,
|
||||
isPreviewMode: detectPreview(),
|
||||
isFreeTier: state.isFreeTier,
|
||||
isPremiumTier: state.isPremiumTier,
|
||||
isGlobalAdmin: state.isGlobalAdmin,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import { ISoftware } from "interfaces/software";
|
|||
|
||||
import TeamsDropdown from "components/TeamsDropdown";
|
||||
import Button from "components/buttons/Button";
|
||||
import HostsSummary from "./HostsSummary";
|
||||
import ActivityFeed from "./ActivityFeed";
|
||||
import InfoCard from "./components/InfoCard";
|
||||
import HostsSummary from "./cards/HostsSummary";
|
||||
import ActivityFeed from "./cards/ActivityFeed";
|
||||
import Software from "./cards/Software";
|
||||
import LearnFleet from "./cards/LearnFleet";
|
||||
import WelcomeHost from "./cards/WelcomeHost";
|
||||
import LinkArrow from "../../../assets/images/icon-arrow-right-vibrant-blue-10x18@2x.png";
|
||||
import Software from "./Software";
|
||||
|
||||
interface ITeamsResponse {
|
||||
teams: ITeam[];
|
||||
|
|
@ -24,9 +27,13 @@ const baseClass = "homepage";
|
|||
|
||||
const Homepage = (): JSX.Element => {
|
||||
const { MANAGE_HOSTS } = paths;
|
||||
const { config, currentTeam, isPremiumTier, setCurrentTeam } = useContext(
|
||||
AppContext
|
||||
);
|
||||
const {
|
||||
config,
|
||||
currentTeam,
|
||||
isPremiumTier,
|
||||
isPreviewMode,
|
||||
setCurrentTeam,
|
||||
} = useContext(AppContext);
|
||||
|
||||
const [isSoftwareModalOpen, setIsSoftwareModalOpen] = useState<boolean>(
|
||||
false
|
||||
|
|
@ -46,8 +53,6 @@ const Homepage = (): JSX.Element => {
|
|||
setCurrentTeam(selectedTeam);
|
||||
};
|
||||
|
||||
const canSeeActivity = !isPremiumTier || !currentTeam;
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__header-wrap`}>
|
||||
|
|
@ -69,25 +74,28 @@ const Homepage = (): JSX.Element => {
|
|||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__section one-column`}>
|
||||
<div className={`${baseClass}__info-card`}>
|
||||
<div className={`${baseClass}__section-title`}>
|
||||
<h2>Hosts</h2>
|
||||
<Link to={MANAGE_HOSTS} className={`${baseClass}__action-button`}>
|
||||
<span>View all hosts</span>
|
||||
<img src={LinkArrow} alt="link arrow" id="link-arrow" />
|
||||
</Link>
|
||||
</div>
|
||||
<InfoCard
|
||||
title="Hosts"
|
||||
action={{ type: "link", to: MANAGE_HOSTS, text: "View all hosts" }}
|
||||
>
|
||||
<HostsSummary />
|
||||
</div>
|
||||
</InfoCard>
|
||||
</div>
|
||||
{canSeeActivity && (
|
||||
{isPreviewMode && (
|
||||
<div className={`${baseClass}__section two-column`}>
|
||||
<InfoCard title="Welcome to Fleet">
|
||||
<WelcomeHost />
|
||||
</InfoCard>
|
||||
<InfoCard title="Learn how to use Fleet">
|
||||
<LearnFleet />
|
||||
</InfoCard>
|
||||
</div>
|
||||
)}
|
||||
{!isPreviewMode && !currentTeam && (
|
||||
<div className={`${baseClass}__section one-column`}>
|
||||
<div className={`${baseClass}__info-card`}>
|
||||
<div className={`${baseClass}__section-title`}>
|
||||
<h2>Activity</h2>
|
||||
</div>
|
||||
<InfoCard title="Activity">
|
||||
<ActivityFeed />
|
||||
</div>
|
||||
</InfoCard>
|
||||
</div>
|
||||
)}
|
||||
{/* TODO: Re-add this commented out section once the /software API is running */}
|
||||
|
|
@ -96,32 +104,23 @@ const Homepage = (): JSX.Element => {
|
|||
${currentTeam ? 'one' : 'two'}-column
|
||||
`}>
|
||||
{!currentTeam && (
|
||||
<div className={`${baseClass}__info-card`}>
|
||||
<div className={`${baseClass}__section-title`}>
|
||||
<h2>Software</h2>
|
||||
<Button
|
||||
className={`${baseClass}__action-button`}
|
||||
variant="text-link"
|
||||
onClick={() => setIsSoftwareModalOpen(true)}
|
||||
>
|
||||
<>
|
||||
<span>View all software</span>
|
||||
<img src={LinkArrow} alt="link arrow" id="link-arrow" />
|
||||
</>
|
||||
</Button>
|
||||
</div>
|
||||
<InfoCard
|
||||
title="Software"
|
||||
action={{
|
||||
type: button,
|
||||
text: "View all software",
|
||||
onClick: () => setIsSoftwareModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<Software
|
||||
isModalOpen={isSoftwareModalOpen}
|
||||
setIsSoftwareModalOpen={setIsSoftwareModalOpen}
|
||||
/>
|
||||
</div>
|
||||
</InfoCard>
|
||||
)}
|
||||
<div className={`${baseClass}__info-card`}>
|
||||
<div className={`${baseClass}__section-title`}>
|
||||
<h2>Activity</h2>
|
||||
</div>
|
||||
<InfoCard title="Activity">
|
||||
<ActivityFeed />
|
||||
</div>
|
||||
</InfoCard>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,19 +12,6 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
&__action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $x-small;
|
||||
color: $core-vibrant-blue;
|
||||
font-weight: $bold;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#link-arrow {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
&__header-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -78,19 +65,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__info-card {
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
background-color: $core-white;
|
||||
border: 1px solid $ui-fleet-blue-15;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 0 0 $ui-fleet-blue-15;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import Button from "components/buttons/Button";
|
|||
import Spinner from "components/loaders/Spinner"; // @ts-ignore
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
|
||||
import ErrorIcon from "../../../../assets/images/icon-error-16x16@2x.png";
|
||||
import OpenNewTabIcon from "../../../../assets/images/open-new-tab-12x12@2x.png";
|
||||
import ErrorIcon from "../../../../../assets/images/icon-error-16x16@2x.png";
|
||||
import OpenNewTabIcon from "../../../../../assets/images/open-new-tab-12x12@2x.png";
|
||||
|
||||
const baseClass = "activity-feed";
|
||||
|
||||
|
|
@ -4,9 +4,9 @@ import { reduce } from "lodash";
|
|||
import { ILabel } from "interfaces/label";
|
||||
// @ts-ignore
|
||||
import { getLabels } from "redux/nodes/components/ManageHostsPage/actions";
|
||||
import WindowsIcon from "../../../../assets/images/icon-windows-48x48@2x.png";
|
||||
import LinuxIcon from "../../../../assets/images/icon-linux-48x48@2x.png";
|
||||
import MacIcon from "../../../../assets/images/icon-mac-48x48@2x.png";
|
||||
import WindowsIcon from "../../../../../assets/images/icon-windows-48x48@2x.png";
|
||||
import LinuxIcon from "../../../../../assets/images/icon-linux-48x48@2x.png";
|
||||
import MacIcon from "../../../../../assets/images/icon-mac-48x48@2x.png";
|
||||
|
||||
const baseClass = "hosts-summary";
|
||||
|
||||
27
frontend/pages/Homepage/cards/LearnFleet/LearnFleet.tsx
Normal file
27
frontend/pages/Homepage/cards/LearnFleet/LearnFleet.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
|
||||
import LinkArrow from "../../../../../assets/images/icon-arrow-right-vibrant-blue-10x18@2x.png";
|
||||
|
||||
const baseClass = "learn-fleet";
|
||||
|
||||
const LearnFleet = (): JSX.Element => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<p>
|
||||
Want to explore Fleet's features? Learn how to ask questions about
|
||||
your device using queries.
|
||||
</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="homepage-info-card__action-button"
|
||||
href="https://fleetdm.com/docs/using-fleet/learn-how-to-use-fleet"
|
||||
>
|
||||
Learn how to use Fleet
|
||||
<img src={LinkArrow} alt="link arrow" id="link-arrow" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LearnFleet;
|
||||
5
frontend/pages/Homepage/cards/LearnFleet/_styles.scss
Normal file
5
frontend/pages/Homepage/cards/LearnFleet/_styles.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.learn-fleet {
|
||||
p {
|
||||
font-size: $x-small;
|
||||
}
|
||||
}
|
||||
1
frontend/pages/Homepage/cards/LearnFleet/index.ts
Normal file
1
frontend/pages/Homepage/cards/LearnFleet/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./LearnFleet";
|
||||
|
|
@ -6,7 +6,7 @@ import { ISoftware } from "interfaces/software";
|
|||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import Chevron from "../../../../assets/images/icon-chevron-blue-16x16@2x.png";
|
||||
import Chevron from "../../../../../assets/images/icon-chevron-blue-16x16@2x.png";
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
293
frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx
Normal file
293
frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
import React, { useState } from "react";
|
||||
import PATHS from "router/paths";
|
||||
import { Link } from "react-router";
|
||||
import { useQuery } from "react-query";
|
||||
import { useDispatch } from "react-redux";
|
||||
import moment from "moment";
|
||||
|
||||
import { IHost } from "interfaces/host";
|
||||
import { IHostPolicy } from "interfaces/host_policy";
|
||||
import hostAPI from "services/entities/hosts"; // @ts-ignore
|
||||
import { renderFlash } from "redux/nodes/notifications/actions";
|
||||
|
||||
import Spinner from "components/loaders/Spinner";
|
||||
import Button from "components/buttons/Button";
|
||||
import Modal from "components/modals/Modal";
|
||||
import LaptopMac from "../../../../../assets/images/laptop-mac.png";
|
||||
import LinkArrow from "../../../../../assets/images/icon-arrow-right-vibrant-blue-10x18@2x.png";
|
||||
import IconDisabled from "../../../../../assets/images/icon-action-disable-red-16x16@2x.png";
|
||||
import IconPassed from "../../../../../assets/images/icon-check-circle-green-16x16@2x.png";
|
||||
import IconError from "../../../../../assets/images/icon-exclamation-circle-red-16x16@2x.png";
|
||||
import IconChevron from "../../../../../assets/images/icon-chevron-purple-9x6@2x.png";
|
||||
import SlackButton from "../../../../../assets/images/slack-button-get-help.png";
|
||||
|
||||
interface IHostResponse {
|
||||
host: IHost;
|
||||
}
|
||||
|
||||
const baseClass = "welcome-host";
|
||||
const HOST_ID = 8;
|
||||
|
||||
const WelcomeHost = (): JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
|
||||
const [currentPolicyShown, setCurrentPolicyShown] = useState<IHostPolicy>();
|
||||
const [showPolicyModal, setShowPolicyModal] = useState<boolean>(false);
|
||||
const [
|
||||
showRefetchLoadingSpinner,
|
||||
setShowRefetchLoadingSpinner,
|
||||
] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
isLoading: isLoadingHost,
|
||||
data: host,
|
||||
error: loadingHostError,
|
||||
refetch: fullyReloadHost,
|
||||
} = useQuery<IHostResponse, Error, IHost>(
|
||||
["host"],
|
||||
() => hostAPI.load(HOST_ID),
|
||||
{
|
||||
select: (data: IHostResponse) => data.host,
|
||||
onSuccess: (returnedHost) => {
|
||||
setShowRefetchLoadingSpinner(returnedHost.refetch_requested);
|
||||
if (returnedHost.refetch_requested) {
|
||||
// Code duplicated from HostDetailsPage. See comments there.
|
||||
if (!refetchStartTime) {
|
||||
if (returnedHost.status === "online") {
|
||||
setRefetchStartTime(Date.now());
|
||||
setTimeout(() => {
|
||||
fullyReloadHost();
|
||||
}, 1000);
|
||||
} else {
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
}
|
||||
} else {
|
||||
const totalElapsedTime = Date.now() - refetchStartTime;
|
||||
if (totalElapsedTime < 60000) {
|
||||
if (returnedHost.status === "online") {
|
||||
setTimeout(() => {
|
||||
fullyReloadHost();
|
||||
}, 1000);
|
||||
} else {
|
||||
dispatch(
|
||||
renderFlash(
|
||||
"error",
|
||||
`This host is offline. Please try refetching host vitals later.`
|
||||
)
|
||||
);
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
}
|
||||
} else {
|
||||
dispatch(
|
||||
renderFlash(
|
||||
"error",
|
||||
`We're having trouble fetching fresh vitals for this host. Please try again later.`
|
||||
)
|
||||
);
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error);
|
||||
dispatch(
|
||||
renderFlash("error", `Unable to load host. Please try again.`)
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const onRefetchHost = async () => {
|
||||
if (host) {
|
||||
setShowRefetchLoadingSpinner(true);
|
||||
|
||||
try {
|
||||
await hostAPI.refetch(host).then(() => {
|
||||
setRefetchStartTime(Date.now());
|
||||
setTimeout(() => fullyReloadHost(), 1000);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch(renderFlash("error", `Host "${host.hostname}" refetch error`));
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePolicyModal = (id: number) => {
|
||||
const policy = host?.policies.find((p) => p.id === id);
|
||||
|
||||
if (policy) {
|
||||
setCurrentPolicyShown(policy);
|
||||
setShowPolicyModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoadingHost) {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__loading`}>
|
||||
<p>Adding your device to Fleet</p>
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (loadingHostError) {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__error`}>
|
||||
<p>
|
||||
<img
|
||||
alt="Disabled icon"
|
||||
className="icon-disabled"
|
||||
src={IconDisabled}
|
||||
/>
|
||||
Your device is not communicating with Fleet.
|
||||
</p>
|
||||
<p>Join the #fleet Slack channel for help troubleshooting.</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://osquery.slack.com/archives/C01DXJL16D8"
|
||||
>
|
||||
<img
|
||||
alt="Get help on Slack"
|
||||
className="button-slack"
|
||||
src={SlackButton}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (host && !host.policies) {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__error`}>
|
||||
<p className="error-message">
|
||||
<img
|
||||
alt="Disabled icon"
|
||||
className="icon-disabled"
|
||||
src={IconDisabled}
|
||||
/>
|
||||
No policies apply to your device.
|
||||
</p>
|
||||
<p>Join the #fleet Slack channel for help troubleshooting.</p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://osquery.slack.com/archives/C01DXJL16D8"
|
||||
>
|
||||
<img
|
||||
alt="Get help on Slack"
|
||||
className="button-slack"
|
||||
src={SlackButton}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (host) {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__intro`}>
|
||||
<img alt="" src={LaptopMac} />
|
||||
<div className="info">
|
||||
<Link to={PATHS.HOST_DETAILS(host)} className="external-link">
|
||||
{host.hostname}
|
||||
<img alt="" src={LinkArrow} />
|
||||
</Link>
|
||||
<p>
|
||||
Your device is successully connected to this local preview of
|
||||
Fleet.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__blurb`}>
|
||||
<p>
|
||||
Fleet already ran the following checks to assess the security of
|
||||
your device:{" "}
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__policies`}>
|
||||
{host.policies?.slice(0, 10).map((p) => (
|
||||
<div className="policy-block">
|
||||
<div className="info">
|
||||
<img
|
||||
alt={p.response}
|
||||
src={p.response === "passing" ? IconPassed : IconError}
|
||||
/>
|
||||
{p.query_name}
|
||||
</div>
|
||||
<Button
|
||||
variant="text-icon"
|
||||
onClick={() => handlePolicyModal(p.id)}
|
||||
>
|
||||
<img alt="" src={IconChevron} />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{host.policies?.length > 10 && (
|
||||
<Link to={PATHS.HOST_DETAILS(host)} className="external-link">
|
||||
Go to Host details to see all checks
|
||||
<img alt="" src={LinkArrow} />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${baseClass}__blurb`}>
|
||||
<p>
|
||||
Resolved a failing check? Refetch your device information to verify.
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${baseClass}__refetch`}>
|
||||
<Button
|
||||
variant="blue-green"
|
||||
className={`refetch-spinner ${
|
||||
showRefetchLoadingSpinner ? "spin" : ""
|
||||
}`}
|
||||
onClick={onRefetchHost}
|
||||
disabled={showRefetchLoadingSpinner}
|
||||
>
|
||||
Refetch
|
||||
</Button>
|
||||
<span>Last updated {moment(host.detail_updated_at).fromNow()}</span>
|
||||
</div>
|
||||
{showPolicyModal && (
|
||||
<Modal
|
||||
title={currentPolicyShown?.query_name || ""}
|
||||
onExit={() => setShowPolicyModal(false)}
|
||||
className={`${baseClass}__policy-modal`}
|
||||
>
|
||||
<>
|
||||
<p>{currentPolicyShown?.query_description}</p>
|
||||
{currentPolicyShown?.resolution && (
|
||||
<p>
|
||||
<b>Resolve:</b>
|
||||
{currentPolicyShown.resolution}
|
||||
</p>
|
||||
)}
|
||||
<div className="done">
|
||||
<Button
|
||||
variant="brand"
|
||||
onClick={() => setShowPolicyModal(false)}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <Spinner />;
|
||||
};
|
||||
|
||||
export default WelcomeHost;
|
||||
141
frontend/pages/Homepage/cards/WelcomeHost/_styles.scss
Normal file
141
frontend/pages/Homepage/cards/WelcomeHost/_styles.scss
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
.welcome-host {
|
||||
p {
|
||||
font-size: $x-small;
|
||||
}
|
||||
|
||||
.button-slack {
|
||||
width: 173px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.icon-disabled {
|
||||
width: 16px;
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
p.error-message {
|
||||
font-weight: $bold;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.external-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $x-small;
|
||||
color: $core-vibrant-blue;
|
||||
font-weight: $bold;
|
||||
text-decoration: none !important;
|
||||
|
||||
img {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-top: $pad-large;
|
||||
}
|
||||
}
|
||||
&__intro {
|
||||
margin-top: $pad-large;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > img {
|
||||
width: 126px;
|
||||
}
|
||||
.info {
|
||||
margin-left: $pad-medium;
|
||||
flex: 1;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
margin-top: $pad-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__policies {
|
||||
margin-top: $pad-large;
|
||||
margin-bottom: $pad-large;
|
||||
|
||||
.policy-block {
|
||||
padding: $pad-small 12px;
|
||||
border: 1px solid $ui-fleet-black-25;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.info {
|
||||
font-size: $small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
|
||||
img {
|
||||
margin-right: $pad-large;
|
||||
transform: scale(0.5);
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
button {
|
||||
height: auto;
|
||||
|
||||
img {
|
||||
transform: scale(0.58) rotate(-90deg);
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&__refetch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
margin-right: $pad-small;
|
||||
|
||||
&.refetch-spinner {
|
||||
color: $core-white;
|
||||
font-size: $x-small;
|
||||
height: 38px;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 5px 0 0 0; // centers spin
|
||||
display: inline-block;
|
||||
content: url(../assets/images/icon-refetch-white-12x12@2x.png);
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
&.spin::before {
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: scale(0.5) rotate(0deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.5) rotate(360deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
span {
|
||||
padding-left: 4px;
|
||||
font-size: $x-small;
|
||||
}
|
||||
}
|
||||
&__policy-modal {
|
||||
.done {
|
||||
margin-top: $pad-large;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
frontend/pages/Homepage/cards/WelcomeHost/index.ts
Normal file
1
frontend/pages/Homepage/cards/WelcomeHost/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./WelcomeHost";
|
||||
65
frontend/pages/Homepage/components/InfoCard/InfoCard.tsx
Normal file
65
frontend/pages/Homepage/components/InfoCard/InfoCard.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-router";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import LinkArrow from "../../../../../assets/images/icon-arrow-right-vibrant-blue-10x18@2x.png";
|
||||
|
||||
interface IInfoCardProps {
|
||||
title: string;
|
||||
children: React.ReactChild | React.ReactChild[];
|
||||
action?:
|
||||
| {
|
||||
type: "link";
|
||||
to: string;
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: "button";
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const baseClass = "homepage-info-card";
|
||||
|
||||
const InfoCard = ({ title, children, action }: IInfoCardProps) => {
|
||||
const renderAction = () => {
|
||||
if (action) {
|
||||
if (action.type === "button") {
|
||||
return (
|
||||
<Button
|
||||
className={`${baseClass}__action-button`}
|
||||
variant="text-link"
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<>
|
||||
<span>{action.text}</span>
|
||||
<img src={LinkArrow} alt="link arrow" id="link-arrow" />
|
||||
</>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={action.to} className={`${baseClass}__action-button`}>
|
||||
<span>{action.text}</span>
|
||||
<img src={LinkArrow} alt="link arrow" id="link-arrow" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__section-title`}>
|
||||
<h2>{title}</h2>
|
||||
{renderAction()}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoCard;
|
||||
31
frontend/pages/Homepage/components/InfoCard/_styles.scss
Normal file
31
frontend/pages/Homepage/components/InfoCard/_styles.scss
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
.homepage-info-card {
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
background-color: $core-white;
|
||||
border: 1px solid $ui-fleet-blue-15;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 0 0 $ui-fleet-blue-15;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__section-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
h2 {
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $x-small;
|
||||
color: $core-vibrant-blue;
|
||||
font-weight: $bold;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
#link-arrow {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
1
frontend/pages/Homepage/components/InfoCard/index.ts
Normal file
1
frontend/pages/Homepage/components/InfoCard/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./InfoCard";
|
||||
|
|
@ -20,7 +20,7 @@ interface ILoginData {
|
|||
|
||||
const PreviewLoginPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { setCurrentUser } = useContext(AppContext);
|
||||
const { isPreviewMode, setCurrentUser } = useContext(AppContext);
|
||||
const [loginVisible, setLoginVisible] = useState<boolean>(true);
|
||||
|
||||
const onSubmit = debounce((formData: ILoginData) => {
|
||||
|
|
@ -41,7 +41,7 @@ const PreviewLoginPage = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.origin === "http://localhost:1337") {
|
||||
if (isPreviewMode) {
|
||||
onSubmit({
|
||||
email: "admin@example.com",
|
||||
password: "admin123#",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
// @ts-ignore
|
||||
import { AppContext } from "context/app"; // @ts-ignore
|
||||
import Fleet from "fleet"; // @ts-ignore
|
||||
import { stringToClipboard } from "utilities/copy_text";
|
||||
import { ITeam } from "interfaces/team";
|
||||
|
|
@ -49,6 +49,7 @@ const PlatformWrapper = ({
|
|||
selectedTeam,
|
||||
onCancel,
|
||||
}: IPlatformWrapperProp): JSX.Element => {
|
||||
const { config } = useContext(AppContext);
|
||||
const [copyMessage, setCopyMessage] = useState<string>("");
|
||||
const [certificate, setCertificate] = useState<string | undefined>(undefined);
|
||||
const [fetchCertificateError, setFetchCertificateError] = useState<
|
||||
|
|
@ -86,7 +87,7 @@ const PlatformWrapper = ({
|
|||
enrollSecret = selectedTeam.secrets[0].secret;
|
||||
}
|
||||
|
||||
let installerString = `fleetctl package --type=${platform} --fleet-url=https://localhost:8412 --enroll-secret=${enrollSecret}`;
|
||||
let installerString = `fleetctl package --type=${platform} --fleet-url=${config?.server_url} --enroll-secret=${enrollSecret}`;
|
||||
if (platform === "rpm" || platform === "deb") {
|
||||
installerString +=
|
||||
" --fleet-certificate=/home/username/Downloads/fleet.pem";
|
||||
|
|
|
|||
Loading…
Reference in a new issue