tdaq-develop-2025-02-12
ECLSupervisor.cc
1 #include "otsdaq-utilities/ECLWriter/ECLSupervisor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/ConfigurationInterface/ConfigurationManager.h"
4 #include "otsdaq/Macros/CoutMacros.h"
5 #include "otsdaq/MessageFacility/MessageFacility.h"
6 #include "otsdaq/SOAPUtilities/SOAPCommand.h"
7 #include "otsdaq/SOAPUtilities/SOAPParameters.h"
8 #include "otsdaq/SOAPUtilities/SOAPUtilities.h"
9 #include "otsdaq/TablePlugins/XDAQContextTable/XDAQContextTable.h"
10 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
11 
12 #include <dirent.h> /*DIR and dirent*/
13 #include <sys/stat.h> /*mkdir*/
14 
15 #include <xdaq/NamespaceURI.h>
16 
17 #include <iomanip>
18 #include <iostream>
19 #include "otsdaq/TableCore/TableGroupKey.h"
20 
21 using namespace ots;
22 
23 #undef __MF_SUBJECT__
24 #define __MF_SUBJECT__ "ECL"
25 
26 #define XML_ADMIN_STATUS "logbook_admin_status"
27 #define XML_STATUS "logbook_status"
28 #define XML_MOST_RECENT_DAY "most_recent_day"
29 #define XML_TIMEZONE_OFFSET "timezone_offset_hours"
30 #define XML_CATEGORY_ROOT "categories"
31 #define XML_CATEGORY "category"
32 #define XML_ACTIVE_CATEGORY "active_category"
33 #define XML_SAFE_URL "ecl_url"
34 #define XML_RESPONSE_CATEGORY "response_category"
35 #define XML_CATEGORY_CREATE "create_time"
36 #define XML_CATEGORY_CREATOR "creator"
37 
38 #define XML_LOGBOOK_ENTRY "logbook_entry"
39 #define XML_LOGBOOK_ENTRY_SUBJECT "logbook_entry_subject"
40 #define XML_LOGBOOK_ENTRY_TEXT "logbook_entry_text"
41 #define XML_LOGBOOK_ENTRY_FILE "logbook_entry_file"
42 #define XML_LOGBOOK_ENTRY_TIME "logbook_entry_time"
43 #define XML_LOGBOOK_ENTRY_CREATOR "logbook_entry_creator"
44 #define XML_LOGBOOK_ENTRY_HIDDEN "logbook_entry_hidden"
45 #define XML_LOGBOOK_ENTRY_HIDER "logbook_entry_hider"
46 #define XML_LOGBOOK_ENTRY_HIDDEN_TIME "logbook_entry_hidden_time"
47 
48 XDAQ_INSTANTIATOR_IMPL(ECLSupervisor)
49 
50 //==============================================================================
51 ECLSupervisor::ECLSupervisor(xdaq::ApplicationStub* stub) : CoreSupervisorBase(stub)
52 {
53  __SUP_COUT__ << "Constructor." << __E__;
54 
55  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
56 
57  xoap::bind(
58  this, &ECLSupervisor::MakeSystemLogEntry, "MakeSystemLogEntry", XDAQ_NS_URI);
59 
60  init();
61 
62  __SUP_COUT__ << "Constructed." << __E__;
63 } // end constructor
64 
65 //==============================================================================
66 void ECLSupervisor::init(void)
67 try
68 {
69  // do not put username/pw in saved/committed text files
70  ECLUser_ = __ENV__("ECL_USER_NAME");
71  ECLHost_ = __ENV__("ECL_URL"); // e.g. https://dbweb6.fnal.gov:8443/ECL/test_beam
72  ECLPwd_ = __ENV__("ECL_PASSWORD");
73  ECLCategory_ = __ENV__("ECL_CATEGORY");
74  CategoryName_ = __ENV__("OTS_OWNER") + std::string(" ots");
75 
76  // Determine the timezone offset from UTC time in hours
77  // Get the current time
78  std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
79 
80  // Get the current time in UTC
81  std::chrono::zoned_time<std::chrono::system_clock::duration> utcTime{"UTC", now};
82 
83  // Get the current local time using the current system's timezone
84  std::chrono::zoned_time<std::chrono::system_clock::duration> localTime{
85  std::chrono::current_zone(), now};
86 
87  // Calculate the difference between local time and UTC
88  std::chrono::seconds offset = std::chrono::duration_cast<std::chrono::seconds>(
89  localTime.get_local_time() - utcTime.get_local_time());
90 
91  // Convert the offset to hours and minutes
92  timezoneHourOffset_ = offset.count() / 3600;
93 
94  __SUP_COUTV__(timezoneHourOffset_);
95 
96  eclConn_ = std::make_unique<ECLConnection>(ECLUser_, ECLPwd_, ECLHost_);
97 
98 } // end init()
99 catch(const std::runtime_error& e)
100 {
101  __COUT_ERR__ << "ECL environment variables not setup: " << e.what() << __E__;
102  ECLUser_ = ""; //clearing to disable
103 }
104 
105 //==============================================================================
106 ECLSupervisor::~ECLSupervisor(void) { destroy(); }
107 
108 //==============================================================================
109 void ECLSupervisor::destroy(void)
110 {
111  // // called by destructor
112  // delete theConfigurationManager_;
113 
114  __SUP_COUT__ << "Destructed." << __E__;
115 } // end destroy()
116 
117 //==============================================================================
118 void ECLSupervisor::defaultPage(xgi::Input* /*in*/, xgi::Output* out)
119 {
120  __COUT__ << " active category " << ECLCategory_ << std::endl;
121  *out << "<!DOCTYPE HTML><html lang='en'><head><title>ots</title>"
122  << ECLSupervisor::getIconHeaderString() <<
123  // end show ots icon
124  "</head>"
125  << "<frameset col='100%' row='100%'><frame "
126  "src='/WebPath/html/Logbook.html?urn="
127  << this->getApplicationDescriptor()->getLocalId()
128  << "&active_category=" << ECLCategory_ << "'></frameset></html>";
129 } //end defaultPage()
130 
131 //==============================================================================
132 std::string ECLSupervisor::getIconHeaderString(void)
133 {
134  // show ots icon
135  // from http://www.favicon-generator.org/
136  return "<link rel='apple-touch-icon' sizes='57x57' href='/WebPath/images/otsdaqIcons/apple-icon-57x57.png'>\
137  <link rel='apple-touch-icon' sizes='60x60' href='/WebPath/images/otsdaqIcons/apple-icon-60x60.png'>\
138  <link rel='apple-touch-icon' sizes='72x72' href='/WebPath/images/otsdaqIcons/apple-icon-72x72.png'>\
139  <link rel='apple-touch-icon' sizes='76x76' href='/WebPath/images/otsdaqIcons/apple-icon-76x76.png'>\
140  <link rel='apple-touch-icon' sizes='114x114' href='/WebPath/images/otsdaqIcons/apple-icon-114x114.png'>\
141  <link rel='apple-touch-icon' sizes='120x120' href='/WebPath/images/otsdaqIcons/apple-icon-120x120.png'>\
142  <link rel='apple-touch-icon' sizes='144x144' href='/WebPath/images/otsdaqIcons/apple-icon-144x144.png'>\
143  <link rel='apple-touch-icon' sizes='152x152' href='/WebPath/images/otsdaqIcons/apple-icon-152x152.png'>\
144  <link rel='apple-touch-icon' sizes='180x180' href='/WebPath/images/otsdaqIcons/apple-icon-180x180.png'>\
145  <link rel='icon' type='image/png' sizes='192x192' href='/WebPath/images/otsdaqIcons/android-icon-192x192.png'>\
146  <link rel='icon' type='image/png' sizes='144x144' href='/WebPath/images/otsdaqIcons/android-icon-144x144.png'>\
147  <link rel='icon' type='image/png' sizes='48x48' href='/WebPath/images/otsdaqIcons/android-icon-48x48.png'>\
148  <link rel='icon' type='image/png' sizes='72x72' href='/WebPath/images/otsdaqIcons/android-icon-72x72.png'>\
149  <link rel='icon' type='image/png' sizes='32x32' href='/WebPath/images/otsdaqIcons/favicon-32x32.png'>\
150  <link rel='icon' type='image/png' sizes='96x96' href='/WebPath/images/otsdaqIcons/favicon-96x96.png'>\
151  <link rel='icon' type='image/png' sizes='16x16' href='/WebPath/images/otsdaqIcons/favicon-16x16.png'>\
152  <link rel='manifest' href='/WebPath/images/otsdaqIcons/manifest.json'>\
153  <meta name='msapplication-TileColor' content='#ffffff'>\
154  <meta name='msapplication-TileImage' content='/WebPath/images/otsdaqIcons/ms-icon-144x144.png'>\
155  <meta name='theme-color' content='#ffffff'>";
156 
157 } // end getIconHeaderString()
158 
159 //==============================================================================
164 {
165  CorePropertySupervisorBase::setSupervisorProperty(
166  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.UserPermissionsThreshold,
167  std::string() +
168  "*=1 | CreateCategory=-1 | RemoveCategory=-1 | GetCategoryListAdmin=-1 "
169  "| SetActiveCategory=-1" +
170  " | AdminRemoveRestoreEntry=-1");
171 
172  CorePropertySupervisorBase::setSupervisorProperty(
173  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AllowNoLoginRequestTypes,
174  "RefreshLogbook | GetCategoryList");
175 
176 } //end setSupervisorPropertyDefaults()
177 
178 //==============================================================================
182 {
183  CorePropertySupervisorBase::addSupervisorProperty(
184  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
185  "RefreshLogbook");
186  CorePropertySupervisorBase::setSupervisorProperty(
187  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.NonXMLRequestTypes,
188  "LogImage | LogReport");
189  CorePropertySupervisorBase::addSupervisorProperty(
190  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.RequireUserLockRequestTypes,
191  "CreateCategory | RemoveCategory | PreviewEntry | AdminRemoveRestoreEntry");
192 } //end forceSupervisorPropertyValues()
193 
194 //==============================================================================
198 void ECLSupervisor::request(const std::string& requestType,
199  cgicc::Cgicc& cgiIn,
200  HttpXmlDocument& xmlOut,
201  const WebUsers::RequestUserInfo& userInfo)
202 {
203  __COUTTV__(requestType);
204 
205  // Commands - Note: treat 'Category' as ECL Category
206  // N/A CreateCategory
207  // N/A RemoveCategory
208  // GetCategoryList
209  // SetActiveCategory
210  // RefreshLogbook
211  // PreviewEntry
212  // ApproveEntry
213  // N/A AdminRemoveRestoreEntry
214 
215  // to report to logbook admin status use
216  // xmlOut.addTextElementToData(XML_ADMIN_STATUS,tempStr);
217 
218  if(0 && requestType == "CreateCategory")
219  {
220  // check that category directory does not exist, and it is not in xml list
221  // create category (TODO - could set env variable ECLConnection/$ECL_CATEGORY)
222  //
223 
224  // get creator name
225  std::string creator = userInfo.username_;
226 
227  // createCategory(
228  // CgiDataUtilities::postData(cgiIn, "Category"), creator, &xmlOut);
229 
230  __COUT__ << "Created" << std::endl;
231  }
232  else if(0 && requestType == "RemoveCategory")
233  {
234  // remove from xml list, but do not remove directory (requires manual delete so
235  // mistakes aren't made)
236  //(TODO - could unset env variable ECLConnection/$ECL_CATEGORY)
237 
238  // get remover name
239  std::string remover = userInfo.username_;
240  // removeCategory(
241  // CgiDataUtilities::postData(cgiIn, "Category"), remover, &xmlOut);
242  }
243  else if(requestType == "GetCategoryList")
244  {
245  //allow all users to ECL categories, but tell GUI not admin since have no access to managing ECL posts directly
246  xmlOut.addTextElementToData("is_admin", "0"); // indicate not an admin
247  getCategories(&xmlOut);
248  }
249  else if(requestType == "SetActiveCategory")
250  {
251  // check that category exists
252  // set active category
253 
254  // if(userPermissions < ADMIN_PERMISSIONS_THRESHOLD)
255  // {
256  // xmlOut.addTextElementToData(XML_ADMIN_STATUS,"Error - Insufficient
257  // permissions."); goto CLEANUP;
258  // }
259 
260  webUserSetActiveCategory(CgiDataUtilities::postData(cgiIn, "Category"), &xmlOut);
261  }
262  else if(requestType == "RefreshLogbook")
263  {
264  // returns logbook for currently active category based on date and duration
265  // parameters
266 
267  std::string Date = CgiDataUtilities::postData(cgiIn, "Date");
268  uint32_t Duration = CgiDataUtilities::postDataAsInt(cgiIn, "Duration");
269  std::string CategoryFilter = CgiDataUtilities::postData(cgiIn, "CategoryFilter");
270 
271  __COUTV__(CategoryFilter);
272 
273  time_t date;
274  sscanf(Date.c_str(), "%li", &date); // scan for unsigned long
275 
276  __COUT__ << "date " << date << " duration " << Duration << std::endl;
277  std::stringstream str;
278  refreshLogbook(date,
279  Duration,
280  &xmlOut,
281  (std::ostringstream*)&str,
282  StringMacros::decodeURIComponent(CategoryFilter));
283  __COUT__ << str.str() << std::endl;
284  }
285  // else if(requestType == "PreviewEntry")
286  // {
287  // // cleanup temporary folder
288  // // NOTE: all input parameters for PreviewEntry will be attached to form
289  // // so use cgiIn(xxx) to get values.
290  // // increment number for each temporary preview, previewPostTempIndex_
291  // // save entry and uploads to previewPath / previewPostTempIndex_ /.
292 
293  // cleanUpPreviews();
294  // std::string EntryText = cgiIn("EntryText");
295  // __COUT__ << "EntryText " << EntryText << std::endl << std::endl;
296  // std::string EntrySubject = cgiIn("EntrySubject");
297  // __COUT__ << "EntrySubject " << EntrySubject << std::endl << std::endl;
298 
299  // // get creator name
300  // std::string creator = userInfo.username_;
301 
302  // savePostPreview(EntrySubject, EntryText, cgiIn.getFiles(), creator, &xmlOut);
303  // // else xmlOut.addTextElementToData(XML_STATUS,"Failed - could not get username
304  // // info.");
305  // }
306  // else if(requestType == "ApproveEntry")
307  // {
308  // // If Approve = "1", then previewed Log entry specified by PreviewNumber
309  // // is moved to logbook
310  // // Else the specified Log entry is deleted.
311  // std::string PreviewNumber = CgiDataUtilities::postData(cgiIn, "PreviewNumber");
312  // std::string Approve = CgiDataUtilities::postData(cgiIn, "Approve");
313 
314  // movePreviewEntry(PreviewNumber, Approve == "1", &xmlOut);
315  // }
316  // else if(requestType == "AdminRemoveRestoreEntry")
317  // {
318  // // if(userPermissions < ADMIN_PERMISSIONS_THRESHOLD)
319  // // {
320  // // xmlOut.addTextElementToData(XML_ADMIN_STATUS,"Error - Insufficient
321  // // permissions."); goto CLEANUP;
322  // // }
323 
324  // std::string EntryId = CgiDataUtilities::postData(cgiIn, "EntryId");
325  // bool Hide = CgiDataUtilities::postData(cgiIn, "Hide") == "1" ? true : false;
326 
327  // // get creator name
328  // std::string hider = userInfo.username_;
329 
330  // hideLogbookEntry(EntryId, Hide, hider);
331 
332  // xmlOut.addTextElementToData(XML_ADMIN_STATUS, "1"); // success
333  // }
334  else
335  __COUT__ << "requestType request not recognized." << std::endl;
336 } //end request()
337 
338 //==============================================================================
342 void ECLSupervisor::getCategories(HttpXmlDocument* xmlOut, std::ostringstream* out)
343 {
344  if(ECLUser_ == "" ||
345  ECLHost_ == "") //ignore ECL when environment variables are not set
346  {
347  __SS__ << "No ECL user/host specified for logbook access." << __E__;
348  __SS_THROW__;
349  }
350 
351  std::string response, url = "/A/xml_category_list";
352  eclConn_->Get(url, response);
353  __COUTTV__(response);
354 
355  std::vector<std::string> exps;
356  std::string name;
357  size_t after = 0;
358 
359  //example response:
360  // <?xml version="1.0" encoding="UTF-8"?>
361  // <category_list>
362  // <category path="Accelerator"/>
363  // <category path="CRV"/>
364  // <category path="CRV/Vertical Slice Test"/>
365  // <category path="Calorimeter"/>
366  // <category path="Cryogenics"/>
367  // <category path="Cryogenics/Cryogenics Construction"/>
368  // <category path="Cryogenics/Mu2e Controls"/>
369  // <category path="Cryogenics/Mu2e ODH System"/>
370  // <category path="Cryogenics/Mu2e Operations"/>
371  // <category path="Cryogenics/Mu2e Vacuum"/>
372  // <category path="Cryogenics/Muon Campus Operations"/>
373  // <category path="Extinction Monitor"/>
374  // <category path="Facilities / Building"/>
375  // <category path="Global Run"/>
376  // <category path="Leak checking"/>
377  // <category path="Mu2e team member location"/>
378  // <category path="Muon beamline"/>
379  // <category path="Planning"/>
380  // <category path="Production"/>
381  // <category path="Production/MDC18"/>
382  // <category path="Production/su2020"/>
383  // <category path="Safety"/>
384  // <category path="Solenoids"/>
385  // <category path="Stopping Target Monitor"/>
386  // <category path="TDAQ"/>
387  // <category path="Tracker"/>
388  // <category path="Tracker/VST"/>
389  // <category path="Transfer Lines"/>
390  // <category path="help"/>
391  // <category path="test"/>
392  // <category path="testbeam"/>
393  // </category_list>
394 
395  while((name = StringMacros::extractXmlField(
396  response, "category", 0, after, &after, "path=", "\"")) != "")
397  {
398  after +=
399  std::string("category").size(); //move forward to prepare for next search
400  __COUTTV__(name);
401  exps.push_back(name);
402  }
403 
404  // // check that category listing doesn't already exist
405  // HttpXmlDocument expXml;
406  // if(!expXml.loadXmlDocument((std::string)LOGBOOK_CATEGORY_LIST_PATH))
407  // {
408  // __COUT__ << "Fatal Error - Category database." << std::endl;
409  // __COUT__ << "Creating empty category database." << std::endl;
410 
411  // expXml.addTextElementToData((std::string)XML_CATEGORY_ROOT);
412  // expXml.saveXmlDocument((std::string)LOGBOOK_CATEGORY_LIST_PATH);
413  // return;
414  // }
415 
416  // expXml.getAllMatchingValues(XML_CATEGORY, exps);
417 
418  if(xmlOut)
419  {
420  xmlOut->addTextElementToData(XML_ACTIVE_CATEGORY, ECLCategory_);
421  xmlOut->addTextElementToData(XML_SAFE_URL, eclConn_->getSafeURL());
422  }
423 
424  for(unsigned int i = 0; i < exps.size(); ++i) // loop categories
425  {
426  if(xmlOut)
427  xmlOut->addTextElementToData(XML_CATEGORY, exps[i]);
428  if(out)
429  *out << exps[i] << std::endl;
430  }
431 } //end getCategories()
432 
433 //==============================================================================
437 void ECLSupervisor::webUserSetActiveCategory(std::string category,
438  HttpXmlDocument* xmlOut)
439 {
440  // no check, just set
441  ECLCategory_ = category;
442  if(xmlOut)
443  xmlOut->addTextElementToData(
444  XML_ADMIN_STATUS, "Active category set to " + category + " successfully.");
445 } //end webUserSetActiveCategory()
446 
447 //==============================================================================
453 void ECLSupervisor::refreshLogbook(time_t date,
454  size_t duration,
455  HttpXmlDocument* xmlOut,
456  std::ostringstream* out,
457  std::string categoryFilter)
458 {
459  if(ECLUser_ == "" ||
460  ECLHost_ == "") //ignore ECL when environment variables are not set
461  {
462  __SS__ << "No ECL user/host specified for logbook access." << __E__;
463  __SS_THROW__;
464  }
465 
466  if(categoryFilter == "")
467  categoryFilter = ECLCategory_; // default to active category
468  if(xmlOut)
469  xmlOut->addTextElementToData(XML_ACTIVE_CATEGORY, ECLCategory_); // for success
470  if(xmlOut)
471  xmlOut->addTextElementToData(XML_RESPONSE_CATEGORY,
472  categoryFilter); // for success
473 
474  int64_t mostRecentTime = 0;
475  time_t baseTime;
476 
477  __COUTTV__(date);
478  if(!date) // if date is 0 take most recent day and update it
479  baseTime = time(0); // / (60 * 60 * 24);
480  else
481  baseTime = date - timezoneHourOffset_ * 60 * 60 +
482  1; //date is 12:00a GMT, so could give wrong day in local timezone
483 
484  if(0) //test xml_get
485  {
486  std::string response, url = "/E/xml_get?e=" + std::string("1843");
487  __COUTV__(url);
488  eclConn_->Get(url, response);
489  __COUTV__(response);
490  }
491  if(0) //test xml_search
492  {
493  std::string response, url = "/E/xml_search?l=5"; //limit to 5
494  // url += "&c=Facilities / Building";
495  __COUTV__(url);
496  eclConn_->Get(url, response);
497  __COUTV__(response);
498  }
499  if(0) //test xml_search
500  {
501  std::string response, url = "/E/xml_search?l=5"; //limit to 5
502  url +=
503  "&c=" + StringMacros::encodeURIComponent("Facilities / Building"); //category
504  __COUTV__(url);
505  eclConn_->Get(url, response);
506  __COUTV__(response);
507  }
508  if(0) //test xml_search
509  {
510  std::string response, url = "/E/xml_search?l=5"; //limit to 5
511  url += "&c=" +
512  StringMacros::encodeURIComponent("Facilities &#46 Building"); //category
513  __COUTV__(url);
514  eclConn_->Get(url, response);
515  __COUTV__(response);
516  }
517 
518  //add all posts that match date/duration criteria
519  {
520  __COUTTV__(categoryFilter);
521  std::string response, url = "/E/xml_search?"; //l=100"; //limit to 100
522  // "&a=" + std::to_string(duration) + "days" + //after
523  // "&b=" + baseTimeTmBuffer; //before
524 
525  bool applyCategoryFilter = false;
526  bool applyInvertedCategoryFilter = false;
527  std::vector<std::string> acceptCategories;
528  //filter can start with * for all, or with ! for inverted selection (i.e. all without certain categories)
529  if(categoryFilter.size() && categoryFilter[0] != '*' &&
530  categoryFilter[0] != '!' && categoryFilter.find(',') == std::string::npos &&
531  categoryFilter.find(" / ") == std::string::npos)
532  url +=
533  "l=100&c=" + StringMacros::encodeURIComponent(categoryFilter); //category
534  else
535  {
536  if(duration > 14)
537  url += "l=1000"; //get more so better chance to find in filter
538  else
539  url += "l=300"; //get more so better chance to find in filter
540 
541  applyCategoryFilter =
542  (categoryFilter.size() && categoryFilter[0] != '*') ? true : false;
543  applyInvertedCategoryFilter =
544  (categoryFilter.size() && categoryFilter[0] == '!') ? true : false;
545  if(applyCategoryFilter)
546  {
547  if(applyInvertedCategoryFilter) //skip 1st char
548  acceptCategories =
549  StringMacros::getVectorFromString(categoryFilter.substr(1));
550  else
551  acceptCategories = StringMacros::getVectorFromString(categoryFilter);
552  __COUTTV__(StringMacros::vectorToString(acceptCategories));
553  }
554  __COUTTV__(applyInvertedCategoryFilter);
555  __COUTTV__(applyCategoryFilter);
556  }
557 
558  //apply date range
559  {
560  __COUTTV__(baseTime);
561  __COUTTV__(duration);
562 
563  if(TTEST(30)) //debug date
564  {
565  for(size_t i = 0; i < 24; ++i)
566  {
567  __COUTT__ << "i-: " << i << " "
568  << ((baseTime - i * 60 * 60) / (60 * 60 * 24));
569 
570  time_t modTime = baseTime - i * 60 * 60;
571  std::tm* baseTimeTm =
572  std::localtime(&modTime); // Convert to local time
573  char translatedDate[256];
574  strftime(
575  translatedDate, sizeof(translatedDate), "%Y-%m-%d", baseTimeTm);
576  __COUTTV__(translatedDate);
577  }
578 
579  for(size_t i = 0; i < 24; ++i)
580  {
581  __COUTT__ << "i+: " << i << " "
582  << ((baseTime + i * 60 * 60) / (60 * 60 * 24));
583  time_t modTime = baseTime + i * 60 * 60;
584  std::tm* baseTimeTm =
585  std::localtime(&modTime); // Convert to local time
586  char translatedDate[256];
587  strftime(
588  translatedDate, sizeof(translatedDate), "%Y-%m-%d", baseTimeTm);
589  __COUTTV__(translatedDate);
590  }
591  }
592 
593  // add one day to calculate before
594  baseTime += 1 * (60 * 60 * 24); //before is non-inclusive, after is inclusive
595  std::tm* baseTimeTm = std::localtime(&baseTime); // Convert to local time
596  char baseTimeTmBuffer[256];
597  strftime(baseTimeTmBuffer, sizeof(baseTimeTmBuffer), "%Y-%m-%d", baseTimeTm);
598  __COUTTV__(baseTimeTmBuffer);
599  url += "&b=" + std::string(baseTimeTmBuffer) + "+00:00:00"; //before
600 
601  //now calculate after from duration in days
602  baseTime -=
603  duration * (60 * 60 * 24); //before is non-inclusive, after is inclusive
604  baseTimeTm = std::localtime(&baseTime); // Convert to local time
605  strftime(baseTimeTmBuffer, sizeof(baseTimeTmBuffer), "%Y-%m-%d", baseTimeTm);
606  __COUTTV__(baseTimeTmBuffer);
607  url += "&a=" + std::string(baseTimeTmBuffer) + "+00:00:00"; //after
608  }
609  // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
610  eclConn_->Get(url, response);
611  __COUTVS__(3, response);
612 
613  //example response:
614  // <?xml version="1.0" encoding="UTF-8"?>
615  // <entry_list ids_only="False">
616 
617  // <entry
618  // id="2502"
619  // author="mu2e_ots"
620  // category="Global Run"
621  // timestamp="12/05/2024 17:49:53"
622  // html="yes"
623  // formatted="no"
624  // form="default"
625  // images="0"
626  // files="0">
627  // <text>Message: &amp;#010;Run stopped. Run &amp;apos;105214&amp;apos; duration so far of 00:11:57.35 seconds.&amp;#010;&amp;#010;This was a System Generated Log Entry from &amp;apos;Mu2e ot>
628  // <text-html><![CDATA[<pre class="html_safe_entry">Message: &#010;Run stopped. Run &apos;105214&apos; duration so far of 00:11:57.35 seconds.&#010;&#010;This was a System Generated Log Entry from &apos;Mu2e>
629  // <text-cdata><![CDATA[<pre class="html_safe_entry">Message: &#010;Run stopped. Run &apos;105214&apos; duration so far of 00:11:57.35 seconds.&#010;&#010;This was a System Generated Log Entry from &apos;Mu2>
630  // </entry>
631  // <entry
632  // id="2501"
633  // author="mu2e_ots"
634  // ...
635 
636  //and result to request is:
637  // <XML_LOGBOOK_ENTRY>
638  // <XML_LOGBOOK_ENTRY_TIME>
639  // <XML_LOGBOOK_ENTRY_CREATOR>
640  // <XML_LOGBOOK_ENTRY_SUBJECT>
641  // <XML_LOGBOOK_ENTRY_TEXT>
642  // <XML_LOGBOOK_ENTRY_FILE value=fileType0>
643  // <XML_LOGBOOK_ENTRY_FILE value=fileType1> ...
644  // </XML_LOGBOOK_ENTRY>
645 
646  std::string id, author, subject, category, timestamp, files, images, text;
647  size_t after = 0, before = -1, entryCount = 0, lastBefore = -1;
648  std::tm tm;
649  std::string preText, postText;
650 
651  while((id = //StringMacros::extractXmlField(response, "entry", 0, after, &after,
652  StringMacros::rextractXmlField(response,
653  "entry",
654  0,
655  before,
656  &before, //e.g. for reverse order
657  "id=",
658  "\"")) != "")
659  {
660  __COUTVS__(2, id);
661  ++entryCount;
662 
663  after = before;
664 
665  author = StringMacros::extractXmlField(
666  response, "entry", 0, after, nullptr, "author=", "\"");
667  subject = StringMacros::extractXmlField(
668  response, "entry", 0, after, nullptr, "subject=", "\"");
669  category = StringMacros::extractXmlField(
670  response, "entry", 0, after, nullptr, "category=", "\"");
671 
672  if(applyCategoryFilter)
673  {
674  bool found = applyInvertedCategoryFilter;
675  for(const auto& acceptCategory : acceptCategories)
676  if(category == acceptCategory ||
677  category.find(acceptCategory + "/") != std::string::npos)
678  {
679  found = !applyInvertedCategoryFilter;
680  break;
681  }
682 
683  if(!found)
684  {
685  __COUT_TYPE__(TLVL_DEBUG + 10)
686  << __COUT_HDR__ << "Skipping unaccepted category: " << category
687  << __E__;
688  lastBefore = before;
689  --before; //move back to prepare for next search
690  continue;
691  }
692  }
693 
694  timestamp = StringMacros::extractXmlField(
695  response, "entry", 0, after, nullptr, "timestamp=", "\"");
696  images = StringMacros::extractXmlField(
697  response, "entry", 0, after, nullptr, "images=", "\"");
698  files = StringMacros::extractXmlField(
699  response, "entry", 0, after, nullptr, "files=", "\"");
700 
701  __COUTVS__(2, author);
702  __COUTVS__(2, timestamp);
703  tm = {}; //clear
704  std::istringstream ss(timestamp);
705  ss >>
706  std::get_time(
707  &tm, "%m/%d/%Y %H:%M:%S"); // Parse the string into the tm structure
708  time_t t = std::mktime(&tm); // Convert tm structure to time_t
709 
710  __COUTVS__(2, t);
711  __COUTVS__(2, mostRecentTime);
712  if(!date && t > mostRecentTime)
713  mostRecentTime = t; //track most recent entry
714  __COUTVS__(2, mostRecentTime);
715 
716  size_t foundTextPos;
717  text = StringMacros::extractXmlField(response,
718  "pre class=\"html_safe_entry\"",
719  0,
720  after,
721  &foundTextPos,
722  "",
723  ">");
724  __COUTVS__(2, text.size());
725  if(text.size() == 0 || //if not found
726  foundTextPos > lastBefore) //or found in different entry!
727  {
728  text = StringMacros::extractXmlField(
729  response, "text", 0, after, nullptr, "", ">");
730  __COUTVS__(2, text.size());
731  }
732  __COUTVS__(3, text);
733  if(text.size() > std::string("Message: &#010;").size() && text[0] == 'M' &&
734  text[6] == 'e' && text[7] == ':' && text[9] == '&' && text[10] == '#')
735  text = text.substr(
736  std::string("Message: &#010;").size()); //skip Message header
737 
738  preText = "";
739  postText = "";
740  bool needAttachments = false;
741  if(atoi(images.c_str()))
742  {
743  needAttachments = true;
744  preText += "Attached Images: " + images + "<br>";
745  }
746  if(atoi(files.c_str()))
747  {
748  needAttachments = true;
749  preText += "Attached Files: " + files + "<br>";
750  }
751 
752  if(needAttachments)
753  {
754  std::string response, url = "/E/xml_get?e=" + id;
755  // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
756  eclConn_->Get(url, response);
757  __COUTVS__(3, response);
758 
759  size_t fileCount = atoi(files.c_str());
760  __COUTV__(fileCount);
761  size_t attachmentAfter = 0;
762 
763  if(fileCount)
764  postText += "<br><br>=====> Attached Files:";
765  for(size_t j = 0; j < fileCount; ++j)
766  {
767  __COUTTV__(attachmentAfter);
768  std::string furl = StringMacros::extractXmlField(response,
769  "attachment",
770  0,
771  attachmentAfter,
772  &attachmentAfter,
773  "url=",
774  "\"");
775 
776  __COUTVS__(2, furl);
777  std::string fname = StringMacros::extractXmlField(response,
778  "attachment",
779  0,
780  attachmentAfter,
781  nullptr,
782  "filename=",
783  "\"");
784 
785  __COUTVS__(2, fname);
786  postText += "<br>";
787  if(fileCount > 1)
788  postText += "Attached File #" + std::to_string(j + 1) + ": ";
789  postText +=
790  "<a target='_blank' href='" + furl + "'>" + fname + "</a>";
791  attachmentAfter += 20; //advance to next
792  }
793 
794  size_t imageCount = atoi(images.c_str());
795  __COUTVS__(3, imageCount);
796  attachmentAfter = 0;
797  if(imageCount)
798  postText += "<br><br>=====> Attached Images:";
799  for(size_t j = 0; j < imageCount; ++j)
800  {
801  __COUTTV__(attachmentAfter);
802  attachmentAfter = response.find("type=\"image\"", attachmentAfter);
803  if(attachmentAfter == std::string::npos)
804  break;
805  attachmentAfter = response.rfind("<attachment", attachmentAfter);
806  if(attachmentAfter == std::string::npos)
807  break;
808 
809  std::string furl = StringMacros::extractXmlField(response,
810  "attachment",
811  0,
812  attachmentAfter,
813  nullptr,
814  "full_url=",
815  "\"");
816 
817  __COUTVS__(2, furl);
818  std::string fname = StringMacros::extractXmlField(response,
819  "attachment",
820  0,
821  attachmentAfter,
822  nullptr,
823  "filename=",
824  "\"");
825 
826  __COUTVS__(2, fname);
827  postText += "<br>";
828  if(imageCount > 1)
829  postText += "Attached Image #" + std::to_string(j + 1) + ": ";
830  postText +=
831  "<a target='_blank' href='" + furl + "'>" + fname + "</a>";
832 
833  attachmentAfter += 50; //advance to next
834  }
835  } //end attachment handling
836 
837  // Lambda function to wrap links
838  auto wrapLinks = [](const std::string& inputText) -> std::string {
839  std::string result;
840  std::string::size_type pos = 0;
841  std::string::size_type start;
842 
843  while((start = inputText.find("http://", pos)) != std::string::npos ||
844  (start = inputText.find("https://", pos)) != std::string::npos)
845  {
846  // Append text before the link
847  result.append(inputText, pos, start - pos);
848 
849  // Find the end of the URL (stop at space or end of string)
850  std::string::size_type end =
851  inputText.find_first_of(" \t\n!<>(),\"\'", start);
852  if(end == std::string::npos)
853  {
854  end = inputText.size();
855  }
856 
857  // Extract the URL
858  std::string url = inputText.substr(start, end - start);
859 
860  // Append the <a> tag
861  result += "<a href=\"" + url + "\" target=\"_blank\">" + url + "</a>";
862 
863  // Move position to after the URL
864  pos = end;
865  }
866 
867  // Append any remaining text
868  result.append(inputText, pos, inputText.size() - pos);
869 
870  return result;
871  };
872 
873  text =
874  preText + (preText.size() ? "\n<br>" : "") + wrapLinks(text) + postText;
875  __COUTVS__(2, text);
876 
877  if(xmlOut)
878  {
879  auto entryParent = xmlOut->addTextElementToData(XML_LOGBOOK_ENTRY);
880 
881  xmlOut->addTextElementToParent(
882  XML_LOGBOOK_ENTRY_TIME, std::to_string(t), entryParent);
883  xmlOut->addTextElementToParent(
884  XML_LOGBOOK_ENTRY_CREATOR, author, entryParent);
885  xmlOut->addTextElementToParent(XML_LOGBOOK_ENTRY_TEXT, text, entryParent);
886  xmlOut->addTextElementToParent(
887  XML_LOGBOOK_ENTRY_SUBJECT,
888  category + " - entry #" + id + " - " + subject,
889  entryParent);
890  }
891 
892  lastBefore = before;
893  --before; //move back to prepare for next search
894  // after += std::string("entry").size(); //move forward to prepare for next search
895 
896  } //end primary entry extraction loop
897 
898  __COUTV__(entryCount);
899  } //end add all posts that match
900 
901  if(xmlOut)
902  xmlOut->addTextElementToData(XML_STATUS, "1"); // for success
903  if(out)
904  *out << __COUT_HDR_FL__ << "Today: " << time(0) / (60 * 60 * 24) << std::endl;
905 
906  if(TTEST(30))
907  {
908  __COUTTV__(mostRecentTime);
909  __COUTTV__(time(0));
910  __COUTTV__(time(0) - mostRecentTime);
911  __COUTTV__((time(0) - mostRecentTime) / (60 * 60 * 24));
912  __COUTTV__(timezoneHourOffset_);
913  for(size_t i = 0; i < 24; ++i)
914  __COUTT__ << "i: " << i << " "
915  << ((time(0) - i * 60 * 60 + timezoneHourOffset_ * 60 * 60) /
916  (60 * 60 * 24));
917  }
918 
919  if(xmlOut)
920  {
921  xmlOut->addNumberElementToData(XML_TIMEZONE_OFFSET, timezoneHourOffset_);
922 
923  if(0 &&
924  mostRecentTime) //always return 0 for ECL, because category filter may change, and may want live view of today..
925  {
926  int64_t mostRecentDayIndex =
927  (mostRecentTime + timezoneHourOffset_ * 60 * 60) / (60 * 60 * 24);
928  int64_t nowDayIndex =
929  (time(0) + timezoneHourOffset_ * 60 * 60) / (60 * 60 * 24);
930  if(TTEST(30))
931  {
932  __COUTTV__(mostRecentDayIndex);
933  __COUTTV__(nowDayIndex);
934  __COUTTV__(mostRecentTime);
935  __COUTTV__(((time(0) - mostRecentTime + timezoneHourOffset_ * 60 * 60) /
936  (60 * 60 * 24)));
937  }
938  __COUTTV__(nowDayIndex - mostRecentDayIndex);
939  xmlOut->addNumberElementToData(
940  XML_MOST_RECENT_DAY, //0 is today
941  nowDayIndex -
942  mostRecentDayIndex); // send most recent day index found in response
943  }
944  else
945  xmlOut->addNumberElementToData(XML_MOST_RECENT_DAY, 0);
946  }
947 
948 } //end refreshLogbook()
949 
950 //==============================================================================
954 xoap::MessageReference ECLSupervisor::MakeSystemLogEntry(xoap::MessageReference msg)
955 {
956  SOAPParameters parameters("EntryText");
957  parameters.addParameter("SubjectText");
958  SOAPUtilities::receive(msg, parameters);
959  std::string EntryText =
960  StringMacros::decodeURIComponent(parameters.getValue("EntryText"));
961  std::string SubjectText =
962  StringMacros::decodeURIComponent(parameters.getValue("SubjectText"));
963 
964  __COUT__ << "Received External Supervisor System Entry " << EntryText << std::endl;
965  __COUTV__(SubjectText);
966 
967  std::string retStr = "Success";
968 
969  if(ECLUser_ == "" ||
970  ECLHost_ == "") //ignore ECL when environment variables are not set
971  {
972  __COUT_INFO__ << "No ECL user/host specified for log entry: " << EntryText
973  << __E__;
974 
975  // fill return parameters
976  SOAPParameters retParameters("Status", retStr);
977 
978  return SOAPUtilities::makeSOAPMessageReference("SystemLogEntryStatusResponse",
979  retParameters);
980  }
981 
982  ECLEntry_t eclEntry;
983  eclEntry.author(StringMacros::escapeString(ECLUser_));
984  eclEntry.category(StringMacros::escapeString(ECLCategory_));
985  eclEntry.subject(StringMacros::escapeString(SubjectText));
986 
987  Form_t form;
988  Field_t field;
989  Form_t::field_sequence fields;
990  std::string users = theRemoteWebUsers_.getActiveUserList();
991 
992  form.name("default"); // these form names must be created in advance? ... default
993  // seems to have one field and be generic: 'text' field
994 
995  {
996  std::stringstream ss;
997  ss << "Message: " << __E__ << EntryText << __E__ << __E__;
998  ss << "This was a System Generated Log Entry from '" << CategoryName_
999  << "' at host '" << __ENV__("THIS_HOST") << "'" << __E__;
1000  ss << "Active ots users: " << users << __E__;
1001  ss << "USER_DATA: " << __ENV__("USER_DATA") << __E__;
1002  ss << "Uptime: "
1003  << StringMacros::getTimeDurationString(
1004  CorePropertySupervisorBase::getSupervisorUptime())
1005  << __E__;
1006  field = Field_t(StringMacros::escapeString(ss.str(), true /* keep white space */),
1007  "text");
1008  fields.push_back(field);
1009  }
1010 
1011  form.field(fields);
1012  eclEntry.form(form);
1013  try
1014  {
1015  // ECLConnection eclConn(ECLUser_, ECLPwd_, ECLHost_);
1016  if(!eclConn_->Post(eclEntry))
1017  {
1018  __COUT_ERR__ << "Failure to post ECL entry." << __E__;
1019  retStr = "Failure";
1020  }
1021  }
1022  catch(const std::runtime_error& e)
1023  {
1024  __SS__ << "Exception caught during Logbook ECL connection: " << e.what();
1025  __COUT_ERR__ << ss.str();
1026  retStr = ss.str();
1027  }
1028 
1029  // fill return parameters
1030  SOAPParameters retParameters("Status", retStr);
1031 
1032  return SOAPUtilities::makeSOAPMessageReference("SystemLogEntryStatusResponse",
1033  retParameters);
1034 } // end MakeSystemLogEntry()
Definition: ECL.hxx:495
Definition: ECL.hxx:386
Definition: ECL.hxx:436
virtual void forceSupervisorPropertyValues(void) override
override to force supervisor property values (and ignore user settings)
xoap::MessageReference MakeSystemLogEntry(xoap::MessageReference msg)
virtual void setSupervisorPropertyDefaults(void) override
override to control supervisor specific defaults
virtual void request(const std::string &requestType, cgicc::Cgicc &cgiIn, HttpXmlDocument &xmlOut, const WebUsers::RequestUserInfo &userInfo) override