Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jmc-agent): add JMC Agent probes UI #558

Merged
merged 46 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
850fad4
Agent Plugin Changes
Josh-Matsuoka Aug 15, 2022
aff2c4f
tmp
Josh-Matsuoka Aug 15, 2022
acd72c3
Syncing with upstream
Josh-Matsuoka Aug 15, 2022
9776154
Fix refreshing of ProbeTemplate table, add notifications
Josh-Matsuoka Oct 12, 2022
e9a53db
Adding Agent Tests
Josh-Matsuoka Oct 12, 2022
d39a8d7
Merging changes from upstream
Josh-Matsuoka Oct 13, 2022
8a5a5b3
Fix ErrorView and AuthRetry
Josh-Matsuoka Oct 13, 2022
960ef73
Merge remote-tracking branch 'upstream/main' into agent-plugin
Josh-Matsuoka Oct 19, 2022
fbfb31b
Fix Agent Probe Templates page tests, fix probe templates table, add …
Josh-Matsuoka Oct 19, 2022
cd26d77
Fixing Agent Live Probes page to work with new API implementation
Josh-Matsuoka Oct 20, 2022
85bcccc
Backing out unnecessary package.json change
Josh-Matsuoka Oct 20, 2022
6d48d48
Running prettier to fix formatting
Josh-Matsuoka Oct 20, 2022
898d6ea
Removing extraneous change
Josh-Matsuoka Oct 20, 2022
bc06912
Removing unnecessary change
Josh-Matsuoka Oct 20, 2022
a6ffd94
Fixing deletion warning and notifications
Josh-Matsuoka Oct 20, 2022
2db1ec6
Moving Agent items under Events page, adding hints to error message w…
Josh-Matsuoka Oct 20, 2022
beb17ec
Moving Agent help into agent components, adding notification for prob…
Josh-Matsuoka Oct 20, 2022
ea4abb5
Remove duplicate notification
Josh-Matsuoka Oct 20, 2022
ea970a6
Add error hint for Probetemplate component
Josh-Matsuoka Oct 21, 2022
6e3c9fd
clean up, formatting
andrewazores Oct 21, 2022
ad90878
add parameter to suppress graphical notification on error
andrewazores Oct 21, 2022
52054af
test for and only display JMC Agent card if support is detected
andrewazores Oct 21, 2022
8f083ff
fixup! test for and only display JMC Agent card if support is detected
andrewazores Oct 21, 2022
a74db43
apply prettier
andrewazores Oct 21, 2022
d991616
chore(events): clean up api calls and use separate state for 2 tables
Oct 21, 2022
8f8e1e9
chore(live-probes): clean up live probe views
Oct 22, 2022
582eb6b
feat(agent): add delete modal for active probe view
Oct 22, 2022
2048604
chore(agent): clean up probe template view
Oct 22, 2022
7e4d03f
chore(agent): continue to clean up
Oct 24, 2022
13baf3b
tests(agent): fix tests
Oct 24, 2022
1d86c1a
Suppress notifications on refreshing probes
Josh-Matsuoka Oct 24, 2022
cf62bf3
Fix notification handling for removing probes
Josh-Matsuoka Oct 24, 2022
96ca5f6
leave card visible but disable tab if Agent not detected
andrewazores Oct 24, 2022
b234ed7
Refresh probes table when receiving a removal notification, adjust te…
Josh-Matsuoka Oct 24, 2022
b41291c
correct hook dep
andrewazores Oct 24, 2022
a2cc958
don't re-query after successful deletion request, allow notification …
andrewazores Oct 24, 2022
6045b0b
skip query on notification, just clear client-side model
andrewazores Oct 24, 2022
65fb096
test correction
andrewazores Oct 24, 2022
1c58913
Actually commit tests this time
Josh-Matsuoka Oct 24, 2022
1d1c034
Fix test mocks, adjust test to check for both probes, fix dependency …
Josh-Matsuoka Oct 24, 2022
23aba8f
fix(agent): disable remove button if no probes
Oct 24, 2022
d2d00eb
fix(agent): add more tests
Oct 24, 2022
4a48bd5
chore(agent): apply prettier
Oct 24, 2022
0246a64
disable probe insertion button if agent not detected
andrewazores Oct 24, 2022
fcfcd73
tests(agent): add tests for inserting probes
Oct 25, 2022
5624cfb
chore(agent): remove unused imports
Oct 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.2",
"webpack-merge": "^5.8.0",
"yarn": "^1.22.13"
"yarn": "^1.22.18"
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
},
"dependencies": {
"@patternfly/react-core": "^4.224.1",
Expand Down
68 changes: 68 additions & 0 deletions src/app/Agent/Agent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { TargetView } from '@app/TargetView/TargetView';
import { Card, CardBody, Tab, Tabs } from '@patternfly/react-core';
import { AgentProbeTemplates } from '@app/Agent/AgentProbeTemplates';
import { AgentLiveProbes } from '@app/Agent/AgentLiveProbes';

export const Agent = () => {
const [activeTab, setActiveTab] = React.useState(0);

const handleTabSelect = (evt, idx) => {
setActiveTab(idx);
}

return (<>
<TargetView pageTitle="JMC Agent">
<Card>
<CardBody>
<Tabs activeKey={activeTab} onSelect={handleTabSelect}>
<Tab eventKey={0} title="Probe Templates">
<AgentProbeTemplates />
</Tab>
<Tab eventKey={1} title="Live Configuration">
<AgentLiveProbes />
</Tab>
</Tabs>
</CardBody>
</Card>
</TargetView>
</>);

}
200 changes: 200 additions & 0 deletions src/app/Agent/AgentLiveProbes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* 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 { ServiceContext } from '@app/Shared/Services/Services';
import { NotificationCategory } from '@app/Shared/Services/NotificationChannel.service';
import { NO_TARGET } from '@app/Shared/Services/Target.service';
import { useSubscriptions } from '@app/utils/useSubscriptions';
import { ActionGroup, Button, FileUpload, Form, FormGroup, Modal, ModalVariant, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem, TextInput } from '@patternfly/react-core';
import { PlusIcon } from '@patternfly/react-icons';
import { Table, TableBody, TableHeader, TableVariant, IAction, IRowData, IExtraData, ISortBy, SortByDirection, sortable } from '@patternfly/react-table';
import { useHistory } from 'react-router-dom';
import { concatMap, filter, first } from 'rxjs/operators';
import { LoadingView } from '@app/LoadingView/LoadingView';
import { authFailMessage, ErrorView, isAuthFail } from '@app/ErrorView/ErrorView';
import { EventProbe } from '@app/Shared/Services/Api.service';

export const AgentLiveProbes = () => {

const context = React.useContext(ServiceContext);
const history = useHistory();
tthvo marked this conversation as resolved.
Show resolved Hide resolved

const [templates, setTemplates] = React.useState([] as EventProbe[]);
const [filteredTemplates, setFilteredTemplates] = React.useState([] as EventProbe[]);
tthvo marked this conversation as resolved.
Show resolved Hide resolved
const [filterText, setFilterText] = React.useState('');
const [sortBy, setSortBy] = React.useState({} as ISortBy);
const [isLoading, setIsLoading] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState('');
const addSubscription = useSubscriptions();

const tableColumns = [
{ title: 'ID', transforms: [ sortable ] },
{ title: 'Label', transforms: [ sortable ] },
{ title: 'Class' , transforms: [ sortable ] },
{ title: 'Description' , transforms: [ sortable ] },
{ title: 'Method' , transforms: [ sortable ] }
];

React.useEffect(() => {
let filtered;
if (!filterText) {
filtered = templates;
} else {
const ft = filterText.trim().toLowerCase();
filtered = templates.filter((t: EventProbe) => t.name.toLowerCase().includes(ft) || t.description.toLowerCase().includes(ft)
|| t.clazz.toLowerCase().includes(ft) || t.methodDescriptor.toLowerCase().includes(ft) || t.methodName.toLowerCase().includes(ft));
}
const { index, direction } = sortBy;
if (typeof index === 'number') {
const keys = ['ID', 'Label', 'Description', 'Class', 'Method'];
tthvo marked this conversation as resolved.
Show resolved Hide resolved
const key = keys[index];
const sorted = filtered.sort((a, b) => (a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0));
filtered = direction === SortByDirection.asc ? sorted : sorted.reverse();
}
setFilteredTemplates([...filtered]);
}, [filterText, templates, sortBy]);
tthvo marked this conversation as resolved.
Show resolved Hide resolved

const handleTemplates = React.useCallback((templates) => {
console.log(templates);
setTemplates(templates);
setErrorMessage('');
setIsLoading(false);
}, [setTemplates, setIsLoading, setErrorMessage]);

const handleError = React.useCallback((error) => {
setErrorMessage(error.message);
setIsLoading(false);
}, [setIsLoading, setErrorMessage]);

const refreshTemplates = React.useCallback(() => {
setIsLoading(true);
addSubscription(
context.target.target()
.pipe(
filter(target => target !== NO_TARGET),
first(),
concatMap(target => context.api.getActiveProbes()),
).subscribe(value => handleTemplates(value), err => handleError(err))
);
}, [addSubscription, context, context.target, context.api, setIsLoading, handleTemplates, handleError]);

React.useEffect(() => {
addSubscription(
context.target.target().subscribe(() => {
setFilterText('');
refreshTemplates();
}));
}, [context, context.target, addSubscription, refreshTemplates]);
tthvo marked this conversation as resolved.
Show resolved Hide resolved

React.useEffect(() => {
if (!context.settings.autoRefreshEnabled()) {
return;
}
const id = window.setInterval(() => refreshTemplates(), context.settings.autoRefreshPeriod() * context.settings.autoRefreshUnits());
return () => window.clearInterval(id);
}, []);

React.useEffect(() => {
const sub = context.target.authFailure().subscribe(() => {
tthvo marked this conversation as resolved.
Show resolved Hide resolved
setErrorMessage("Auth failure");
});
return () => sub.unsubscribe();
}, [context.target]);

const displayTemplates = React.useMemo(
() => templates.map((t: EventProbe) => ([t.id, t.name , t.clazz , t.description , t.methodName + t.methodDescriptor ])),
[templates]
);

const handleModalToggle = () => {
tthvo marked this conversation as resolved.
Show resolved Hide resolved
addSubscription(
context.api.removeProbes()
.pipe(first())
.subscribe(() => {refreshTemplates();})
);
};

React.useEffect(() => {
addSubscription(
context.notificationChannel.messages(NotificationCategory.ProbeTemplateApplied)
.subscribe(v => refreshTemplates())
);
}, [addSubscription, context, context.notificationChannel, setTemplates]);


const authRetry = React.useCallback(() => {
context.target.setAuthRetry();
}, [context.target, context.target.setAuthRetry]);


if (errorMessage != '') {
return (<ErrorView
title={'Error retrieving events'}
message={errorMessage}
retry={isAuthFail(errorMessage) ? authRetry : undefined}
/>)
} else if (isLoading) {
return (<LoadingView/>)
} else {
return (<>
<Toolbar id="event-templates-toolbar">
<ToolbarContent>
<ToolbarGroup variant="filter-group">
<ToolbarItem>
<TextInput name="templateFilter" id="templateFilter" type="search" placeholder="Filter..." aria-label="Probe template filter" onChange={setFilterText}/>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<Button key="create" variant="primary" onClick={handleModalToggle}>Remove Probes</Button>
</ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
<Table aria-label="Active Events"
variant={TableVariant.compact}
cells={tableColumns}
rows={displayTemplates}
>
<TableHeader />
<TableBody />
</Table>
</>);
}

}
Loading