OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:convert'; | |
6 import 'dart:io'; | |
7 import 'dart:math'; | |
8 | |
9 import 'package:analysis_server/plugin/protocol/protocol.dart' hide Element; | |
10 import 'package:analysis_server/src/analysis_server.dart'; | |
11 import 'package:analysis_server/src/domain_completion.dart'; | |
12 import 'package:analysis_server/src/domain_diagnostic.dart'; | |
13 import 'package:analysis_server/src/domain_execution.dart'; | |
14 import 'package:analysis_server/src/services/completion/completion_performance.d art'; | |
15 import 'package:analysis_server/src/socket_server.dart'; | |
16 import 'package:analyzer/exception/exception.dart'; | |
17 import 'package:analyzer/file_system/file_system.dart'; | |
18 import 'package:analyzer/source/error_processor.dart'; | |
19 import 'package:analyzer/source/sdk_ext.dart'; | |
20 import 'package:analyzer/src/context/source.dart'; | |
21 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
22 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
23 import 'package:analyzer/src/generated/engine.dart'; | |
24 import 'package:analyzer/src/generated/sdk.dart'; | |
25 import 'package:analyzer/src/generated/source.dart'; | |
26 import 'package:analyzer/src/generated/utilities_general.dart'; | |
27 import 'package:analyzer/src/services/lint.dart'; | |
28 import 'package:analyzer/task/model.dart'; | |
29 import 'package:plugin/plugin.dart'; | |
30 | |
31 /** | |
32 * A function that can be used to generate HTML output into the given [buffer]. | |
33 * The HTML that is generated must be valid (special characters must already be | |
34 * encoded). | |
35 */ | |
36 typedef void HtmlGenerator(StringBuffer buffer); | |
37 | |
38 /** | |
39 * Instances of the class [GetHandler2] handle GET requests. | |
40 */ | |
41 class GetHandler2 { | |
42 /** | |
43 * The path used to request overall performance information. | |
44 */ | |
45 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis'; | |
46 | |
47 /** | |
48 * The path used to request code completion information. | |
49 */ | |
50 static const String COMPLETION_PATH = '/completion'; | |
51 | |
52 /** | |
53 * The path used to request communication performance information. | |
54 */ | |
55 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication'; | |
56 | |
57 /** | |
58 * The path used to request information about a specific context. | |
59 */ | |
60 static const String CONTEXT_PATH = '/context'; | |
61 | |
62 /** | |
63 * The path used to request an overlay contents. | |
64 */ | |
65 static const String OVERLAY_PATH = '/overlay'; | |
66 | |
67 /** | |
68 * The path used to request overlays information. | |
69 */ | |
70 static const String OVERLAYS_PATH = '/overlays'; | |
71 | |
72 /** | |
73 * The path used to request the status of the analysis server as a whole. | |
74 */ | |
75 static const String STATUS_PATH = '/status'; | |
76 | |
77 /** | |
78 * Query parameter used to represent the context to search for. | |
79 */ | |
80 static const String CONTEXT_QUERY_PARAM = 'context'; | |
81 | |
82 /** | |
83 * Query parameter used to represent the path of an overlayed file. | |
84 */ | |
85 static const String PATH_PARAM = 'path'; | |
86 | |
87 static final ContentType _htmlContent = | |
88 new ContentType("text", "html", charset: "utf-8"); | |
89 | |
90 /** | |
91 * The socket server whose status is to be reported on. | |
92 */ | |
93 SocketServer _server; | |
94 | |
95 /** | |
96 * Buffer containing strings printed by the analysis server. | |
97 */ | |
98 List<String> _printBuffer; | |
99 | |
100 /** | |
101 * Contents of overlay files. | |
102 */ | |
103 final Map<String, String> _overlayContents = <String, String>{}; | |
104 | |
105 /** | |
106 * Handler for diagnostics requests. | |
107 */ | |
108 DiagnosticDomainHandler _diagnosticHandler; | |
109 | |
110 /** | |
111 * Initialize a newly created handler for GET requests. | |
112 */ | |
113 GetHandler2(this._server, this._printBuffer); | |
114 | |
115 DiagnosticDomainHandler get diagnosticHandler { | |
116 if (_diagnosticHandler == null) { | |
117 _diagnosticHandler = new DiagnosticDomainHandler(_server.analysisServer); | |
118 } | |
119 return _diagnosticHandler; | |
120 } | |
121 | |
122 /** | |
123 * Return the active [CompletionDomainHandler] | |
124 * or `null` if either analysis server is not running | |
125 * or there is no completion domain handler. | |
126 */ | |
127 CompletionDomainHandler get _completionDomainHandler { | |
128 AnalysisServer analysisServer = _server.analysisServer; | |
129 if (analysisServer == null) { | |
130 return null; | |
131 } | |
132 return analysisServer.handlers | |
133 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null); | |
134 } | |
135 | |
136 /** | |
137 * Handle a GET request received by the HTTP server. | |
138 */ | |
139 void handleGetRequest(HttpRequest request) { | |
140 String path = request.uri.path; | |
141 if (path == '/' || path == STATUS_PATH) { | |
142 _returnServerStatus(request); | |
143 } else if (path == ANALYSIS_PERFORMANCE_PATH) { | |
144 _returnAnalysisPerformance(request); | |
145 } else if (path == COMPLETION_PATH) { | |
146 _returnCompletionInfo(request); | |
147 } else if (path == COMMUNICATION_PERFORMANCE_PATH) { | |
148 _returnCommunicationPerformance(request); | |
149 } else if (path == CONTEXT_PATH) { | |
150 _returnContextInfo(request); | |
151 } else if (path == OVERLAY_PATH) { | |
152 _returnOverlayContents(request); | |
153 } else if (path == OVERLAYS_PATH) { | |
154 _returnOverlaysInfo(request); | |
155 } else { | |
156 _returnUnknownRequest(request); | |
157 } | |
158 } | |
159 | |
160 /** | |
161 * Return the folder being managed by the given [analysisServer] that matches | |
162 * the given [contextFilter], or `null` if there is none. | |
163 */ | |
164 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) { | |
165 return analysisServer.driverMap.keys.firstWhere( | |
166 (Folder folder) => folder.path == contextFilter, | |
167 orElse: () => null); | |
168 } | |
169 | |
170 /** | |
171 * Return `true` if the given analysis [context] has at least one entry with | |
172 * an exception. | |
173 */ | |
174 bool _hasException(AnalysisDriver context) { | |
175 // if (context == null) { | |
176 // return false; | |
177 // } | |
178 // MapIterator<AnalysisTarget, CacheEntry> iterator = | |
179 // context.analysisCache.iterator(); | |
180 // while (iterator.moveNext()) { | |
181 // CacheEntry entry = iterator.value; | |
182 // if (entry == null || entry.exception != null) { | |
183 // return true; | |
184 // } | |
185 // } | |
186 // TODO(scheglov) | |
187 return false; | |
188 } | |
189 | |
190 /** | |
191 * Return a response displaying overall performance information. | |
192 */ | |
193 void _returnAnalysisPerformance(HttpRequest request) { | |
194 AnalysisServer analysisServer = _server.analysisServer; | |
195 if (analysisServer == null) { | |
196 return _returnFailure(request, 'Analysis server is not running'); | |
197 } | |
198 _writeResponse(request, (StringBuffer buffer) { | |
199 _writePage(buffer, 'Analysis Server - Analysis Performance', [], | |
200 (StringBuffer buffer) { | |
201 buffer.write('<h3>Analysis Performance</h3>'); | |
202 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
203 // | |
204 // Write performance tags. | |
205 // | |
206 buffer.write('<p><b>Performance tag data</b></p>'); | |
207 buffer.write( | |
208 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">'); | |
209 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'], | |
210 header: true); | |
211 // prepare sorted tags | |
212 List<PerformanceTag> tags = PerformanceTag.all.toList(); | |
213 tags.remove(ServerPerformanceStatistics.idle); | |
214 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); | |
215 // prepare total time | |
216 int totalTagTime = 0; | |
217 tags.forEach((PerformanceTag tag) { | |
218 totalTagTime += tag.elapsedMs; | |
219 }); | |
220 // write rows | |
221 void writeRow(PerformanceTag tag) { | |
222 double percent = (tag.elapsedMs * 100) / totalTagTime; | |
223 String percentStr = '${percent.toStringAsFixed(2)}%'; | |
224 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label], | |
225 classes: ["right", "right", null]); | |
226 } | |
227 | |
228 tags.forEach(writeRow); | |
229 buffer.write('</table>'); | |
230 }, (StringBuffer buffer) { | |
231 // | |
232 // Write task model timing information. | |
233 // | |
234 buffer.write('<p><b>Task performance data</b></p>'); | |
235 buffer.write( | |
236 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">'); | |
237 _writeRow( | |
238 buffer, | |
239 [ | |
240 'Task Name', | |
241 'Count', | |
242 'Total Time (in ms)', | |
243 'Average Time (in ms)' | |
244 ], | |
245 header: true); | |
246 | |
247 Map<Type, int> countMap = AnalysisTask.countMap; | |
248 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap; | |
249 List<Type> taskClasses = stopwatchMap.keys.toList(); | |
250 taskClasses.sort((Type first, Type second) => | |
251 first.toString().compareTo(second.toString())); | |
252 int totalTaskTime = 0; | |
253 taskClasses.forEach((Type taskClass) { | |
254 int count = countMap[taskClass]; | |
255 if (count == null) { | |
256 count = 0; | |
257 } | |
258 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds; | |
259 totalTaskTime += taskTime; | |
260 _writeRow(buffer, [ | |
261 taskClass.toString(), | |
262 count, | |
263 taskTime, | |
264 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3) | |
265 ], classes: [ | |
266 null, | |
267 "right", | |
268 "right", | |
269 "right" | |
270 ]); | |
271 }); | |
272 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'], | |
273 classes: [null, "right", "right", "right"]); | |
274 buffer.write('</table>'); | |
275 }); | |
276 }); | |
277 }); | |
278 } | |
279 | |
280 /** | |
281 * Return a response displaying overall performance information. | |
282 */ | |
283 void _returnCommunicationPerformance(HttpRequest request) { | |
284 AnalysisServer analysisServer = _server.analysisServer; | |
285 if (analysisServer == null) { | |
286 return _returnFailure(request, 'Analysis server is not running'); | |
287 } | |
288 _writeResponse(request, (StringBuffer buffer) { | |
289 _writePage(buffer, 'Analysis Server - Communication Performance', [], | |
290 (StringBuffer buffer) { | |
291 buffer.write('<h3>Communication Performance</h3>'); | |
292 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
293 ServerPerformance perf = analysisServer.performanceDuringStartup; | |
294 int requestCount = perf.requestCount; | |
295 num averageLatency = requestCount > 0 | |
296 ? (perf.requestLatency / requestCount).round() | |
297 : 0; | |
298 int maximumLatency = perf.maxLatency; | |
299 num slowRequestPercent = requestCount > 0 | |
300 ? (perf.slowRequestCount * 100 / requestCount).round() | |
301 : 0; | |
302 buffer.write('<h4>Startup</h4>'); | |
303 buffer.write('<table>'); | |
304 _writeRow(buffer, [requestCount, 'requests'], | |
305 classes: ["right", null]); | |
306 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
307 classes: ["right", null]); | |
308 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
309 classes: ["right", null]); | |
310 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
311 classes: ["right", null]); | |
312 if (analysisServer.performanceAfterStartup != null) { | |
313 int startupTime = analysisServer.performanceAfterStartup.startTime - | |
314 perf.startTime; | |
315 _writeRow( | |
316 buffer, [startupTime, 'ms for initial analysis to complete']); | |
317 } | |
318 buffer.write('</table>'); | |
319 }, (StringBuffer buffer) { | |
320 ServerPerformance perf = analysisServer.performanceAfterStartup; | |
321 if (perf == null) { | |
322 return; | |
323 } | |
324 int requestCount = perf.requestCount; | |
325 num averageLatency = requestCount > 0 | |
326 ? (perf.requestLatency * 10 / requestCount).round() / 10 | |
327 : 0; | |
328 int maximumLatency = perf.maxLatency; | |
329 num slowRequestPercent = requestCount > 0 | |
330 ? (perf.slowRequestCount * 100 / requestCount).round() | |
331 : 0; | |
332 buffer.write('<h4>Current</h4>'); | |
333 buffer.write('<table>'); | |
334 _writeRow(buffer, [requestCount, 'requests'], | |
335 classes: ["right", null]); | |
336 _writeRow(buffer, [averageLatency, 'ms average latency'], | |
337 classes: ["right", null]); | |
338 _writeRow(buffer, [maximumLatency, 'ms maximum latency'], | |
339 classes: ["right", null]); | |
340 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'], | |
341 classes: ["right", null]); | |
342 buffer.write('</table>'); | |
343 }); | |
344 }); | |
345 }); | |
346 } | |
347 | |
348 /** | |
349 * Return a response displaying code completion information. | |
350 */ | |
351 void _returnCompletionInfo(HttpRequest request) { | |
352 String value = request.requestedUri.queryParameters['index']; | |
353 int index = value != null ? int.parse(value, onError: (_) => 0) : 0; | |
354 _writeResponse(request, (StringBuffer buffer) { | |
355 _writePage(buffer, 'Analysis Server - Completion Stats', [], | |
356 (StringBuffer buffer) { | |
357 _writeCompletionPerformanceDetail(buffer, index); | |
358 _writeCompletionPerformanceList(buffer); | |
359 }); | |
360 }); | |
361 } | |
362 | |
363 /** | |
364 * Return a response containing information about a single source file in the | |
365 * cache. | |
366 */ | |
367 void _returnContextInfo(HttpRequest request) { | |
368 AnalysisServer analysisServer = _server.analysisServer; | |
369 if (analysisServer == null) { | |
370 return _returnFailure(request, 'Analysis server not running'); | |
371 } | |
372 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM]; | |
373 if (contextFilter == null) { | |
374 return _returnFailure( | |
375 request, 'Query parameter $CONTEXT_QUERY_PARAM required'); | |
376 } | |
377 AnalysisDriver driver = null; | |
378 Folder folder = _findFolder(analysisServer, contextFilter); | |
379 if (folder == null) { | |
380 return _returnFailure(request, 'Invalid context: $contextFilter'); | |
381 } else { | |
382 driver = analysisServer.driverMap[folder]; | |
383 } | |
384 | |
385 // List<String> priorityNames = <String>[]; | |
Paul Berry
2016/11/27 23:16:17
Can you add a TODO comment indicating what you int
scheglov
2016/11/28 16:28:43
Done.
| |
386 List<String> addedFiles = driver.addedFiles.toList(); | |
387 List<String> implicitFiles = | |
388 driver.knownFiles.difference(driver.addedFiles).toList(); | |
389 addedFiles.sort(); | |
390 implicitFiles.sort(); | |
391 | |
392 // _overlayContents.clear(); | |
Paul Berry
2016/11/27 23:16:17
Same here
scheglov
2016/11/28 16:28:43
Done.
| |
393 // context.visitContentCache((String fullName, int stamp, String contents) { | |
394 // _overlayContents[fullName] = contents; | |
395 // }); | |
396 | |
397 void _writeFiles(StringBuffer buffer, String title, List<String> files) { | |
398 buffer.write('<h3>$title</h3>'); | |
399 if (files == null || files.isEmpty) { | |
400 buffer.write('<p>None</p>'); | |
401 } else { | |
402 buffer.write('<p><table style="width: 100%">'); | |
403 for (String file in files) { | |
404 buffer.write('<tr><td>'); | |
405 buffer.write(file); | |
406 buffer.write('</td><td>'); | |
407 if (_overlayContents.containsKey(files)) { | |
408 buffer.write(makeLink(OVERLAY_PATH, {PATH_PARAM: file}, 'overlay')); | |
409 } | |
410 buffer.write('</td></tr>'); | |
411 } | |
412 buffer.write('</table></p>'); | |
413 } | |
414 } | |
415 | |
416 void writeOptions(StringBuffer buffer, AnalysisOptionsImpl options, | |
417 {void writeAdditionalOptions(StringBuffer buffer)}) { | |
418 if (options == null) { | |
419 buffer.write('<p>No option information available.</p>'); | |
420 return; | |
421 } | |
422 buffer.write('<p>'); | |
423 _writeOption( | |
424 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies); | |
425 _writeOption( | |
426 buffer, 'Enable strict call checks', options.enableStrictCallChecks); | |
427 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins); | |
428 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint); | |
429 _writeOption(buffer, 'Generate errors in implicit files', | |
430 options.generateImplicitErrors); | |
431 _writeOption( | |
432 buffer, 'Generate errors in SDK files', options.generateSdkErrors); | |
433 _writeOption(buffer, 'Generate hints', options.hint); | |
434 _writeOption(buffer, 'Incremental resolution', options.incremental); | |
435 _writeOption(buffer, 'Incremental resolution with API changes', | |
436 options.incrementalApi); | |
437 _writeOption(buffer, 'Preserve comments', options.preserveComments); | |
438 _writeOption(buffer, 'Strong mode', options.strongMode); | |
439 _writeOption(buffer, 'Strong mode hints', options.strongModeHints); | |
440 if (writeAdditionalOptions != null) { | |
441 writeAdditionalOptions(buffer); | |
442 } | |
443 buffer.write('</p>'); | |
444 } | |
445 | |
446 _writeResponse(request, (StringBuffer buffer) { | |
447 _writePage( | |
448 buffer, 'Analysis Server - Context', ['Context: $contextFilter'], | |
449 (StringBuffer buffer) { | |
450 buffer.write('<h3>Configuration</h3>'); | |
451 | |
452 _writeColumns(buffer, <HtmlGenerator>[ | |
453 (StringBuffer buffer) { | |
454 buffer.write('<p><b>Context Options</b></p>'); | |
455 writeOptions(buffer, driver.analysisOptions); | |
456 }, | |
457 (StringBuffer buffer) { | |
458 buffer.write('<p><b>SDK Context Options</b></p>'); | |
459 DartSdk sdk = driver?.sourceFactory?.dartSdk; | |
460 writeOptions(buffer, sdk?.context?.analysisOptions, | |
461 writeAdditionalOptions: (StringBuffer buffer) { | |
462 if (sdk is FolderBasedDartSdk) { | |
463 _writeOption(buffer, 'Use summaries', sdk.useSummary); | |
464 } | |
465 }); | |
466 }, | |
467 (StringBuffer buffer) { | |
468 List<Linter> lints = driver.analysisOptions.lintRules; | |
469 buffer.write('<p><b>Lints</b></p>'); | |
470 if (lints.isEmpty) { | |
471 buffer.write('<p>none</p>'); | |
472 } else { | |
473 for (Linter lint in lints) { | |
474 buffer.write('<p>'); | |
475 buffer.write(lint.runtimeType); | |
476 buffer.write('</p>'); | |
477 } | |
478 } | |
479 | |
480 List<ErrorProcessor> errorProcessors = | |
481 driver.analysisOptions.errorProcessors; | |
482 int processorCount = errorProcessors?.length ?? 0; | |
483 buffer | |
484 .write('<p><b>Error Processor count</b>: $processorCount</p>'); | |
485 } | |
486 ]); | |
487 | |
488 SourceFactory sourceFactory = driver.sourceFactory; | |
489 if (sourceFactory is SourceFactoryImpl) { | |
490 buffer.write('<h3>Resolvers</h3>'); | |
491 for (UriResolver resolver in sourceFactory.resolvers) { | |
492 buffer.write('<p>'); | |
493 buffer.write(resolver.runtimeType); | |
494 if (resolver is DartUriResolver) { | |
495 DartSdk sdk = resolver.dartSdk; | |
496 buffer.write(' (sdk = '); | |
497 buffer.write(sdk.runtimeType); | |
498 if (sdk is FolderBasedDartSdk) { | |
499 buffer.write(' (path = '); | |
500 buffer.write(sdk.directory.path); | |
501 buffer.write(')'); | |
502 } else if (sdk is EmbedderSdk) { | |
503 buffer.write(' (map = '); | |
504 _writeMapOfStringToString(buffer, sdk.urlMappings); | |
505 buffer.write(')'); | |
506 } | |
507 buffer.write(')'); | |
508 } else if (resolver is SdkExtUriResolver) { | |
509 buffer.write(' (map = '); | |
510 _writeMapOfStringToString(buffer, resolver.urlMappings); | |
511 buffer.write(')'); | |
512 } | |
513 buffer.write('</p>'); | |
514 } | |
515 } | |
516 | |
517 // _writeFiles( | |
Paul Berry
2016/11/27 23:16:17
And here
scheglov
2016/11/28 16:28:43
Done.
| |
518 // buffer, 'Priority Files (${priorityNames.length})', priorityNames) ; | |
519 _writeFiles(buffer, 'Added Files (${addedFiles.length})', addedFiles); | |
520 _writeFiles( | |
521 buffer, | |
522 'Implicitly Analyzed Files (${implicitFiles.length})', | |
523 implicitFiles); | |
524 | |
525 // buffer.write('<h3>Exceptions</h3>'); | |
Paul Berry
2016/11/27 23:16:17
And here
scheglov
2016/11/28 16:28:43
Done.
| |
526 // if (exceptions.isEmpty) { | |
527 // buffer.write('<p>none</p>'); | |
528 // } else { | |
529 // exceptions.forEach((CaughtException exception) { | |
530 // _writeException(buffer, exception); | |
531 // }); | |
532 // } | |
533 }); | |
534 }); | |
535 } | |
536 | |
537 void _returnFailure(HttpRequest request, String message) { | |
538 _writeResponse(request, (StringBuffer buffer) { | |
539 _writePage(buffer, 'Analysis Server - Failure', [], | |
540 (StringBuffer buffer) { | |
541 buffer.write(HTML_ESCAPE.convert(message)); | |
542 }); | |
543 }); | |
544 } | |
545 | |
546 void _returnOverlayContents(HttpRequest request) { | |
547 String path = request.requestedUri.queryParameters[PATH_PARAM]; | |
548 if (path == null) { | |
549 return _returnFailure(request, 'Query parameter $PATH_PARAM required'); | |
550 } | |
551 String contents = _overlayContents[path]; | |
552 | |
553 _writeResponse(request, (StringBuffer buffer) { | |
554 _writePage(buffer, 'Analysis Server - Overlay', [], | |
555 (StringBuffer buffer) { | |
556 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>'); | |
557 }); | |
558 }); | |
559 } | |
560 | |
561 /** | |
562 * Return a response displaying overlays information. | |
563 */ | |
564 void _returnOverlaysInfo(HttpRequest request) { | |
565 AnalysisServer analysisServer = _server.analysisServer; | |
566 if (analysisServer == null) { | |
567 return _returnFailure(request, 'Analysis server is not running'); | |
568 } | |
569 | |
570 _writeResponse(request, (StringBuffer buffer) { | |
571 _writePage(buffer, 'Analysis Server - Overlays', [], | |
572 (StringBuffer buffer) { | |
573 buffer.write('<table border="1">'); | |
574 _overlayContents.clear(); | |
575 ContentCache overlayState = analysisServer.overlayState; | |
576 overlayState.accept((String fullName, int stamp, String contents) { | |
577 buffer.write('<tr>'); | |
578 String link = | |
579 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName); | |
580 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp); | |
581 _writeRow(buffer, [link, time]); | |
582 _overlayContents[fullName] = contents; | |
583 }); | |
584 int count = _overlayContents.length; | |
585 buffer.write('<tr><td colspan="2">Total: $count entries.</td></tr>'); | |
586 buffer.write('</table>'); | |
587 }); | |
588 }); | |
589 } | |
590 | |
591 /** | |
592 * Return a response indicating the status of the analysis server. | |
593 */ | |
594 void _returnServerStatus(HttpRequest request) { | |
595 _writeResponse(request, (StringBuffer buffer) { | |
596 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) { | |
597 if (_writeServerStatus(buffer)) { | |
598 _writeAnalysisStatus(buffer); | |
599 _writeEditStatus(buffer); | |
600 _writeExecutionStatus(buffer); | |
601 _writePluginStatus(buffer); | |
602 _writeRecentOutput(buffer); | |
603 } | |
604 }); | |
605 }); | |
606 } | |
607 | |
608 /** | |
609 * Return an error in response to an unrecognized request received by the HTTP | |
610 * server. | |
611 */ | |
612 void _returnUnknownRequest(HttpRequest request) { | |
613 _writeResponse(request, (StringBuffer buffer) { | |
614 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) { | |
615 buffer.write('<h3>Unknown page: '); | |
616 buffer.write(request.uri.path); | |
617 buffer.write('</h3>'); | |
618 buffer.write(''' | |
619 <p> | |
620 You have reached an un-recognized page. If you reached this page by | |
621 following a link from a status page, please report the broken link to | |
622 the Dart analyzer team: | |
623 <a>https://github.com/dart-lang/sdk/issues/new</a>. | |
624 </p><p> | |
625 If you mistyped the URL, you can correct it or return to | |
626 ${makeLink(STATUS_PATH, {}, 'the main status page')}. | |
627 </p>'''); | |
628 }); | |
629 }); | |
630 } | |
631 | |
632 /** | |
633 * Return a two digit decimal representation of the given non-negative integer | |
634 * [value]. | |
635 */ | |
636 String _twoDigit(int value) { | |
637 if (value < 10) { | |
638 return '0$value'; | |
639 } | |
640 return value.toString(); | |
641 } | |
642 | |
643 /** | |
644 * Write the status of the analysis domain (on the main status page) to the | |
645 * given [buffer] object. | |
646 */ | |
647 void _writeAnalysisStatus(StringBuffer buffer) { | |
648 AnalysisServer analysisServer = _server.analysisServer; | |
649 Map<Folder, AnalysisDriver> driverMap = analysisServer.driverMap; | |
650 List<Folder> folders = driverMap.keys.toList(); | |
651 folders.sort((Folder first, Folder second) => | |
652 first.shortName.compareTo(second.shortName)); | |
653 | |
654 buffer.write('<h3>Analysis Domain</h3>'); | |
655 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
656 buffer.write('<p>Using package resolver provider: '); | |
657 buffer.write(_server.packageResolverProvider != null); | |
658 buffer.write('</p>'); | |
659 buffer.write('<p>'); | |
660 buffer.write(makeLink(OVERLAYS_PATH, {}, 'All overlay information')); | |
661 buffer.write('</p>'); | |
662 | |
663 buffer.write('<p><b>Analysis Contexts</b></p>'); | |
664 buffer.write('<p>'); | |
665 bool first = true; | |
666 folders.forEach((Folder folder) { | |
667 if (first) { | |
668 first = false; | |
669 } else { | |
670 buffer.write('<br>'); | |
671 } | |
672 String key = folder.shortName; | |
673 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path}, | |
674 key, _hasException(driverMap[folder]))); | |
675 if (!folder.getChild('.packages').exists) { | |
676 buffer.write(' <small>[no .packages file]</small>'); | |
677 } | |
678 }); | |
679 buffer.write('</p>'); | |
680 | |
681 int freq = AnalysisServer.performOperationDelayFrequency; | |
682 String delay = freq > 0 ? '1 ms every $freq ms' : 'off'; | |
683 | |
684 buffer.write('<p><b>Performance Data</b></p>'); | |
685 buffer.write('<p>Perform operation delay: $delay</p>'); | |
686 buffer.write('<p>'); | |
687 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data')); | |
688 buffer.write('</p>'); | |
689 }, (StringBuffer buffer) { | |
690 _writeSubscriptionMap( | |
691 buffer, AnalysisService.VALUES, analysisServer.analysisServices); | |
692 }); | |
693 } | |
694 | |
695 /** | |
696 * Write multiple columns of information to the given [buffer], where the list | |
697 * of [columns] functions are used to generate the content of those columns. | |
698 */ | |
699 void _writeColumns(StringBuffer buffer, List<HtmlGenerator> columns) { | |
700 buffer | |
701 .write('<table class="column"><tr class="column"><td class="column">'); | |
702 int count = columns.length; | |
703 for (int i = 0; i < count; i++) { | |
704 if (i > 0) { | |
705 buffer.write('</td><td class="column">'); | |
706 } | |
707 columns[i](buffer); | |
708 } | |
709 buffer.write('</td></tr></table>'); | |
710 } | |
711 | |
712 /** | |
713 * Write performance information about a specific completion request | |
714 * to the given [buffer] object. | |
715 */ | |
716 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) { | |
717 CompletionDomainHandler handler = _completionDomainHandler; | |
718 CompletionPerformance performance; | |
719 if (handler != null) { | |
720 List<CompletionPerformance> list = handler.performanceList; | |
721 if (list != null && list.isNotEmpty) { | |
722 performance = list[max(0, min(list.length - 1, index))]; | |
723 } | |
724 } | |
725 if (performance == null) { | |
726 buffer.write('<h3>Completion Performance Detail</h3>'); | |
727 buffer.write('<p>No completions yet</p>'); | |
728 return; | |
729 } | |
730 buffer.write('<h3>Completion Performance Detail</h3>'); | |
731 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}'); | |
732 buffer.write('<table>'); | |
733 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true); | |
734 performance.operations.forEach((OperationPerformance op) { | |
735 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???'; | |
736 _writeRow(buffer, [elapsed, ' ', op.name]); | |
737 }); | |
738 buffer.write('</table>'); | |
739 buffer.write('<p><b>Compute Cache Performance</b>: '); | |
740 if (handler.computeCachePerformance == null) { | |
741 buffer.write('none'); | |
742 } else { | |
743 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds; | |
744 Source source = handler.computeCachePerformance.source; | |
745 buffer.write(' $elapsed ms for $source'); | |
746 } | |
747 buffer.write('</p>'); | |
748 } | |
749 | |
750 /** | |
751 * Write a table showing summary information for the last several | |
752 * completion requests to the given [buffer] object. | |
753 */ | |
754 void _writeCompletionPerformanceList(StringBuffer buffer) { | |
755 CompletionDomainHandler handler = _completionDomainHandler; | |
756 buffer.write('<h3>Completion Performance List</h3>'); | |
757 if (handler == null) { | |
758 return; | |
759 } | |
760 buffer.write('<table>'); | |
761 _writeRow( | |
762 buffer, | |
763 [ | |
764 'Start Time', | |
765 '', | |
766 'First (ms)', | |
767 '', | |
768 'Complete (ms)', | |
769 '', | |
770 '# Notifications', | |
771 '', | |
772 '# Suggestions', | |
773 '', | |
774 'Snippet' | |
775 ], | |
776 header: true); | |
777 int index = 0; | |
778 for (CompletionPerformance performance in handler.performanceList) { | |
779 String link = makeLink(COMPLETION_PATH, {'index': '$index'}, | |
780 '${performance.startTimeAndMs}'); | |
781 _writeRow(buffer, [ | |
782 link, | |
783 ' ', | |
784 performance.firstNotificationInMilliseconds, | |
785 ' ', | |
786 performance.elapsedInMilliseconds, | |
787 ' ', | |
788 performance.notificationCount, | |
789 ' ', | |
790 performance.suggestionCount, | |
791 ' ', | |
792 HTML_ESCAPE.convert(performance.snippet) | |
793 ]); | |
794 ++index; | |
795 } | |
796 | |
797 buffer.write('</table>'); | |
798 buffer.write(''' | |
799 <p><strong>First (ms)</strong> - the number of milliseconds | |
800 from when completion received the request until the first notification | |
801 with completion results was queued for sending back to the client. | |
802 <p><strong>Complete (ms)</strong> - the number of milliseconds | |
803 from when completion received the request until the final notification | |
804 with completion results was queued for sending back to the client. | |
805 <p><strong># Notifications</strong> - the total number of notifications | |
806 sent to the client with completion results for this request. | |
807 <p><strong># Suggestions</strong> - the number of suggestions | |
808 sent to the client in the first notification, followed by a comma, | |
809 followed by the number of suggestions send to the client | |
810 in the last notification. If there is only one notification, | |
811 then there will be only one number in this column.'''); | |
812 } | |
813 | |
814 /** | |
815 * Write the status of the edit domain (on the main status page) to the given | |
816 * [buffer]. | |
817 */ | |
818 void _writeEditStatus(StringBuffer buffer) { | |
819 buffer.write('<h3>Edit Domain</h3>'); | |
820 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
821 buffer.write('<p><b>Performance Data</b></p>'); | |
822 buffer.write('<p>'); | |
823 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data')); | |
824 buffer.write('</p>'); | |
825 }, (StringBuffer buffer) {}); | |
826 } | |
827 | |
828 /** | |
829 * Write a representation of the given [caughtException] to the given | |
830 * [buffer]. If [isCause] is `true`, then the exception was a cause for | |
831 * another exception. | |
832 */ | |
833 void _writeException(StringBuffer buffer, CaughtException caughtException, | |
834 {bool isCause: false}) { | |
835 Object exception = caughtException.exception; | |
836 | |
837 if (exception is AnalysisException) { | |
838 buffer.write('<p>'); | |
839 if (isCause) { | |
840 buffer.write('Caused by '); | |
841 } | |
842 buffer.write(exception.message); | |
843 buffer.write('</p>'); | |
844 _writeStackTrace(buffer, caughtException.stackTrace); | |
845 CaughtException cause = exception.cause; | |
846 if (cause != null) { | |
847 buffer.write('<blockquote>'); | |
848 _writeException(buffer, cause, isCause: true); | |
849 buffer.write('</blockquote>'); | |
850 } | |
851 } else { | |
852 buffer.write('<p>'); | |
853 if (isCause) { | |
854 buffer.write('Caused by '); | |
855 } | |
856 buffer.write(exception.toString()); | |
857 buffer.write('<p>'); | |
858 _writeStackTrace(buffer, caughtException.stackTrace); | |
859 } | |
860 } | |
861 | |
862 /** | |
863 * Write the status of the execution domain (on the main status page) to the | |
864 * given [buffer]. | |
865 */ | |
866 void _writeExecutionStatus(StringBuffer buffer) { | |
867 AnalysisServer analysisServer = _server.analysisServer; | |
868 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere( | |
869 (RequestHandler handler) => handler is ExecutionDomainHandler, | |
870 orElse: () => null); | |
871 Set<ExecutionService> services = new Set<ExecutionService>(); | |
872 if (handler.onFileAnalyzed != null) { | |
873 services.add(ExecutionService.LAUNCH_DATA); | |
874 } | |
875 | |
876 if (handler != null) { | |
877 buffer.write('<h3>Execution Domain</h3>'); | |
878 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
879 _writeSubscriptionList(buffer, ExecutionService.VALUES, services); | |
880 }, (StringBuffer buffer) {}); | |
881 } | |
882 } | |
883 | |
884 /** | |
885 * Write to the given [buffer] a representation of the given [map] of strings | |
886 * to strings. | |
887 */ | |
888 void _writeMapOfStringToString(StringBuffer buffer, Map<String, String> map) { | |
889 List<String> keys = map.keys.toList(); | |
890 keys.sort(); | |
891 int length = keys.length; | |
892 buffer.write('{'); | |
893 for (int i = 0; i < length; i++) { | |
894 buffer.write('<br>'); | |
895 String key = keys[i]; | |
896 if (i > 0) { | |
897 buffer.write(', '); | |
898 } | |
899 buffer.write(key); | |
900 buffer.write(' = '); | |
901 buffer.write(map[key]); | |
902 } | |
903 buffer.write('<br>}'); | |
904 } | |
905 | |
906 /** | |
907 * Write a representation of an analysis option with the given [name] and | |
908 * [value] to the given [buffer]. The option should be separated from other | |
909 * options unless the [last] flag is true, indicating that this is the last | |
910 * option in the list of options. | |
911 */ | |
912 void _writeOption(StringBuffer buffer, String name, Object value, | |
913 {bool last: false}) { | |
914 buffer.write(name); | |
915 buffer.write(' = '); | |
916 buffer.write(value.toString()); | |
917 if (!last) { | |
918 buffer.write('<br>'); | |
919 } | |
920 } | |
921 | |
922 /** | |
923 * Write a standard HTML page to the given [buffer]. The page will have the | |
924 * given [title] and a body that is generated by the given [body] generator. | |
925 */ | |
926 void _writePage(StringBuffer buffer, String title, List<String> subtitles, | |
927 HtmlGenerator body) { | |
928 DateTime now = new DateTime.now(); | |
929 String date = "${now.month}/${now.day}/${now.year}"; | |
930 String time = | |
931 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil lisecond}"; | |
932 | |
933 buffer.write('<!DOCTYPE html>'); | |
934 buffer.write('<html>'); | |
935 buffer.write('<head>'); | |
936 buffer.write('<meta charset="utf-8">'); | |
937 buffer.write( | |
938 '<meta name="viewport" content="width=device-width, initial-scale=1.0">' ); | |
939 buffer.write('<title>$title</title>'); | |
940 buffer.write('<style>'); | |
941 buffer.write('a {color: #0000DD; text-decoration: none;}'); | |
942 buffer.write('a:link.error {background-color: #FFEEEE;}'); | |
943 buffer.write('a:visited.error {background-color: #FFEEEE;}'); | |
944 buffer.write('a:hover.error {background-color: #FFEEEE;}'); | |
945 buffer.write('a:active.error {background-color: #FFEEEE;}'); | |
946 buffer.write( | |
947 'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}'); | |
948 buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}'); | |
949 buffer.write( | |
950 'p.commentary {margin-top: 1em; margin-bottom: 1em; margin-left: 2em; fo nt-style: italic;}'); | |
951 // response.write('span.error {text-decoration-line: underline; text-decorati on-color: red; text-decoration-style: wavy;}'); | |
952 buffer.write( | |
953 'table.column {border: 0px solid black; width: 100%; table-layout: fixed ;}'); | |
954 buffer.write('td.column {vertical-align: top; width: 50%;}'); | |
955 buffer.write('td.right {text-align: right;}'); | |
956 buffer.write('th {text-align: left; vertical-align:top;}'); | |
957 buffer.write('tr {vertical-align:top;}'); | |
958 buffer.write('</style>'); | |
959 buffer.write('</head>'); | |
960 | |
961 buffer.write('<body>'); | |
962 buffer.write( | |
963 '<h2>$title <small><small>(as of $time on $date)</small></small></h2>'); | |
964 if (subtitles != null && subtitles.isNotEmpty) { | |
965 buffer.write('<blockquote>'); | |
966 bool first = true; | |
967 for (String subtitle in subtitles) { | |
968 if (first) { | |
969 first = false; | |
970 } else { | |
971 buffer.write('<br>'); | |
972 } | |
973 buffer.write('<b>'); | |
974 buffer.write(subtitle); | |
975 buffer.write('</b>'); | |
976 } | |
977 buffer.write('</blockquote>'); | |
978 } | |
979 try { | |
980 body(buffer); | |
981 } catch (exception, stackTrace) { | |
982 buffer.write('<h3>Exception while creating page</h3>'); | |
983 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
984 } | |
985 buffer.write('</body>'); | |
986 buffer.write('</html>'); | |
987 } | |
988 | |
989 /** | |
990 * Write the recent output section (on the main status page) to the given | |
991 * [buffer] object. | |
992 */ | |
993 void _writePluginStatus(StringBuffer buffer) { | |
994 void writePlugin(Plugin plugin) { | |
995 buffer.write(plugin.uniqueIdentifier); | |
996 buffer.write(' ('); | |
997 buffer.write(plugin.runtimeType); | |
998 buffer.write(')<br>'); | |
999 } | |
1000 | |
1001 buffer.write('<h3>Plugin Status</h3><p>'); | |
1002 writePlugin(AnalysisEngine.instance.enginePlugin); | |
1003 writePlugin(_server.serverPlugin); | |
1004 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) { | |
1005 writePlugin(plugin); | |
1006 } | |
1007 buffer.write('<p>'); | |
1008 } | |
1009 | |
1010 /** | |
1011 * Write the recent output section (on the main status page) to the given | |
1012 * [buffer] object. | |
1013 */ | |
1014 void _writeRecentOutput(StringBuffer buffer) { | |
1015 buffer.write('<h3>Recent Output</h3>'); | |
1016 String output = HTML_ESCAPE.convert(_printBuffer.join('\n')); | |
1017 if (output.isEmpty) { | |
1018 buffer.write('<i>none</i>'); | |
1019 } else { | |
1020 buffer.write('<pre>'); | |
1021 buffer.write(output); | |
1022 buffer.write('</pre>'); | |
1023 } | |
1024 } | |
1025 | |
1026 void _writeResponse(HttpRequest request, HtmlGenerator writePage) { | |
1027 HttpResponse response = request.response; | |
1028 response.statusCode = HttpStatus.OK; | |
1029 response.headers.contentType = _htmlContent; | |
1030 try { | |
1031 StringBuffer buffer = new StringBuffer(); | |
1032 try { | |
1033 writePage(buffer); | |
1034 } catch (exception, stackTrace) { | |
1035 buffer.clear(); | |
1036 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) { | |
1037 _writeException(buffer, new CaughtException(exception, stackTrace)); | |
1038 }); | |
1039 } | |
1040 response.write(buffer.toString()); | |
1041 } finally { | |
1042 response.close(); | |
1043 } | |
1044 } | |
1045 | |
1046 /** | |
1047 * Write a single row within a table to the given [buffer]. The row will have | |
1048 * one cell for each of the [columns], and will be a header row if [header] is | |
1049 * `true`. | |
1050 */ | |
1051 void _writeRow(StringBuffer buffer, List<Object> columns, | |
1052 {bool header: false, List<String> classes}) { | |
1053 buffer.write('<tr>'); | |
1054 int count = columns.length; | |
1055 int maxClassIndex = classes == null ? 0 : classes.length - 1; | |
1056 for (int i = 0; i < count; i++) { | |
1057 String classAttribute = ''; | |
1058 if (classes != null) { | |
1059 String className = classes[min(i, maxClassIndex)]; | |
1060 if (className != null) { | |
1061 classAttribute = ' class="$className"'; | |
1062 } | |
1063 } | |
1064 if (header) { | |
1065 buffer.write('<th$classAttribute>'); | |
1066 } else { | |
1067 buffer.write('<td$classAttribute>'); | |
1068 } | |
1069 buffer.write(columns[i]); | |
1070 if (header) { | |
1071 buffer.write('</th>'); | |
1072 } else { | |
1073 buffer.write('</td>'); | |
1074 } | |
1075 } | |
1076 buffer.write('</tr>'); | |
1077 } | |
1078 | |
1079 /** | |
1080 * Write the status of the service domain (on the main status page) to the | |
1081 * given [response] object. | |
1082 */ | |
1083 bool _writeServerStatus(StringBuffer buffer) { | |
1084 AnalysisServer analysisServer = _server.analysisServer; | |
1085 Set<ServerService> services = analysisServer.serverServices; | |
1086 | |
1087 buffer.write('<h3>Server Domain</h3>'); | |
1088 _writeTwoColumns(buffer, (StringBuffer buffer) { | |
1089 if (analysisServer == null) { | |
1090 buffer.write('Status: <span style="color:red">Not running</span>'); | |
1091 return; | |
1092 } | |
1093 buffer.write('<p>'); | |
1094 buffer.write('Status: Running<br>'); | |
1095 buffer.write('New analysis driver: '); | |
1096 buffer.write(analysisServer.options.enableNewAnalysisDriver); | |
1097 buffer.write('<br>'); | |
1098 buffer.write('Instrumentation: '); | |
1099 if (AnalysisEngine.instance.instrumentationService.isActive) { | |
1100 buffer.write('<span style="color:red">Active</span>'); | |
1101 } else { | |
1102 buffer.write('Inactive'); | |
1103 } | |
1104 buffer.write('<br>'); | |
1105 buffer.write('Version: '); | |
1106 buffer.write(AnalysisServer.VERSION); | |
1107 buffer.write('<br>'); | |
1108 buffer.write('Process ID: '); | |
1109 buffer.write(pid); | |
1110 buffer.write('</p>'); | |
1111 | |
1112 buffer.write('<p><b>Performance Data</b></p>'); | |
1113 buffer.write('<p>'); | |
1114 buffer.write(makeLink( | |
1115 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance')); | |
1116 buffer.write('</p>'); | |
1117 }, (StringBuffer buffer) { | |
1118 _writeSubscriptionList(buffer, ServerService.VALUES, services); | |
1119 }); | |
1120 return analysisServer != null; | |
1121 } | |
1122 | |
1123 /** | |
1124 * Write a representation of the given [stackTrace] to the given [buffer]. | |
1125 */ | |
1126 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) { | |
1127 if (stackTrace != null) { | |
1128 String trace = stackTrace.toString().replaceAll('#', '<br>#'); | |
1129 if (trace.startsWith('<br>#')) { | |
1130 trace = trace.substring(4); | |
1131 } | |
1132 buffer.write('<p>'); | |
1133 buffer.write(trace); | |
1134 buffer.write('</p>'); | |
1135 } | |
1136 } | |
1137 | |
1138 /** | |
1139 * Given a [service] that could be subscribed to and a set of the services | |
1140 * that are actually subscribed to ([subscribedServices]), write a | |
1141 * representation of the service to the given [buffer]. | |
1142 */ | |
1143 void _writeSubscriptionInList( | |
1144 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) { | |
1145 if (subscribedServices.contains(service)) { | |
1146 buffer.write('<code>+ </code>'); | |
1147 } else { | |
1148 buffer.write('<code>- </code>'); | |
1149 } | |
1150 buffer.write(service.name); | |
1151 buffer.write('<br>'); | |
1152 } | |
1153 | |
1154 /** | |
1155 * Given a [service] that could be subscribed to and a set of paths that are | |
1156 * subscribed to the services ([subscribedPaths]), write a representation of | |
1157 * the service to the given [buffer]. | |
1158 */ | |
1159 void _writeSubscriptionInMap( | |
1160 StringBuffer buffer, Enum service, Set<String> subscribedPaths) { | |
1161 buffer.write('<p>'); | |
1162 buffer.write(service.name); | |
1163 buffer.write('</p>'); | |
1164 if (subscribedPaths == null || subscribedPaths.isEmpty) { | |
1165 buffer.write('none'); | |
1166 } else { | |
1167 List<String> paths = subscribedPaths.toList(); | |
1168 paths.sort(); | |
1169 for (String path in paths) { | |
1170 buffer.write('<p>'); | |
1171 buffer.write(path); | |
1172 buffer.write('</p>'); | |
1173 } | |
1174 } | |
1175 } | |
1176 | |
1177 /** | |
1178 * Given a list containing all of the services that can be subscribed to in a | |
1179 * single domain ([allServices]) and a set of the services that are actually | |
1180 * subscribed to ([subscribedServices]), write a representation of the | |
1181 * subscriptions to the given [buffer]. | |
1182 */ | |
1183 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices, | |
1184 Set<Enum> subscribedServices) { | |
1185 buffer.write('<p><b>Subscriptions</b></p>'); | |
1186 buffer.write('<p>'); | |
1187 for (Enum service in allServices) { | |
1188 _writeSubscriptionInList(buffer, service, subscribedServices); | |
1189 } | |
1190 buffer.write('</p>'); | |
1191 } | |
1192 | |
1193 /** | |
1194 * Given a list containing all of the services that can be subscribed to in a | |
1195 * single domain ([allServices]) and a set of the services that are actually | |
1196 * subscribed to ([subscribedServices]), write a representation of the | |
1197 * subscriptions to the given [buffer]. | |
1198 */ | |
1199 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices, | |
1200 Map<Enum, Set<String>> subscribedServices) { | |
1201 buffer.write('<p><b>Subscriptions</b></p>'); | |
1202 for (Enum service in allServices) { | |
1203 _writeSubscriptionInMap(buffer, service, subscribedServices[service]); | |
1204 } | |
1205 } | |
1206 | |
1207 /** | |
1208 * Write two columns of information to the given [buffer], where the | |
1209 * [leftColumn] and [rightColumn] functions are used to generate the content | |
1210 * of those columns. | |
1211 */ | |
1212 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn, | |
1213 HtmlGenerator rightColumn) { | |
1214 buffer | |
1215 .write('<table class="column"><tr class="column"><td class="column">'); | |
1216 leftColumn(buffer); | |
1217 buffer.write('</td><td class="column">'); | |
1218 rightColumn(buffer); | |
1219 buffer.write('</td></tr></table>'); | |
1220 } | |
1221 | |
1222 /** | |
1223 * Create a link to [path] with query parameters [params], with inner HTML | |
1224 * [innerHtml]. If [hasError] is `true`, then the link will have the class | |
1225 * 'error'. | |
1226 */ | |
1227 static String makeLink( | |
1228 String path, Map<String, String> params, String innerHtml, | |
1229 [bool hasError = false]) { | |
1230 Uri uri = params.isEmpty | |
1231 ? new Uri(path: path) | |
1232 : new Uri(path: path, queryParameters: params); | |
1233 String href = HTML_ESCAPE.convert(uri.toString()); | |
1234 String classAttribute = hasError ? ' class="error"' : ''; | |
1235 return '<a href="$href"$classAttribute>$innerHtml</a>'; | |
1236 } | |
1237 } | |
OLD | NEW |