o #¤2i×^ã@sÜdZddlZddlmZmZddlmZmZmZmZm Z m Z m Z m Z m Z mZmZmZddlmZddlmZmZmZddlmZddlmZdd lmZdd lmZdd lm Z dd l!m"Z"dd l#m$Z$Gdd„deƒZ%dS)zu Dashboard Widget for Python Scheduler Control panel style with critical jobs, active executions, and recent history éN)ÚdatetimeÚ timedelta) ÚQWidgetÚ QVBoxLayoutÚ QHBoxLayoutÚQLabelÚQFrameÚ QScrollAreaÚ QGroupBoxÚ QGridLayoutÚ QTableWidgetÚQTableWidgetItemÚ QPushButtonÚ QHeaderView)ÚQt)ÚQFontÚQColorÚQPalette)Ú CronTrigger)ÚIntervalTrigger)Ú DateTrigger)Úlogger)ÚSchedulerDatabase)ÚSchedulerConfig)Úconvert_unix_to_iso_dayofweekcsüeZdZdZdedef‡fdd„ Zdd„Zded ed e fd d „Z d d„Z dd„Z dd„Z dd„Zdd„Zdd„Zdd„Zdd„Zdd„Zdd „Zd!d"„Zd#d$„Zd%ed&efd'd(„Zd%ed&ed efd)d*„Zd+ed efd,d-„Zd.ed efd/d0„Zd1d2„Z‡ZS)3ÚDashboardWidgetz"Dashboard with control panel styleÚdbÚconfigcs:tƒ ¡||_||_| t ¡¡|_| ¡|  ¡dS)N) ÚsuperÚ__init__rrÚ_get_week_startrÚnowÚcurrent_week_startÚ _setup_uiÚrefresh)Úselfrr©Ú __class__©ú+c:\PYTHON\Scheduler\gui\dashboard_widget.pyrs  zDashboardWidget.__init__cCsXt|ƒ}| dddd¡| d¡tƒ}| dd¡|_tƒ|_|j |j¡| |jd¡| dd¡|_ tƒ|_ |j  |j ¡| |j d¡|  |¡| dd¡|_ t ƒ}| d ¡| tjj¡| d ¡tƒ}tƒ|_| |j¡| |¡tƒ}| |¡|j  |¡| |j d¡| d d ¡|_tƒ|_|j |j¡| |jd ¡| ¡dS)zSetup dashboard UIéu🔴 CRITICAL JOBSz#ffebeeéuâ–¶ï¸ ACTIVE NOWz#e8f5e9u📊 RECENT EXECUTIONSz#e3f2fdTi,u📅 WEEKLY TIMELINEz#fff3e0éN)rÚsetContentsMarginsZ setSpacingrÚ _create_panelZcritical_panelÚcritical_contentZ setLayoutÚ addWidgetZ active_panelÚactive_contentÚ addLayoutZ recent_panelr ZsetWidgetResizableZ setFrameShaperÚShapeZNoFrameZsetMaximumHeightrÚrecent_contentZ setWidgetZtimeline_panelÚtimeline_contentÚ addStretch)r%Ú main_layoutZtop_rowZ recent_scrollZrecent_containerZ panel_layoutr(r(r)r#&s>         zDashboardWidget._setup_uiÚtitleÚbg_colorÚreturncCst|ƒ}| d|›d¡|S)zCreate a styled panela  QGroupBox { font-size: 14px; font-weight: bold; border: 2px solid #ccc; border-radius: 8px; margin-top: 10px; padding: 15px; background-color: z¬; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } )r Ú setStyleSheet)r%r8r9Zpanelr(r(r)r.[s  øzDashboardWidget._create_panelc Cs^z| ¡| ¡| ¡| ¡WdSty.}zt d|›¡WYd}~dSd}~ww)zRefresh dashboard datazError refreshing dashboard: N)Ú_refresh_critical_jobsÚ_refresh_active_jobsÚ_refresh_recent_executionsÚ_refresh_weekly_timelineÚ ExceptionrÚerror)r%Úer(r(r)r$ss€ÿzDashboardWidget.refreshcCsŒ| |j¡|jjdd}dd„|Dƒ}|s&tdƒ}| d¡|j |¡n|D]}td|d›ƒ}| d ¡|j |¡q(|j ¡d S) zRefresh critical jobs sectionT©Ú enabled_onlycSsg|] }|ddkr|‘qS)Ú criticalityÚcriticalr()Ú.0Újr(r(r)Ú „sz:DashboardWidget._refresh_critical_jobs..zNo critical jobs configuredú color: #666; font-style: italic;uâ— Únamez3font-size: 12px; color: #c62828; font-weight: bold;N)Ú _clear_layoutr/rÚ get_all_jobsrr;r0r6)r%Zall_jobsZ critical_jobsÚlabelÚjobÚ job_labelr(r(r)r<}s   z&DashboardWidget._refresh_critical_jobscCs¢| |j¡|j ¡}|stdƒ}| d¡|j |¡n-|D]*}td|d›ƒ}| d¡|j |¡td|d›ƒ}| d¡|j |¡q|j ¡d S) zRefresh active jobs sectionzNo jobs runningrJuâ–¶ Újob_namez3font-size: 12px; color: #2e7d32; font-weight: bold;z Status: Ústatuszfont-size: 10px; color: #666;N)rLr1rÚget_active_executionsrr;r0r6)r%ZactiverNÚ executionrPÚ status_labelr(r(r)r=’s      z$DashboardWidget._refresh_active_jobscCsî| |j¡|j dd¡}|jj|d}|s&tdƒ}| d¡|j |¡nÊ|D]Ç}t ƒ}|  t j j t j jB¡| d¡| d¡t|ƒ}| dd dd ¡|d rZ|d d d …nd }t|ƒ}| d¡| d¡| |¡| |d¡} t| ƒ} |  d¡| | ¡t|dƒ} |  d¡| | d¡|dr¢|dd›d} nd } t| ƒ} |  d¡|  tjj¡|  d¡| | ¡|dd urÊt|dƒnd }td|›ƒ}| d¡| tjj¡| d¡| |¡|j |¡q(|j ¡d S)z!Refresh recent executions sectionzgui.dashboard_recent_jobsr*)ÚlimitzNo recent executionsrJr+z:background-color: white; border-radius: 4px; padding: 5px;é éÚ start_timeNéúN/Aé–z(font-family: monospace; font-size: 11px;rRérQz#font-size: 11px; font-weight: bold;Úduration_secondsz.1fÚsé<Ú exit_codezExit: )rLr4rÚgetrÚget_recent_executionsrr;r0rZ setFrameStyler3ZBoxZShadowZPlainZ setLineWidthrr-Ú setFixedWidthÚ_get_status_iconÚ setAlignmentrÚ AlignmentFlagZ AlignRightÚstrr6)r%rVZrecentrNrTZ exec_frameZ exec_layoutÚtime_strÚ time_labelZ status_iconrUZ name_labelZ duration_strZduration_labelZ exit_code_strZ exit_labelr(r(r)r>«sT                  z*DashboardWidget._refresh_recent_executionscCs’d}|j ¡dkr|j d¡ ¡}t|tƒr| ¡ ¡}| |j¡t ƒ}t dƒ}|  d¡|j   |j¡| |¡|jtdd}t|j d¡›d| d ¡›ƒ}| tjj¡| d ¡| |d¡t d ƒ}|  d¡|j   |j¡| |¡t d ƒ}|  d¡|j   |j¡| |¡|j |¡tƒ} |  d ¡|  d¡gd¢} |  | ¡d} tdƒD]} | | d} | dd}| d›d|d›}|   | t!|ƒ¡q°| "¡}| #| |¡|  $d¡|  %tj&j'¡|  (tj)j*¡|  +d¡|  ,tj-j.¡|  /tj-j.¡|  0tj1j2¡|  3d¡|  4tj5j6¡|  7¡ 8d¡|  7¡ 9d¡td ƒD] }|  :¡ ;|t¡q%|j | ¡|durG|  ¡ ?|¡dSdS)z%Refresh weekly timeline calendar viewNr+uâ—€ Prevr`é©Údaysz%d %bú - z%d %b %Yz#font-weight: bold; font-size: 12px;ZTodayuNext â–¶rXrZ)ZMonZTueZWedZThuZFrié r,r]Z02dú:TFi^é)@r5ÚcountZitemAtÚwidgetÚ isinstancer ZverticalScrollBarÚvaluerLrrrdÚclickedÚconnectÚ _prev_weekr0r"rrÚstrftimerfrrgÚ AlignCenterr;Ú_reset_to_current_weekÚ _next_weekr2ZsetColumnCountZ setRowCountZsetHorizontalHeaderLabelsÚrangeZsetVerticalHeaderItemr Ú_get_week_executionsÚ_populate_timeline_tableZsetAlternatingRowColorsZsetEditTriggersZ EditTriggerZNoEditTriggersZsetSelectionModeZ SelectionModeZ NoSelectionZ setAutoScrollZsetVerticalScrollModeZ ScrollModeZScrollPerPixelZsetHorizontalScrollModeZsetSizeAdjustPolicyZSizeAdjustPolicyZAdjustToContentsOnFirstShowZsetFixedHeightZsetFocusPolicyZ FocusPolicyZNoFocusZverticalHeaderZsetDefaultSectionSizeZ setVisibleZhorizontalHeaderZsetSectionResizeModerZ ResizeModeZStretchZsetValue)r%Z scroll_posZ old_tableZ nav_layoutZprev_btnÚweek_endZ week_labelZ today_btnZnext_btnÚtableÚheadersZ start_hourÚrowÚhourÚminuterjÚ executionsÚcolr(r(r)r?ìsn                          ÿz(DashboardWidget._refresh_weekly_timelinecCs |t| ¡djdddddS)z%Get Monday of the week for given daterlr)r„r…ÚsecondÚ microsecond)rÚweekdayÚreplace)r%Údater(r(r)r Ks zDashboardWidget._get_week_startcCs |jtdd8_| ¡dS)zNavigate to previous weekérlN©r"rr?©r%r(r(r)rxOó zDashboardWidget._prev_weekcCs |jtdd7_| ¡dS)zNavigate to next weekrrlNrŽrr(r(r)r|TrzDashboardWidget._next_weekcCs| t ¡¡|_| ¡dS)zReset to current weekN)r rr!r"r?rr(r(r)r{Ys z&DashboardWidget._reset_to_current_weekc CsôzÛddl}| d¡}| |j¡}|tdd}|jjdd}g}|D]¶}|dd kr+q"|d d kr2q"| d ¡dur:q"zt|d t ƒrIt   |d ¡n|d }Wnt ym} zt  d|d›d| ›¡WYd} ~ q"d} ~ ww| |d|¡} | syq"d} |} d} d}|| krØz|  | | ¡}Wnt y¬} zt  d|d›d| ›¡WYd} ~ n0d} ~ ww|r³||kr´n$| ||d|d|d |d dœ¡|} |tdd} |d7}|| ks…q"|WSt yù} zt jd| ›ddgWYd} ~ Sd} ~ ww)z2Calculate all schedule executions for current weekrNú Europe/RomerrlTrCÚ schedule_typeÚmanualÚ target_typeÚ sync_profileÚ target_nameÚschedule_configz-Failed to parse schedule config for schedule Úidú: ièz(Error calculating next_run for schedule rK)rÚ schedule_idÚ schedule_namer”r–r+)ÚsecondszError in _get_week_executions: )Úexc_info)ÚpytzÚtimezoneÚlocalizer"rrÚget_all_schedulesrbrtrhÚjsonÚloadsr@rrAÚ_build_trigger_for_timelineÚget_next_fire_timeÚappend)r%ržÚtzZ week_startr€Z schedulesr†ÚschedulerrBÚtriggerZpreviousr!Zmax_iterationsZ iterationsZnext_runr(r(r)r~^sp    ( €þ €þ û쀀þz$DashboardWidget._get_week_executionsc Cs¼zBd}|dkr| d¡}|sWdSt|ƒ}tj||dWS|dkr+td d|i|¤ŽWS|dkr@| d¡}|s9WdSt||d WSWdSty]}zt d |›¡WYd}~dSd}~ww) z3Build APScheduler trigger for timeline calculationsr‘ÚcronN©rŸÚintervalrŸrŒÚrun_date©r­rŸzCould not build trigger: r() rbrrÚ from_crontabrrr@rÚwarning)r%r’rr§Ú cron_exprZ cron_expr_isor­rBr(r(r)r¤§s*  €þz+DashboardWidget._build_trigger_for_timelinec Cs|i}|D]U}|d}|jr| ¡jdd}| ¡}|j}|j}|dks3|dks3|dkr/|dks3|dkr4q|dd|d kr?d nd} | } |} | | f} | |vrRg|| <||  |¡q| ¡D]¹\\} } } t| ƒ}|d kryt | dd dd …ƒ}nt |›d ƒ}g}| D]}|d  d¡}| |›d|d ›d|d›d¡q„|  d  |¡¡|  tjj¡tdd„| Dƒƒ}tdd„| Dƒƒ}tdd„| Dƒƒ}|dkrÞ|dkrÞ|dkrÞ| tdƒ¡n2|dkrò|dkrò|dkrò| tdƒ¡n|dkr |dkr |dkr | tdƒ¡n| tdƒ¡| | | |¡q^tdƒD]} tdƒD]} | | | ¡dur8t dƒ}| | | |¡q"qdS)z'Populate timeline table with executionsrN)ÚtzinforoérrXr,r]r+r›éz schedulesz%H:%Mrnz (r–ú)Ú csó |] }|ddkrdVqdS)r”Úbackup_profiler+Nr(©rGÚexr(r(r)Ú öó€z;DashboardWidget._populate_timeline_table..csr·)r”rOr+Nr(r¹r(r(r)r»÷r¼csr·)r”Úgroupr+Nr(r¹r(r(r)r»ør¼z#bbdefbz#c8e6c9z#e1bee7z#fff9c4rZÚ)r²Ú astimezoner‹rŠr„r…r¦ÚitemsÚlenr ryZ setToolTipÚjoinZsetTextAlignmentrrgrzÚsumZ setBackgroundrZsetItemr}Úitem)r%rr†ÚslotsrTZexec_dtZ day_of_weekr„r…Z slot_indexrƒr‡ÚkeyZexecsrrrÄZ tooltip_linesrºriÚ backup_countÚ job_countÚ group_countr(r(r)r¿s^(&  €ýÿz(DashboardWidget._populate_timeline_tabler’r—c Csøz`d}|dkr| d¡}|sWdStj||d}n<|dkr'td d|i|¤Ž}n.|dkrR| d¡}|s5WdSt|tƒrKzt |¡}Wn tyJYnwt ||d }nWdS|j dt  |j ¡d WSty{}zt  d |›¡WYd}~dSd}~ww) z*Calculate next run datetime for a scheduler‘rªNr«r¬rŸrŒr­r®)Zprevious_fire_timer!zCould not compute next run: r()rbrr¯rrtrhrÚ fromisoformatr@rr¥r!rŸrr°)r%r’r—r§r±r©r­Úexcr(r(r)Ú_calculate_next_runs6    ÿ€þz#DashboardWidget._calculate_next_runcCs˜|dkr d| dd¡›S|dkr5g}dD]}| |¡}|r(| |›d|›¡q|r3dd |¡›Sd S|d krBd | d d¡›S|d krHdSt|ƒS)z#Human-readable schedule descriptionrªzCron: r[r¬)ÚweeksrmÚhoursÚminutesrœú zEvery z, zInterval schedulerŒz One-time: r­r“zManual execution)rbr¦rÂrh)r%r’r—ÚpartsrÆrur(r(r)Ú_format_schedule_text-s €z%DashboardWidget._format_schedule_textr¨cCsN| d¡p d| d¡›}ddddœ}| | d¡| dd ¡¡}|›d |›S) zLabel target with its typer–zID Ú target_idZJobZGroupZBackup)rOr½r¸r”ZTargetr™©rb)r%r¨r–Ztype_mapÚprefixr(r(r)Ú_format_target>sýzDashboardWidget._format_targetrRcCsdddddddœ}| |d¡S) zGet status icon for executionu✅uâŒuâ–¶ï¸uâ±ï¸uâ›”uâ³)Ú completedZfailedÚrunningÚtimeoutÚ cancelledZqueueduâ“rÔ)r%rRZiconsr(r(r)reIsú z DashboardWidget._get_status_iconcCsN| ¡r%| d¡}| ¡r| ¡ ¡n | ¡r| | ¡¡| ¡sdSdS)zClear all widgets from layoutrN)rrZtakeAtrsZ deleteLaterÚlayoutrL)r%rÛrÄr(r(r)rLUs ûzDashboardWidget._clear_layout)Ú__name__Ú __module__Ú __qualname__Ú__doc__rrrr#rhr r.r$r<r=r>r?r rxr|r{r~r¤rÚdictrÌrÒrÖrerLÚ __classcell__r(r(r&r)rs, 5 A_IQ  r)&rßr¢rrÚPyQt6.QtWidgetsrrrrrr r r r r rrÚ PyQt6.QtCorerÚ PyQt6.QtGuirrrZapscheduler.triggers.cronrZapscheduler.triggers.intervalrZapscheduler.triggers.daterÚlogururÚ core.databaserÚ core.configrZcore.cron_utilsrrr(r(r(r)Ús8