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