Skip to content

Commit 5b0d734

Browse files
sbraitschs-braitschHeiko HolzPriebe Daniel
authored
feat #1333: Enable downloading support archives from the configurati… (#1487)
* feat #1333: Enable downloading support archives from the configuration UI * apply inspectIT Ocelot code formatting * change icon for downloading support archive * resolved review notes * minor comment/message refactoring * AgentService checks if LogPreloading is enabled before trying to get the result * fix(code-quality): add accidentally deleted saveactions_settings.xml (#1333) Co-authored-by: Simon Braitsch <simon.braitsch@novatec-gmbh.de> Co-authored-by: Heiko Holz <heiko.holz@novatec-gmbh.de> Co-authored-by: Priebe Daniel <dpr@novatec-gmbh.de>
1 parent 0163d09 commit 5b0d734

File tree

13 files changed

+497
-28
lines changed

13 files changed

+497
-28
lines changed

components/inspectit-ocelot-configurationserver-ui/src/components/views/dialogs/DownloadDialogue.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ const DownloadDialogue = ({ visible, onHide, error, loading, contentValue, conte
2121
return window.URL.createObjectURL(blob);
2222
};
2323

24+
const renderError = (errorType) => {
25+
switch (errorType) {
26+
case 'log':
27+
return `${dialogueSettings.header} could not be loaded due to an unexpected error.\n
28+
Ensure that both agent-commands and log-preloading are enabled and the agent-commands URL is correct in your agent
29+
configuration.`;
30+
case 'archive':
31+
return `Downloading the Support Archive for ${contextName} failed. Make sure agent-commands are enabled and the agent-commands URL is correct in your agent configuration.`;
32+
default:
33+
return `${dialogueSettings.header} could not be loaded due to an unexpected error.`;
34+
}
35+
};
36+
2437
return (
2538
<>
2639
<Dialog
@@ -41,16 +54,7 @@ const DownloadDialogue = ({ visible, onHide, error, loading, contentValue, conte
4154
{loading ? (
4255
<ProgressBar mode="indeterminate" />
4356
) : error ? (
44-
<div>
45-
{dialogueSettings.header} could not be loaded due to an unexpected error.
46-
{contentType === 'log' ? (
47-
<span>
48-
<br />
49-
Ensure that both agent-commands and log-preloading are enabled and the agent-commands URL is correct in your agent
50-
configuration.
51-
</span>
52-
) : null}
53-
</div>
57+
renderError(contentType)
5458
) : (
5559
<SyntaxHighlighter
5660
customStyle={{ maxHeight: '50vh' }}

components/inspectit-ocelot-configurationserver-ui/src/components/views/mappings/ContentTypeMapper.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* mapping function that returns all values needed to setup the download of a file for a given content-type
3-
* explicitly mapped contentTypes are 'config' and 'log', everything else will default to a plain .txt configuration
3+
* explicitly mapped contentTypes are 'config', 'log' and 'archive', everything else will default to a plain .txt configuration
44
* @param contentType
55
* @param contextName
66
* @returns {{fileExtension: string, header: string, language: string, mimeType: string}}
@@ -22,6 +22,13 @@ export const ContentTypeMapper = (contentType, contextName) => {
2222
mimeType: 'text/plain',
2323
header: 'Logs of ' + contextName,
2424
};
25+
case 'archive':
26+
return {
27+
language: '',
28+
fileExtension: '',
29+
mimeType: '',
30+
header: 'Preparing Download of the Support Archive for ' + contextName,
31+
};
2532
default:
2633
return {
2734
language: 'plaintext',

components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusTable.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import dateformat from 'dateformat';
66
import TimeAgo from 'react-timeago';
77
import { map } from 'lodash';
88
import classnames from 'classnames';
9-
import { linkPrefix } from '../../../lib/configuration';
109
import classNames from 'classnames';
10+
import { linkPrefix } from '../../../lib/configuration';
1111

1212
const timeFormatter = (time, unit, suffix) => {
1313
if (unit === 'second') {
@@ -53,18 +53,22 @@ class AgentMappingCell extends React.Component {
5353
display: flex;
5454
align-items: stretch;
5555
}
56+
5657
.mapping-name {
5758
flex: 1;
5859
margin-right: 0.5rem;
5960
}
61+
6062
.no-mapping {
6163
color: gray;
6264
font-style: italic;
6365
}
66+
6467
.show-attributes {
6568
float: right;
6669
cursor: pointer;
6770
}
71+
6872
.attributes {
6973
margin-top: 0.5rem;
7074
border-left: 0.25rem solid #ddd;
@@ -133,6 +137,7 @@ class StatusTable extends React.Component {
133137
.this {
134138
position: relative;
135139
}
140+
136141
.this :global(.config-info-button) {
137142
width: 1.2rem;
138143
height: 1.2rem;
@@ -142,6 +147,7 @@ class StatusTable extends React.Component {
142147
background: #ddd;
143148
border-color: #ddd;
144149
}
150+
145151
.this :global(.log-button) {
146152
width: 1.2rem;
147153
height: 1.2rem;
@@ -151,6 +157,7 @@ class StatusTable extends React.Component {
151157
background: #ddd;
152158
border-color: #ddd;
153159
}
160+
154161
.this :global(.badge) {
155162
width: 1.2rem;
156163
height: 1.2rem;
@@ -216,7 +223,8 @@ class StatusTable extends React.Component {
216223
};
217224

218225
agentHealthTemplate = (rowData) => {
219-
const { health } = rowData;
226+
const { onShowDownloadDialog } = this.props;
227+
const { health, metaInformation } = rowData;
220228

221229
let healthInfo;
222230
let iconClass;
@@ -244,12 +252,30 @@ class StatusTable extends React.Component {
244252
.state {
245253
align-items: center;
246254
display: flex;
255+
position: relative;
256+
}
257+
258+
.state :global(.archive-button) {
259+
width: 1.2rem;
260+
height: 1.2rem;
261+
position: absolute;
262+
right: 0;
263+
top: 0;
264+
background: #ddd;
265+
border-color: #ddd;
247266
}
248267
`}</style>
249268
{health ? (
250269
<div className="state">
251270
<i className={classNames('pi pi-fw', iconClass)} style={{ color: iconColor }}></i>
252271
<span>{healthInfo}</span>
272+
<Button
273+
className="archive-button"
274+
icon="pi pi-cloud-download"
275+
onClick={() => onShowDownloadDialog(metaInformation.agentId, metaInformation.agentVersion, 'archive')}
276+
tooltip="Download Support Archive"
277+
tooltipOptions={{ showDelay: 500 }}
278+
/>
253279
</div>
254280
) : (
255281
'-'
@@ -305,12 +331,15 @@ class StatusTable extends React.Component {
305331
display: flex;
306332
align-items: center;
307333
}
334+
308335
.pi {
309336
margin-right: 0.5rem;
310337
}
338+
311339
.pi.live {
312340
color: #ef5350;
313341
}
342+
314343
.pi.workspace {
315344
color: #616161;
316345
}

components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusView.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import StatusTable from './StatusTable';
55
import StatusToolbar from './StatusToolbar';
66
import StatusFooterToolbar from './StatusFooterToolbar';
77
import axios from '../../../lib/axios-api';
8-
import { map, isEqual } from 'lodash';
8+
import { isEqual, map } from 'lodash';
99
import DownloadDialogue from '../dialogs/DownloadDialogue';
10+
import { downloadArchiveFromJson } from '../../../functions/export-selection.function';
1011

1112
/**
1213
* The view presenting a list of connected agents, their mapping and when they last connected to the server.
@@ -290,6 +291,9 @@ class StatusView extends React.Component {
290291
case 'log':
291292
this.fetchLog(agentId);
292293
break;
294+
case 'archive':
295+
this.downloadSupportArchive(agentId, attributes);
296+
break;
293297
default:
294298
this.setDownloadDialogShown(false);
295299
break;
@@ -298,6 +302,34 @@ class StatusView extends React.Component {
298302
);
299303
};
300304

305+
downloadSupportArchive = (agentId, agentVersion) => {
306+
this.setState(
307+
{
308+
isLoading: true,
309+
},
310+
() => {
311+
axios
312+
.get('/agent/supportArchive', {
313+
params: { 'agent-id': agentId },
314+
})
315+
.then((res) => {
316+
this.setState({
317+
isLoading: false,
318+
});
319+
downloadArchiveFromJson(res.data, agentId, agentVersion);
320+
this.setDownloadDialogShown(false);
321+
})
322+
.catch(() => {
323+
this.setState({
324+
contentValue: null,
325+
contentLoadingFailed: true,
326+
isLoading: false,
327+
});
328+
});
329+
}
330+
);
331+
};
332+
301333
fetchConfiguration = (attributes) => {
302334
const requestParams = attributes;
303335
if (!requestParams) {

components/inspectit-ocelot-configurationserver-ui/src/functions/export-selection.function.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,31 @@ function loadAndCompressSubFolderContent(subFolderPath, subFolderName, subFolder
8282
}
8383
});
8484
}
85+
86+
export function downloadArchiveFromJson(json, agentId, agentVersion) {
87+
for (let key of Object.keys(json)) {
88+
let content = json[key];
89+
let mimetype = 'text/plain;charset=utf-8';
90+
let fileExtension = '.txt';
91+
92+
switch (key) {
93+
case 'currentConfig':
94+
mimetype = 'text/x-yaml;charset=utf-8';
95+
fileExtension = '.yml';
96+
break;
97+
case 'logs':
98+
fileExtension = '.log';
99+
break;
100+
}
101+
102+
if (typeof content === 'object') {
103+
content = JSON.stringify(content, null, 2);
104+
}
105+
const file = new Blob([content], { type: mimetype });
106+
zip.file(`${key}${fileExtension}`, file);
107+
}
108+
zip
109+
.generateAsync({ type: 'blob' })
110+
.then((archive) => saveAs(archive, `${agentId}_${agentVersion}.zip`))
111+
.finally((zip = new JSZip()));
112+
}

components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentcommunication/AgentCommandManager.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.google.common.cache.CacheBuilder;
55
import com.google.common.cache.CacheLoader;
66
import com.google.common.cache.LoadingCache;
7-
import lombok.extern.java.Log;
87
import lombok.extern.slf4j.Slf4j;
98
import org.springframework.beans.factory.annotation.Autowired;
109
import org.springframework.stereotype.Service;
@@ -13,8 +12,6 @@
1312
import rocks.inspectit.ocelot.config.model.InspectitServerSettings;
1413

1514
import javax.annotation.PostConstruct;
16-
import java.time.Duration;
17-
import java.util.LinkedList;
1815
import java.util.concurrent.BlockingQueue;
1916
import java.util.concurrent.ExecutionException;
2017
import java.util.concurrent.LinkedBlockingQueue;
@@ -82,7 +79,6 @@ public void addCommand(String agentId, Command command) throws ExecutionExceptio
8279
public Command getCommand(String agentId, boolean waitForCommand) {
8380
try {
8481
BlockingQueue<Command> commandQueue = agentCommandCache.get(agentId);
85-
8682
Command command;
8783
if (waitForCommand) {
8884
AgentCommandSettings commandSettings = configuration.getAgentCommand();
@@ -92,7 +88,7 @@ public Command getCommand(String agentId, boolean waitForCommand) {
9288
command = commandQueue.poll();
9389
}
9490

95-
if (commandQueue.isEmpty()) {
91+
if (command == null) {
9692
agentCommandCache.invalidate(agentId);
9793
}
9894
return command;

components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentcommunication/handlers/impl/EnvironmentCommandHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class EnvironmentCommandHandler implements CommandHandler {
2828
* Checks if the given {@link Command} is an instance of {@link EnvironmentCommand}.
2929
*
3030
* @param command The command which should be checked.
31+
*
3132
* @return True if the given command is an instance of {@link EnvironmentCommand}.
3233
*/
3334
@Override
@@ -39,6 +40,7 @@ public boolean canHandle(Command command) {
3940
* Checks if the given {@link CommandResponse} is an instance of {@link EnvironmentCommand.Response}.
4041
*
4142
* @param response The response which should be checked.
43+
*
4244
* @return True if the given response is an instance of {@link EnvironmentCommand.Response}.
4345
*/
4446
@Override
@@ -52,6 +54,7 @@ public boolean canHandle(CommandResponse response) {
5254
*
5355
* @param agentId The id of the agent the command is meant for.
5456
* @param command The command to be Executed.
57+
*
5558
* @return An instance of {@link DeferredResult} with a set timeout value and a set timeout function.
5659
*/
5760
@Override
@@ -70,7 +73,7 @@ public DeferredResult<ResponseEntity<?>> prepareResponse(String agentId, Command
7073
/**
7174
* Takes an instance of {@link CommandResponse} as well as an instance of {@link DeferredResult}.
7275
* Sets the {@link ResponseEntity} of the {@link DeferredResult} according to the
73-
* Environment Information received from the respective Agent."
76+
* Environment Information received from the respective Agent.
7477
*
7578
* @param response The {@link CommandResponse} to be handled.
7679
* @param result The {@link DeferredResult} the response should be written in.

components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/agent/AgentController.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.http.HttpStatus;
99
import org.springframework.http.ResponseEntity;
1010
import org.springframework.web.bind.annotation.*;
11+
import org.springframework.web.context.request.async.DeferredResult;
1112
import rocks.inspectit.ocelot.agentcommunication.AgentCallbackManager;
1213
import rocks.inspectit.ocelot.agentcommunication.AgentCommandManager;
1314
import rocks.inspectit.ocelot.agentconfiguration.AgentConfiguration;
@@ -20,6 +21,7 @@
2021

2122
import java.util.Map;
2223
import java.util.UUID;
24+
import java.util.concurrent.ExecutionException;
2325

2426
/**
2527
* The rest controller providing the interface used by the agent for configuration fetching.
@@ -40,6 +42,9 @@ public class AgentController extends AbstractBaseController {
4042
@Autowired
4143
private AgentCallbackManager agentCallbackManager;
4244

45+
@Autowired
46+
private AgentService agentService;
47+
4348
@ExceptionHandler
4449
public void e(Exception e) {
4550
e.printStackTrace();
@@ -97,4 +102,17 @@ public ResponseEntity<Command> fetchCommand(@RequestHeader Map<String, String> h
97102
return ResponseEntity.ok().body(nextCommand);
98103
}
99104
}
105+
106+
/**
107+
* Returns the data for building the downloadable support archive for the agent with the given name in the frontend.
108+
*
109+
* @param attributes the attributes of the agents used to select the appropriate data.
110+
*
111+
* @return The data used in the support archive.
112+
*/
113+
@ApiOperation(value = "Fetch an Agents Data for Downloading a Support Archive", notes = "Bundles useful information for debugging issues raised in support tickets.")
114+
@GetMapping(value = "agent/supportArchive", produces = "application/json")
115+
public DeferredResult<ResponseEntity<?>> fetchSupportArchive(@ApiParam("The agent attributes used to retrieve the correct data") @RequestParam Map<String, String> attributes) throws ExecutionException {
116+
return agentService.buildSupportArchive(attributes, configManager);
117+
}
100118
}

0 commit comments

Comments
 (0)