mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add new "Fleet Desktop" section to global settings page (#6161)
This commit is contained in:
parent
18de43f35b
commit
c146ea4aa4
1
changes/issue-5950-transparency-url
Normal file
1
changes/issue-5950-transparency-url
Normal file
@ -0,0 +1 @@
|
||||
* Enable UI for Fleet Premium licensees to set custom transparancy url for Fleet Desktop
|
@ -696,6 +696,15 @@ describe(
|
||||
cy.loginWithCySession("anna@organization.com", GOOD_PASSWORD);
|
||||
cy.visit("/settings/users");
|
||||
});
|
||||
it("hides access to Fleet Desktop settings", () => {
|
||||
cy.visit("settings/organization");
|
||||
cy.getAttached(".app-settings__form-nav-list").within(() => {
|
||||
cy.findByText(/organization info/i).should("exist");
|
||||
cy.findByText(/fleet desktop/i).should("not.exist");
|
||||
});
|
||||
cy.visit("settings/organization/fleet-desktop");
|
||||
cy.findAllByText(/access denied/i).should("exist");
|
||||
});
|
||||
it("hides access team settings", () => {
|
||||
cy.findByText(/teams/i).should("not.exist");
|
||||
});
|
||||
|
@ -682,6 +682,26 @@ describe("Premium tier - Global Admin user", () => {
|
||||
cy.findByLabelText(/password/i).should("exist");
|
||||
});
|
||||
});
|
||||
it("allows access to Fleet Desktop settings", () => {
|
||||
cy.visit("settings/organization");
|
||||
cy.getAttached(".app-settings__form-nav-list").within(() => {
|
||||
cy.findByText(/organization info/i).should("exist");
|
||||
cy.findByText(/fleet desktop/i)
|
||||
.should("exist")
|
||||
.click();
|
||||
});
|
||||
cy.getAttached("[id=transparency_url")
|
||||
.should("have.value", "https://fleetdm.com/transparency")
|
||||
.clear()
|
||||
.type("example.com/transparency");
|
||||
cy.findByRole("button", { name: /save/i }).click();
|
||||
cy.findByText(/successfully updated/i).should("exist");
|
||||
cy.visit("settings/organization/fleet-desktop");
|
||||
cy.getAttached("[id=transparency_url").should(
|
||||
"have.value",
|
||||
"example.com/transparency"
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("User profile page", () => {
|
||||
it("renders elements according to role-based access controls", () => {
|
||||
|
@ -72,6 +72,10 @@ export default PropTypes.shape({
|
||||
}),
|
||||
});
|
||||
|
||||
export interface IFleetDesktopSettings {
|
||||
transparency_url: string;
|
||||
}
|
||||
|
||||
export interface IConfigFormData {
|
||||
smtpAuthenticationMethod: string;
|
||||
smtpAuthenticationType: string;
|
||||
@ -105,6 +109,7 @@ export interface IConfigFormData {
|
||||
hostStatusWebhookHostPercentage?: number;
|
||||
hostStatusWebhookDaysCount?: number;
|
||||
enableUsageStatistics: boolean;
|
||||
transparency_url: string;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
@ -162,6 +167,7 @@ export interface IConfig {
|
||||
expiration: string;
|
||||
note: string;
|
||||
};
|
||||
fleet_desktop: IFleetDesktopSettings;
|
||||
vulnerabilities: {
|
||||
databases_path: string;
|
||||
periodicity: number;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useContext, useState, useEffect } from "react";
|
||||
import { useErrorHandler } from "react-error-boundary";
|
||||
import { useQuery } from "react-query";
|
||||
import { Params } from "react-router/lib/Router";
|
||||
import { Link } from "react-router";
|
||||
@ -19,6 +20,7 @@ import AgentOptions from "./cards/Agents";
|
||||
import HostStatusWebhook from "./cards/HostStatusWebhook";
|
||||
import Statistics from "./cards/Statistics";
|
||||
import Advanced from "./cards/Advanced";
|
||||
import FleetDesktop from "./cards/FleetDesktop";
|
||||
|
||||
interface IAppSettingsPageProps {
|
||||
params: Params;
|
||||
@ -29,8 +31,10 @@ export const baseClass = "app-settings";
|
||||
const AppSettingsPage = ({
|
||||
params: { section: sectionTitle },
|
||||
}: IAppSettingsPageProps): JSX.Element => {
|
||||
const { isFreeTier, isPremiumTier, setConfig } = useContext(AppContext);
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
const { setConfig } = useContext(AppContext);
|
||||
|
||||
const handlePageError = useErrorHandler();
|
||||
|
||||
const [activeSection, setActiveSection] = useState<string>("info");
|
||||
|
||||
@ -50,7 +54,7 @@ const AppSettingsPage = ({
|
||||
};
|
||||
|
||||
const onFormSubmit = useCallback(
|
||||
(formData: IConfig) => {
|
||||
(formData: Partial<IConfig>) => {
|
||||
if (!appConfig) {
|
||||
return false;
|
||||
}
|
||||
@ -87,10 +91,13 @@ const AppSettingsPage = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFreeTier && sectionTitle === "fleet-desktop") {
|
||||
handlePageError({ status: 403 });
|
||||
}
|
||||
if (sectionTitle) {
|
||||
setActiveSection(sectionTitle);
|
||||
}
|
||||
}, [sectionTitle]);
|
||||
}, [isFreeTier, sectionTitle]);
|
||||
|
||||
const renderSection = () => {
|
||||
if (!isLoading && appConfig) {
|
||||
@ -123,6 +130,13 @@ const AppSettingsPage = ({
|
||||
{activeSection === "advanced" && (
|
||||
<Advanced appConfig={appConfig} handleSubmit={onFormSubmit} />
|
||||
)}
|
||||
{isPremiumTier && activeSection === "fleet-desktop" && (
|
||||
<FleetDesktop
|
||||
appConfig={appConfig}
|
||||
isPremiumTier={isPremiumTier}
|
||||
handleSubmit={onFormSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -208,6 +222,18 @@ const AppSettingsPage = ({
|
||||
Usage statistics
|
||||
</Link>
|
||||
</li>
|
||||
{isPremiumTier && (
|
||||
<li>
|
||||
<Link
|
||||
className={`${baseClass}__nav-link ${isNavItemActive(
|
||||
"fleet-desktop"
|
||||
)}`}
|
||||
to={PATHS.ADMIN_SETTINGS_FLEET_DESKTOP}
|
||||
>
|
||||
Fleet Desktop
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<Link
|
||||
className={`${baseClass}__nav-link ${isNavItemActive(
|
||||
|
@ -293,6 +293,18 @@
|
||||
margin-bottom: $pad-large;
|
||||
}
|
||||
|
||||
&__transparency {
|
||||
color: $core-vibrant-blue;
|
||||
font-size: $x-small;
|
||||
font-weight: $bold;
|
||||
text-decoration: none;
|
||||
|
||||
img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.component__tooltip-wrapper {
|
||||
margin-bottom: $pad-xsmall;
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { IConfig, IConfigFormData } from "interfaces/config";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import OpenNewTabIcon from "../../../../../../assets/images/open-new-tab-12x12@2x.png";
|
||||
|
||||
import {
|
||||
DEFAULT_TRANSPARENCY_URL,
|
||||
IAppConfigFormProps,
|
||||
IFormField,
|
||||
IAppConfigFormErrors,
|
||||
} from "../constants";
|
||||
|
||||
const baseClass = "app-config-form";
|
||||
|
||||
const FleetDesktop = ({
|
||||
appConfig,
|
||||
handleSubmit,
|
||||
isPremiumTier,
|
||||
}: IAppConfigFormProps): JSX.Element => {
|
||||
const [formData, setFormData] = useState<
|
||||
Pick<IConfigFormData, "transparency_url">
|
||||
>({
|
||||
transparency_url:
|
||||
appConfig.fleet_desktop?.transparency_url || DEFAULT_TRANSPARENCY_URL,
|
||||
});
|
||||
|
||||
const [formErrors, setFormErrors] = useState<IAppConfigFormErrors>({});
|
||||
|
||||
const handleInputChange = ({ value }: IFormField) => {
|
||||
setFormData({ transparency_url: value.toString() });
|
||||
setFormErrors({});
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const { transparency_url } = formData;
|
||||
|
||||
const errors: IAppConfigFormErrors = {};
|
||||
if (!transparency_url) {
|
||||
errors.transparency_url = "Transparency URL name must be present";
|
||||
} else if (!validUrl(transparency_url)) {
|
||||
errors.transparency_url = `${transparency_url} is not a valid URL`;
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
};
|
||||
|
||||
const onFormSubmit = (evt: React.MouseEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const formDataForAPI: Pick<IConfig, "fleet_desktop"> = {
|
||||
fleet_desktop: {
|
||||
transparency_url: formData.transparency_url,
|
||||
},
|
||||
};
|
||||
|
||||
handleSubmit(formDataForAPI);
|
||||
};
|
||||
|
||||
if (!isPremiumTier) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={baseClass} onSubmit={onFormSubmit} autoComplete="off">
|
||||
<div className={`${baseClass}__section`}>
|
||||
<h2>Fleet Desktop</h2>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<InputField
|
||||
label="Custom transparency URL"
|
||||
onChange={handleInputChange}
|
||||
name="transparency_url"
|
||||
value={formData.transparency_url}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.transparency_url}
|
||||
/>
|
||||
<p className={`${baseClass}__component-label`}>
|
||||
When an end user clicks “Transparency” in the Fleet Desktop menu, by
|
||||
default they are taken to{" "}
|
||||
<a
|
||||
className={`${baseClass}__transparency`}
|
||||
href="https://fleetdm.com/transparency"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{" "}
|
||||
https://fleetdm.com/transparency{" "}
|
||||
<img className="icon" src={OpenNewTabIcon} alt="open new tab" />
|
||||
</a>{" "}
|
||||
. You can override the URL to take them to a resource of your
|
||||
choice.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="brand"
|
||||
disabled={Object.keys(formErrors).length > 0}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default FleetDesktop;
|
@ -0,0 +1 @@
|
||||
export { default } from "./FleetDesktop";
|
@ -1,7 +1,10 @@
|
||||
import { IConfig } from "interfaces/config";
|
||||
|
||||
export const DEFAULT_TRANSPARENCY_URL = "https://fleetdm.com/transparency";
|
||||
|
||||
export interface IAppConfigFormProps {
|
||||
appConfig: IConfig;
|
||||
isPremiumTier?: boolean;
|
||||
handleSubmit: any;
|
||||
}
|
||||
|
||||
@ -26,6 +29,7 @@ export interface IAppConfigFormErrors {
|
||||
destination_url?: string | null;
|
||||
host_expiry_window?: string | null;
|
||||
agent_options?: string | null;
|
||||
transparency_url?: string | null;
|
||||
}
|
||||
|
||||
export const authMethodOptions = [
|
||||
|
@ -18,6 +18,7 @@ export default {
|
||||
ADMIN_SETTINGS_HOST_STATUS_WEBHOOK: `${URL_PREFIX}/settings/organization/host-status-webhook`,
|
||||
ADMIN_SETTINGS_STATISTICS: `${URL_PREFIX}/settings/organization/statistics`,
|
||||
ADMIN_SETTINGS_ADVANCED: `${URL_PREFIX}/settings/organization/advanced`,
|
||||
ADMIN_SETTINGS_FLEET_DESKTOP: `${URL_PREFIX}/settings/organization/fleet-desktop`,
|
||||
ALL_PACKS: `${URL_PREFIX}/packs/all`,
|
||||
EDIT_PACK: (packId: number): string => {
|
||||
return `${URL_PREFIX}/packs/${packId}/edit`;
|
||||
|
Loading…
Reference in New Issue
Block a user