diff --git a/src/app/Agent/AboutAgentCard.tsx b/src/app/Agent/AboutAgentCard.tsx new file mode 100644 index 000000000..2eaab4bc7 --- /dev/null +++ b/src/app/Agent/AboutAgentCard.tsx @@ -0,0 +1,55 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import { Card, CardBody, CardTitle } from '@patternfly/react-core'; +import * as React from 'react'; + +export interface AboutAgentCardProps {} + +export const AboutAgentCard: React.FunctionComponent = (props) => { + return ( + + About the JMC Agent + + The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make use of + the JMC Agent, the agent jar must be present in the same container as the target, and the target must be started + with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the user can upload probe + templates to Cryostat and insert them to the target, as well as view or remove currently active probes. + + + ); +}; diff --git a/src/app/Agent/AgentLiveProbes.tsx b/src/app/Agent/AgentLiveProbes.tsx index f8a1a550c..91b30c73e 100644 --- a/src/app/Agent/AgentLiveProbes.tsx +++ b/src/app/Agent/AgentLiveProbes.tsx @@ -41,17 +41,11 @@ import { NotificationCategory } from '@app/Shared/Services/NotificationChannel.s import { useSubscriptions } from '@app/utils/useSubscriptions'; import { Button, - Card, - CardBody, - CardHeaderMain, - CardHeader, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem, TextInput, - Text, - TextVariants, Stack, StackItem, EmptyState, @@ -74,6 +68,7 @@ import { EventProbe } from '@app/Shared/Services/Api.service'; import { DeleteWarningModal } from '@app/Modal/DeleteWarningModal'; import { DeleteWarningType } from '@app/Modal/DeleteWarningUtils'; import { SearchIcon } from '@patternfly/react-icons'; +import { AboutAgentCard } from './AboutAgentCard'; export interface AgentLiveProbesProps {} @@ -245,20 +240,7 @@ export const AgentLiveProbes: React.FunctionComponent = (p <> - - - - About the JMC Agent - - - - The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make - use of the JMC Agent, the agent jar must be present in the same container as the target, and the target - must be started with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the - user can upload probe templates to Cryostat and insert them to the target, as well as view or remove - currently active probes. - - + diff --git a/src/app/Agent/AgentProbeTemplates.tsx b/src/app/Agent/AgentProbeTemplates.tsx index 7b23e73ef..409cd254b 100644 --- a/src/app/Agent/AgentProbeTemplates.tsx +++ b/src/app/Agent/AgentProbeTemplates.tsx @@ -42,10 +42,6 @@ import { useSubscriptions } from '@app/utils/useSubscriptions'; import { ActionGroup, Button, - Card, - CardBody, - CardHeaderMain, - CardHeader, FileUpload, Form, FormGroup, @@ -56,8 +52,6 @@ import { ToolbarGroup, ToolbarItem, TextInput, - Text, - TextVariants, StackItem, Stack, EmptyState, @@ -83,6 +77,7 @@ import { ErrorView } from '@app/ErrorView/ErrorView'; import { ProbeTemplate } from '@app/Shared/Services/Api.service'; import { DeleteWarningType } from '@app/Modal/DeleteWarningUtils'; import { DeleteWarningModal } from '@app/Modal/DeleteWarningModal'; +import { AboutAgentCard } from './AboutAgentCard'; export interface AgentProbeTemplatesProps { agentDetected: boolean; @@ -328,20 +323,7 @@ export const AgentProbeTemplates: React.FunctionComponent - - - - About the JMC Agent - - - - The JMC Agent allows users to dynamically inject custom JFR events into running JVMs. In order to make - use of the JMC Agent, the agent jar must be present in the same container as the target, and the target - must be started with the agent (-javaagent:/path/to/agent.jar). Once these pre-requisites are met, the - user can upload probe templates to Cryostat and insert them to the target, as well as view or remove - currently active probes. - - + diff --git a/src/app/Dashboard/Dashboard.tsx b/src/app/Dashboard/Dashboard.tsx index 8d363999d..4e237c459 100644 --- a/src/app/Dashboard/Dashboard.tsx +++ b/src/app/Dashboard/Dashboard.tsx @@ -41,7 +41,7 @@ import { TargetView } from '@app/TargetView/TargetView'; export interface DashboardProps {} export const Dashboard: React.FunctionComponent = (props) => { - return ; + return ; }; export default Dashboard; diff --git a/src/app/Login/Login.tsx b/src/app/Login/Login.tsx index af84d53a5..ac6c75147 100644 --- a/src/app/Login/Login.tsx +++ b/src/app/Login/Login.tsx @@ -38,7 +38,7 @@ import * as React from 'react'; import { ServiceContext } from '@app/Shared/Services/Services'; import { NotificationsContext } from '../Notifications/Notifications'; -import { Card, CardBody, CardFooter, CardHeader, PageSection, Text, Title } from '@patternfly/react-core'; +import { Card, CardBody, CardFooter, CardTitle, PageSection, Text } from '@patternfly/react-core'; import { BasicAuthDescriptionText, BasicAuthForm } from './BasicAuthForm'; import { OpenShiftAuthDescriptionText, OpenShiftPlaceholderAuthForm } from './OpenShiftPlaceholderAuthForm'; import { NoopAuthForm } from './NoopAuthForm'; @@ -100,11 +100,7 @@ export const Login: React.FunctionComponent = (props) => { return ( - - - Login - - + Login {loginForm} {descriptionText} diff --git a/src/app/Recordings/Recordings.tsx b/src/app/Recordings/Recordings.tsx index 59b7b0ea3..89bff0d20 100644 --- a/src/app/Recordings/Recordings.tsx +++ b/src/app/Recordings/Recordings.tsx @@ -38,7 +38,7 @@ import * as React from 'react'; import { ServiceContext } from '@app/Shared/Services/Services'; import { TargetView } from '@app/TargetView/TargetView'; -import { Card, CardBody, CardHeader, Tab, Tabs, Text, TextVariants } from '@patternfly/react-core'; +import { Card, CardBody, CardTitle, Tab, Tabs } from '@patternfly/react-core'; import { ActiveRecordingsTable } from './ActiveRecordingsTable'; import { ArchivedRecordingsTable } from './ArchivedRecordingsTable'; import { useSubscriptions } from '@app/utils/useSubscriptions'; @@ -67,9 +67,7 @@ export const Recordings: React.FunctionComponent = (props) => { ) : ( <> - - Active Recordings - + Active Recordings ); diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index cde6110ba..def9f3248 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -41,8 +41,6 @@ import { Button, Card, CardBody, - CardHeader, - CardHeaderMain, Form, FormGroup, FormSelect, @@ -533,11 +531,6 @@ const Comp = () => { - - - Match Expression Evaluator - - = (props) => { - - - About Automated Rules - - + About Automated Rules Automated Rules define a dynamic set of Target JVMs to connect to and start{' '} Active Recordings using a specific Event Template{' '} diff --git a/src/app/Settings/Settings.tsx b/src/app/Settings/Settings.tsx index 9559b533e..0ef073735 100644 --- a/src/app/Settings/Settings.tsx +++ b/src/app/Settings/Settings.tsx @@ -62,7 +62,7 @@ export const Settings: React.FunctionComponent = (props) => { {settings.map((s) => ( - {s.title} + {s.title} {s.description} {s.element} diff --git a/src/app/Shared/MatchExpressionEvaluator.tsx b/src/app/Shared/MatchExpressionEvaluator.tsx index 93b54b02a..e5c8867fc 100644 --- a/src/app/Shared/MatchExpressionEvaluator.tsx +++ b/src/app/Shared/MatchExpressionEvaluator.tsx @@ -60,6 +60,7 @@ import { InfoCircleIcon, WarningTriangleIcon, } from '@patternfly/react-icons'; +import { SerializedTarget } from '@app/Shared/SerializedTarget'; export interface MatchExpressionEvaluatorProps { inlineHint?: boolean; @@ -155,7 +156,7 @@ export const MatchExpressionEvaluator: React.FunctionComponent - + @@ -189,13 +190,7 @@ export const MatchExpressionEvaluator: React.FunctionComponent )} - {!!target?.alias && !!target?.connectUrl ? ( - - {JSON.stringify(target, null, 2)} - - ) : ( - - )} + diff --git a/src/app/Shared/SerializedTarget.tsx b/src/app/Shared/SerializedTarget.tsx new file mode 100644 index 000000000..beb38d4a8 --- /dev/null +++ b/src/app/Shared/SerializedTarget.tsx @@ -0,0 +1,60 @@ +/* + * Copyright The Cryostat Authors + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or data + * (collectively the "Software"), free of charge and under any and all copyright + * rights in the Software, and any and all patent rights owned or freely + * licensable by each licensor hereunder covering either (i) the unmodified + * Software as contributed to or provided by such licensor, or (ii) the Larger + * Works (as defined below), to deal in both + * + * (a) the Software, and + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software (each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * The above copyright notice and either this complete permission notice or at + * a minimum a reference to the UPL must be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import * as React from 'react'; +import { CodeBlock, CodeBlockCode } from '@patternfly/react-core'; +import { Target } from '@app/Shared/Services/Target.service'; +import { NoTargetSelected } from '@app/TargetView/NoTargetSelected'; + +export interface SerializedTargetProps { + target?: Target; + indentLevel?: number; +} + +export const SerializedTarget: React.FunctionComponent = (props) => { + return ( + <> + {!props.target ? ( + + ) : ( + + {JSON.stringify(props.target, null, props.indentLevel || 2)} + + )} + + ); +}; diff --git a/src/app/Shared/Services/Target.service.tsx b/src/app/Shared/Services/Target.service.tsx index a6abd8c30..d3f6a00ee 100644 --- a/src/app/Shared/Services/Target.service.tsx +++ b/src/app/Shared/Services/Target.service.tsx @@ -58,6 +58,7 @@ export const indexOfTarget = (arr: Target[], target: Target): number => { }; export interface Target { + jvmId?: string; // present in responses, but we do not need to provide it in requests connectUrl: string; alias: string; labels?: {}; diff --git a/src/app/TargetSelect/TargetSelect.tsx b/src/app/TargetSelect/TargetSelect.tsx index 8f35e9504..006dad612 100644 --- a/src/app/TargetSelect/TargetSelect.tsx +++ b/src/app/TargetSelect/TargetSelect.tsx @@ -45,13 +45,12 @@ import { Card, CardActions, CardBody, + CardExpandableContent, CardHeader, - CardHeaderMain, + CardTitle, Select, SelectOption, SelectVariant, - Text, - TextVariants, } from '@patternfly/react-core'; import { ContainerNodeIcon, PlusCircleIcon, TrashIcon } from '@patternfly/react-icons'; import { of } from 'rxjs'; @@ -60,19 +59,26 @@ import { CreateTargetModal } from './CreateTargetModal'; import { DeleteWarningType } from '@app/Modal/DeleteWarningUtils'; import { DeleteWarningModal } from '@app/Modal/DeleteWarningModal'; import { getFromLocalStorage, removeFromLocalStorage, saveToLocalStorage } from '@app/utils/LocalStorage'; +import { SerializedTarget } from '@app/Shared/SerializedTarget'; +import { NoTargetSelected } from '@app/TargetView/NoTargetSelected'; export const CUSTOM_TARGETS_REALM = 'Custom Targets'; -export interface TargetSelectProps {} +export interface TargetSelectProps { + // display a simple, non-expandable component. set this if the view elsewhere + // contains a or other repeated components + simple?: boolean; +} export const TargetSelect: React.FunctionComponent = (props) => { const notifications = React.useContext(NotificationsContext); const context = React.useContext(ServiceContext); const addSubscription = useSubscriptions(); + const [isExpanded, setExpanded] = React.useState(false); const [selected, setSelected] = React.useState(NO_TARGET); const [targets, setTargets] = React.useState([] as Target[]); - const [expanded, setExpanded] = React.useState(false); + const [isDropdownOpen, setDropdownOpen] = React.useState(false); const [isLoading, setLoading] = React.useState(false); const [isModalOpen, setModalOpen] = React.useState(false); const [warningModalOpen, setWarningModalOpen] = React.useState(false); @@ -91,6 +97,10 @@ export const TargetSelect: React.FunctionComponent = (props) removeCachedTargetSelection(); }, [context.target, removeCachedTargetSelection]); + const onExpand = React.useCallback(() => { + setExpanded((v) => !v); + }, [setExpanded]); + const onSelect = React.useCallback( // ATTENTION: do not add onSelect as deps for effect hook as it updates with selected states (evt, selection, isPlaceholder) => { @@ -105,9 +115,9 @@ export const TargetSelect: React.FunctionComponent = (props) }); } } - setExpanded(false); + setDropdownOpen(false); }, - [context.target, notifications, setExpanded, setCachedTargetSelection, resetTargetSelection, selected] + [context.target, notifications, setDropdownOpen, setCachedTargetSelection, resetTargetSelection, selected] ); const selectTargetFromCache = React.useCallback( @@ -249,13 +259,28 @@ export const TargetSelect: React.FunctionComponent = (props) [targets] ); + const cardHeaderProps = React.useMemo( + () => + props.simple + ? {} + : { + onExpand: onExpand, + isToggleRightAligned: true, + toggleButtonProps: { + id: 'target-select-expand-button', + 'aria-label': 'Details', + 'aria-labelledby': 'expandable-card-title target-select-expand-button', + 'aria-expanded': isExpanded, + }, + }, + [props.simple, onExpand, isExpanded] + ); + return ( <> - - - - Target JVM - + + + Target JVM +
+ +