Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | OrthancServer/OrthancRestApi/OrthancRestSystem.cpp@e3b3af80732d |
children | 05b8fd21089c |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeadersServer.h" | |
35 #include "OrthancRestApi.h" | |
36 | |
37 #include "../../Core/DicomParsing/FromDcmtkBridge.h" | |
38 #include "../../Core/MetricsRegistry.h" | |
39 #include "../../Plugins/Engine/OrthancPlugins.h" | |
40 #include "../../Plugins/Engine/PluginsManager.h" | |
41 #include "../OrthancConfiguration.h" | |
42 #include "../ServerContext.h" | |
43 | |
44 | |
45 static const char* LOG_LEVEL_DEFAULT = "default"; | |
46 static const char* LOG_LEVEL_VERBOSE = "verbose"; | |
47 static const char* LOG_LEVEL_TRACE = "trace"; | |
48 | |
49 | |
50 namespace Orthanc | |
51 { | |
52 // System information ------------------------------------------------------- | |
53 | |
54 static void ServeRoot(RestApiGetCall& call) | |
55 { | |
56 call.GetOutput().Redirect("app/explorer.html"); | |
57 } | |
58 | |
59 static void GetSystemInformation(RestApiGetCall& call) | |
60 { | |
61 ServerContext& context = OrthancRestApi::GetContext(call); | |
62 | |
63 Json::Value result = Json::objectValue; | |
64 | |
65 result["ApiVersion"] = ORTHANC_API_VERSION; | |
66 result["Version"] = ORTHANC_VERSION; | |
67 result["DatabaseVersion"] = OrthancRestApi::GetIndex(call).GetDatabaseVersion(); | |
68 result["IsHttpServerSecure"] = context.IsHttpServerSecure(); // New in Orthanc 1.5.8 | |
69 | |
70 { | |
71 OrthancConfiguration::ReaderLock lock; | |
72 result["DicomAet"] = lock.GetConfiguration().GetStringParameter("DicomAet", "ORTHANC"); | |
73 result["DicomPort"] = lock.GetConfiguration().GetUnsignedIntegerParameter("DicomPort", 4242); | |
74 result["HttpPort"] = lock.GetConfiguration().GetUnsignedIntegerParameter("HttpPort", 8042); | |
75 result["Name"] = lock.GetConfiguration().GetStringParameter("Name", ""); | |
76 } | |
77 | |
78 result["StorageAreaPlugin"] = Json::nullValue; | |
79 result["DatabaseBackendPlugin"] = Json::nullValue; | |
80 | |
81 #if ORTHANC_ENABLE_PLUGINS == 1 | |
82 result["PluginsEnabled"] = true; | |
83 const OrthancPlugins& plugins = context.GetPlugins(); | |
84 | |
85 if (plugins.HasStorageArea()) | |
86 { | |
87 std::string p = plugins.GetStorageAreaLibrary().GetPath(); | |
88 result["StorageAreaPlugin"] = boost::filesystem::canonical(p).string(); | |
89 } | |
90 | |
91 if (plugins.HasDatabaseBackend()) | |
92 { | |
93 std::string p = plugins.GetDatabaseBackendLibrary().GetPath(); | |
94 result["DatabaseBackendPlugin"] = boost::filesystem::canonical(p).string(); | |
95 } | |
96 #else | |
97 result["PluginsEnabled"] = false; | |
98 #endif | |
99 | |
100 call.GetOutput().AnswerJson(result); | |
101 } | |
102 | |
103 static void GetStatistics(RestApiGetCall& call) | |
104 { | |
105 static const uint64_t MEGA_BYTES = 1024 * 1024; | |
106 | |
107 uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances; | |
108 OrthancRestApi::GetIndex(call).GetGlobalStatistics(diskSize, uncompressedSize, countPatients, | |
109 countStudies, countSeries, countInstances); | |
110 | |
111 Json::Value result = Json::objectValue; | |
112 result["TotalDiskSize"] = boost::lexical_cast<std::string>(diskSize); | |
113 result["TotalUncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); | |
114 result["TotalDiskSizeMB"] = static_cast<unsigned int>(diskSize / MEGA_BYTES); | |
115 result["TotalUncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES); | |
116 result["CountPatients"] = static_cast<unsigned int>(countPatients); | |
117 result["CountStudies"] = static_cast<unsigned int>(countStudies); | |
118 result["CountSeries"] = static_cast<unsigned int>(countSeries); | |
119 result["CountInstances"] = static_cast<unsigned int>(countInstances); | |
120 | |
121 call.GetOutput().AnswerJson(result); | |
122 } | |
123 | |
124 static void GenerateUid(RestApiGetCall& call) | |
125 { | |
126 std::string level = call.GetArgument("level", ""); | |
127 if (level == "patient") | |
128 { | |
129 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient), MimeType_PlainText); | |
130 } | |
131 else if (level == "study") | |
132 { | |
133 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study), MimeType_PlainText); | |
134 } | |
135 else if (level == "series") | |
136 { | |
137 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series), MimeType_PlainText); | |
138 } | |
139 else if (level == "instance") | |
140 { | |
141 call.GetOutput().AnswerBuffer(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance), MimeType_PlainText); | |
142 } | |
143 } | |
144 | |
145 static void ExecuteScript(RestApiPostCall& call) | |
146 { | |
147 ServerContext& context = OrthancRestApi::GetContext(call); | |
148 | |
149 if (!context.IsExecuteLuaEnabled()) | |
150 { | |
151 LOG(ERROR) << "The URI /tools/execute-script is disallowed for security, " | |
152 << "check your configuration file"; | |
153 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | |
154 return; | |
155 } | |
156 | |
157 std::string result; | |
158 std::string command; | |
159 call.BodyToString(command); | |
160 | |
161 { | |
162 LuaScripting::Lock lock(context.GetLuaScripting()); | |
163 lock.GetLua().Execute(result, command); | |
164 } | |
165 | |
166 call.GetOutput().AnswerBuffer(result, MimeType_PlainText); | |
167 } | |
168 | |
169 template <bool UTC> | |
170 static void GetNowIsoString(RestApiGetCall& call) | |
171 { | |
172 call.GetOutput().AnswerBuffer(SystemToolbox::GetNowIsoString(UTC), MimeType_PlainText); | |
173 } | |
174 | |
175 | |
176 static void GetDicomConformanceStatement(RestApiGetCall& call) | |
177 { | |
178 std::string statement; | |
179 GetFileResource(statement, ServerResources::DICOM_CONFORMANCE_STATEMENT); | |
180 call.GetOutput().AnswerBuffer(statement, MimeType_PlainText); | |
181 } | |
182 | |
183 | |
184 static void GetDefaultEncoding(RestApiGetCall& call) | |
185 { | |
186 Encoding encoding = GetDefaultDicomEncoding(); | |
187 call.GetOutput().AnswerBuffer(EnumerationToString(encoding), MimeType_PlainText); | |
188 } | |
189 | |
190 | |
191 static void SetDefaultEncoding(RestApiPutCall& call) | |
192 { | |
193 std::string body; | |
194 call.BodyToString(body); | |
195 | |
196 Encoding encoding = StringToEncoding(body.c_str()); | |
197 | |
198 { | |
199 OrthancConfiguration::WriterLock lock; | |
200 lock.GetConfiguration().SetDefaultEncoding(encoding); | |
201 } | |
202 | |
203 call.GetOutput().AnswerBuffer(EnumerationToString(encoding), MimeType_PlainText); | |
204 } | |
205 | |
206 | |
207 | |
208 // Plugins information ------------------------------------------------------ | |
209 | |
210 static void ListPlugins(RestApiGetCall& call) | |
211 { | |
212 Json::Value v = Json::arrayValue; | |
213 | |
214 v.append("explorer.js"); | |
215 | |
216 if (OrthancRestApi::GetContext(call).HasPlugins()) | |
217 { | |
218 #if ORTHANC_ENABLE_PLUGINS == 1 | |
219 std::list<std::string> plugins; | |
220 OrthancRestApi::GetContext(call).GetPlugins().GetManager().ListPlugins(plugins); | |
221 | |
222 for (std::list<std::string>::const_iterator | |
223 it = plugins.begin(); it != plugins.end(); ++it) | |
224 { | |
225 v.append(*it); | |
226 } | |
227 #endif | |
228 } | |
229 | |
230 call.GetOutput().AnswerJson(v); | |
231 } | |
232 | |
233 | |
234 static void GetPlugin(RestApiGetCall& call) | |
235 { | |
236 if (!OrthancRestApi::GetContext(call).HasPlugins()) | |
237 { | |
238 return; | |
239 } | |
240 | |
241 #if ORTHANC_ENABLE_PLUGINS == 1 | |
242 const PluginsManager& manager = OrthancRestApi::GetContext(call).GetPlugins().GetManager(); | |
243 std::string id = call.GetUriComponent("id", ""); | |
244 | |
245 if (manager.HasPlugin(id)) | |
246 { | |
247 Json::Value v = Json::objectValue; | |
248 v["ID"] = id; | |
249 v["Version"] = manager.GetPluginVersion(id); | |
250 | |
251 const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); | |
252 const char *c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_RootUri); | |
253 if (c != NULL) | |
254 { | |
255 std::string root = c; | |
256 if (!root.empty()) | |
257 { | |
258 // Turn the root URI into a URI relative to "/app/explorer.js" | |
259 if (root[0] == '/') | |
260 { | |
261 root = ".." + root; | |
262 } | |
263 | |
264 v["RootUri"] = root; | |
265 } | |
266 } | |
267 | |
268 c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_Description); | |
269 if (c != NULL) | |
270 { | |
271 v["Description"] = c; | |
272 } | |
273 | |
274 c = plugins.GetProperty(id.c_str(), _OrthancPluginProperty_OrthancExplorer); | |
275 v["ExtendsOrthancExplorer"] = (c != NULL); | |
276 | |
277 call.GetOutput().AnswerJson(v); | |
278 } | |
279 #endif | |
280 } | |
281 | |
282 | |
283 static void GetOrthancExplorerPlugins(RestApiGetCall& call) | |
284 { | |
285 std::string s = "// Extensions to Orthanc Explorer by the registered plugins\n\n"; | |
286 | |
287 if (OrthancRestApi::GetContext(call).HasPlugins()) | |
288 { | |
289 #if ORTHANC_ENABLE_PLUGINS == 1 | |
290 const OrthancPlugins& plugins = OrthancRestApi::GetContext(call).GetPlugins(); | |
291 const PluginsManager& manager = plugins.GetManager(); | |
292 | |
293 std::list<std::string> lst; | |
294 manager.ListPlugins(lst); | |
295 | |
296 for (std::list<std::string>::const_iterator | |
297 it = lst.begin(); it != lst.end(); ++it) | |
298 { | |
299 const char* tmp = plugins.GetProperty(it->c_str(), _OrthancPluginProperty_OrthancExplorer); | |
300 if (tmp != NULL) | |
301 { | |
302 s += "/**\n * From plugin: " + *it + " (version " + manager.GetPluginVersion(*it) + ")\n **/\n\n"; | |
303 s += std::string(tmp) + "\n\n"; | |
304 } | |
305 } | |
306 #endif | |
307 } | |
308 | |
309 call.GetOutput().AnswerBuffer(s, MimeType_JavaScript); | |
310 } | |
311 | |
312 | |
313 | |
314 | |
315 // Jobs information ------------------------------------------------------ | |
316 | |
317 static void ListJobs(RestApiGetCall& call) | |
318 { | |
319 bool expand = call.HasArgument("expand"); | |
320 | |
321 Json::Value v = Json::arrayValue; | |
322 | |
323 std::set<std::string> jobs; | |
324 OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().ListJobs(jobs); | |
325 | |
326 for (std::set<std::string>::const_iterator it = jobs.begin(); | |
327 it != jobs.end(); ++it) | |
328 { | |
329 if (expand) | |
330 { | |
331 JobInfo info; | |
332 if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, *it)) | |
333 { | |
334 Json::Value tmp; | |
335 info.Format(tmp); | |
336 v.append(tmp); | |
337 } | |
338 } | |
339 else | |
340 { | |
341 v.append(*it); | |
342 } | |
343 } | |
344 | |
345 call.GetOutput().AnswerJson(v); | |
346 } | |
347 | |
348 static void GetJobInfo(RestApiGetCall& call) | |
349 { | |
350 std::string id = call.GetUriComponent("id", ""); | |
351 | |
352 JobInfo info; | |
353 if (OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().GetJobInfo(info, id)) | |
354 { | |
355 Json::Value json; | |
356 info.Format(json); | |
357 call.GetOutput().AnswerJson(json); | |
358 } | |
359 } | |
360 | |
361 | |
362 static void GetJobOutput(RestApiGetCall& call) | |
363 { | |
364 std::string job = call.GetUriComponent("id", ""); | |
365 std::string key = call.GetUriComponent("key", ""); | |
366 | |
367 std::string value; | |
368 MimeType mime; | |
369 | |
370 if (OrthancRestApi::GetContext(call).GetJobsEngine(). | |
371 GetRegistry().GetJobOutput(value, mime, job, key)) | |
372 { | |
373 call.GetOutput().AnswerBuffer(value, mime); | |
374 } | |
375 else | |
376 { | |
377 throw OrthancException(ErrorCode_InexistentItem, | |
378 "Job has no such output: " + key); | |
379 } | |
380 } | |
381 | |
382 | |
383 enum JobAction | |
384 { | |
385 JobAction_Cancel, | |
386 JobAction_Pause, | |
387 JobAction_Resubmit, | |
388 JobAction_Resume | |
389 }; | |
390 | |
391 template <JobAction action> | |
392 static void ApplyJobAction(RestApiPostCall& call) | |
393 { | |
394 std::string id = call.GetUriComponent("id", ""); | |
395 | |
396 bool ok = false; | |
397 | |
398 switch (action) | |
399 { | |
400 case JobAction_Cancel: | |
401 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Cancel(id); | |
402 break; | |
403 | |
404 case JobAction_Pause: | |
405 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Pause(id); | |
406 break; | |
407 | |
408 case JobAction_Resubmit: | |
409 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resubmit(id); | |
410 break; | |
411 | |
412 case JobAction_Resume: | |
413 ok = OrthancRestApi::GetContext(call).GetJobsEngine().GetRegistry().Resume(id); | |
414 break; | |
415 | |
416 default: | |
417 throw OrthancException(ErrorCode_InternalError); | |
418 } | |
419 | |
420 if (ok) | |
421 { | |
422 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
423 } | |
424 } | |
425 | |
426 | |
427 static void GetMetricsPrometheus(RestApiGetCall& call) | |
428 { | |
429 #if ORTHANC_ENABLE_PLUGINS == 1 | |
430 OrthancRestApi::GetContext(call).GetPlugins().RefreshMetrics(); | |
431 #endif | |
432 | |
433 static const float MEGA_BYTES = 1024 * 1024; | |
434 | |
435 ServerContext& context = OrthancRestApi::GetContext(call); | |
436 | |
437 uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances; | |
438 context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, | |
439 countStudies, countSeries, countInstances); | |
440 | |
441 unsigned int jobsPending, jobsRunning, jobsSuccess, jobsFailed; | |
442 context.GetJobsEngine().GetRegistry().GetStatistics(jobsPending, jobsRunning, jobsSuccess, jobsFailed); | |
443 | |
444 MetricsRegistry& registry = context.GetMetricsRegistry(); | |
445 registry.SetValue("orthanc_disk_size_mb", static_cast<float>(diskSize) / MEGA_BYTES); | |
446 registry.SetValue("orthanc_uncompressed_size_mb", static_cast<float>(diskSize) / MEGA_BYTES); | |
447 registry.SetValue("orthanc_count_patients", static_cast<unsigned int>(countPatients)); | |
448 registry.SetValue("orthanc_count_studies", static_cast<unsigned int>(countStudies)); | |
449 registry.SetValue("orthanc_count_series", static_cast<unsigned int>(countSeries)); | |
450 registry.SetValue("orthanc_count_instances", static_cast<unsigned int>(countInstances)); | |
451 registry.SetValue("orthanc_jobs_pending", jobsPending); | |
452 registry.SetValue("orthanc_jobs_running", jobsRunning); | |
453 registry.SetValue("orthanc_jobs_completed", jobsSuccess + jobsFailed); | |
454 registry.SetValue("orthanc_jobs_success", jobsSuccess); | |
455 registry.SetValue("orthanc_jobs_failed", jobsFailed); | |
456 | |
457 std::string s; | |
458 registry.ExportPrometheusText(s); | |
459 | |
460 call.GetOutput().AnswerBuffer(s, MimeType_PrometheusText); | |
461 } | |
462 | |
463 | |
464 static void GetMetricsEnabled(RestApiGetCall& call) | |
465 { | |
466 bool enabled = OrthancRestApi::GetContext(call).GetMetricsRegistry().IsEnabled(); | |
467 call.GetOutput().AnswerBuffer(enabled ? "1" : "0", MimeType_PlainText); | |
468 } | |
469 | |
470 | |
471 static void PutMetricsEnabled(RestApiPutCall& call) | |
472 { | |
473 bool enabled; | |
474 | |
475 std::string body; | |
476 call.BodyToString(body); | |
477 | |
478 if (body == "1") | |
479 { | |
480 enabled = true; | |
481 } | |
482 else if (body == "0") | |
483 { | |
484 enabled = false; | |
485 } | |
486 else | |
487 { | |
488 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
489 "The HTTP body must be 0 or 1, but found: " + body); | |
490 } | |
491 | |
492 // Success | |
493 OrthancRestApi::GetContext(call).GetMetricsRegistry().SetEnabled(enabled); | |
494 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
495 } | |
496 | |
497 | |
498 static void GetLogLevel(RestApiGetCall& call) | |
499 { | |
500 std::string s; | |
501 | |
502 if (Logging::IsTraceLevelEnabled()) | |
503 { | |
504 s = LOG_LEVEL_TRACE; | |
505 } | |
506 else if (Logging::IsInfoLevelEnabled()) | |
507 { | |
508 s = LOG_LEVEL_VERBOSE; | |
509 } | |
510 else | |
511 { | |
512 s = LOG_LEVEL_DEFAULT; | |
513 } | |
514 | |
515 call.GetOutput().AnswerBuffer(s, MimeType_PlainText); | |
516 } | |
517 | |
518 | |
519 static void PutLogLevel(RestApiPutCall& call) | |
520 { | |
521 std::string body; | |
522 call.BodyToString(body); | |
523 | |
524 if (body == LOG_LEVEL_DEFAULT) | |
525 { | |
526 Logging::EnableInfoLevel(false); | |
527 Logging::EnableTraceLevel(false); | |
528 } | |
529 else if (body == LOG_LEVEL_VERBOSE) | |
530 { | |
531 Logging::EnableInfoLevel(true); | |
532 Logging::EnableTraceLevel(false); | |
533 } | |
534 else if (body == LOG_LEVEL_TRACE) | |
535 { | |
536 Logging::EnableInfoLevel(true); | |
537 Logging::EnableTraceLevel(true); | |
538 } | |
539 else | |
540 { | |
541 throw OrthancException(ErrorCode_ParameterOutOfRange, | |
542 "The log level must be one of the following values: \"" + | |
543 std::string(LOG_LEVEL_DEFAULT) + "\", \"" + | |
544 std::string(LOG_LEVEL_VERBOSE) + "\", of \"" + | |
545 std::string(LOG_LEVEL_TRACE) + "\""); | |
546 } | |
547 | |
548 // Success | |
549 LOG(WARNING) << "REST API call has switched the log level to: " << body; | |
550 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
551 } | |
552 | |
553 | |
554 void OrthancRestApi::RegisterSystem() | |
555 { | |
556 Register("/", ServeRoot); | |
557 Register("/system", GetSystemInformation); | |
558 Register("/statistics", GetStatistics); | |
559 Register("/tools/generate-uid", GenerateUid); | |
560 Register("/tools/execute-script", ExecuteScript); | |
561 Register("/tools/now", GetNowIsoString<true>); | |
562 Register("/tools/now-local", GetNowIsoString<false>); | |
563 Register("/tools/dicom-conformance", GetDicomConformanceStatement); | |
564 Register("/tools/default-encoding", GetDefaultEncoding); | |
565 Register("/tools/default-encoding", SetDefaultEncoding); | |
566 Register("/tools/metrics", GetMetricsEnabled); | |
567 Register("/tools/metrics", PutMetricsEnabled); | |
568 Register("/tools/metrics-prometheus", GetMetricsPrometheus); | |
569 Register("/tools/log-level", GetLogLevel); | |
570 Register("/tools/log-level", PutLogLevel); | |
571 | |
572 Register("/plugins", ListPlugins); | |
573 Register("/plugins/{id}", GetPlugin); | |
574 Register("/plugins/explorer.js", GetOrthancExplorerPlugins); | |
575 | |
576 Register("/jobs", ListJobs); | |
577 Register("/jobs/{id}", GetJobInfo); | |
578 Register("/jobs/{id}/cancel", ApplyJobAction<JobAction_Cancel>); | |
579 Register("/jobs/{id}/pause", ApplyJobAction<JobAction_Pause>); | |
580 Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>); | |
581 Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>); | |
582 Register("/jobs/{id}/{key}", GetJobOutput); | |
583 } | |
584 } |