o â¯2izã@s¨dZddlmZmZmZmZmZmZmZm Z m Z m Z m Z m Z mZmZmZmZmZmZmZmZddlmZddlmZddlZddlmZddlmZGdd „d eƒZdS) z‚ Schedule Editor Dialog for Python Scheduler Create and edit schedules for individual jobs or groups (with inline group creation) é)ÚQDialogÚ QVBoxLayoutÚ QHBoxLayoutÚ QFormLayoutÚQLabelÚ QLineEditÚ QTextEditÚ QComboBoxÚ QPushButtonÚ QMessageBoxÚ QGroupBoxÚ QRadioButtonÚ QButtonGroupÚQStackedWidgetÚ QListWidgetÚQListWidgetItemÚQAbstractItemViewÚ QCheckBoxÚQSpinBoxÚQWidget)ÚQt)ÚloggerN)ÚSchedulerDatabase)ÚSchedulerConfigc sÎeZdZdZd.dedededef‡fdd„ Zd d „Zd d „Z d d„Z dd„Z dd„Z dd„Z dd„Zdefdd„Zdd„Zdd„Zdd„Zd d!„Zd"d#„Zd$d%„Zd&efd'd(„Zd)efd*d+„Zd,d-„Z‡ZS)/ÚScheduleEditorDialogz6Dialog for creating/editing schedules (jobs or groups)NÚdbÚconfigÚ schedule_idÚpre_selected_job_idcsvtƒ |¡||_||_||_||_|du|_| |jrdnd¡| dd¡|  d¡|  ¡|jr9|  ¡dSdS)Nz Edit Schedulez New Schedulei¼iXT) ÚsuperÚ__init__rrrrÚ is_edit_modeÚsetWindowTitleÚsetMinimumSizeÚsetModalÚ _setup_uiÚ_load_schedule_data)ÚselfÚparentrrrr©Ú __class__©ú1c:\PYTHON\Scheduler\gui\schedule_editor_dialog.pyr s     ÿzScheduleEditorDialog.__init__c&CsÚt|ƒ}tdƒ}tƒ}tƒ|_|j d¡| d|j¡tƒ|_|j d¡|j  d¡| d|j¡|  |¡|  |¡tdƒ}tƒ}t ƒ}t |ƒ|_tdƒ|_|j d ¡|jj |j¡|j |jd ¡|  |j¡td ƒ|_|jj |j¡|j |jd ¡|  |j¡td ƒ|_|jj |j¡|j |jd¡|  |j¡tdƒ|_|jj |j¡|j |jd¡|  |j¡| ¡| |¡tƒ|_tƒ}t|ƒ}tƒ|_|j d¡|jj  |j!¡|  t"dƒ¡|  |j¡| ¡|j  |¡tƒ} t| ƒ} t ƒ} t |ƒ|_#tdƒ|_$|j$ d ¡|j$j |j%¡|j# |j$d ¡|   |j$¡tdƒ|_&|j&j |j%¡|j# |j&d ¡|   |j&¡|  | ¡tƒ|_'tƒ} t| ƒ} tƒ|_(|j( d¡|   t"dƒ¡|   |j(¡|  ¡|j'  | ¡tƒ}t|ƒ}tƒ|_)|j) d¡|  t"dƒ¡|  |j)¡t ƒ}|  t"dƒ¡tƒ|_*|j* +gd¢¡|  |j*¡| ¡| |¡|  t"dƒ¡t,ƒ|_-|j- .t/j0j1¡|j-  d¡|  |j-¡t ƒ}t2dƒ|_3|j3j4 |j5¡|  |j3¡t2dƒ|_6|j6j4 |j7¡|  |j6¡| ¡| |¡|  t"dƒ¡t,ƒ|_8|j8  d¡|  |j8¡t ƒ}t2d ƒ|_9|j9j4 |j:¡|  |j9¡t2d!ƒ|_;|j;j4 |j<¡|  |j;¡| ¡| |¡|j'  |¡|   |j'¡|j  | ¡tƒ}t|ƒ}tƒ|_=|j= d"¡|  t"d#ƒ¡|  |j=¡| ¡|j  |¡tƒ}t|ƒ}tƒ|_>|j> d$¡|  t"d%ƒ¡|  |j>¡| ¡|j  |¡|  |j¡|  |¡|  |¡td&ƒ}tƒ}t ƒ}|  t"d'ƒ¡tƒ|_?|j? +gd(¢¡|j?j@ |jA¡|  |j?¡| ¡| |¡tƒ|_Btƒ}t|ƒ}tƒ|_C|jC d)¡|  t"d*ƒ¡|  |jC¡t"d+ƒ}| Dd,¡|  |¡t"d-ƒ}| Ed ¡| Dd.¡|  |¡| ¡|jB  |¡tƒ}t|ƒ}tFƒ|_G|jG Hd ¡|jG Id/¡| d0|jG¡tFƒ|_J|jJ Hd ¡|jJ Id1¡| d2|jJ¡tFƒ|_K|jK Hd ¡|jK Id3¡| d4|jK¡tFƒ|_L|jL Hd ¡|jL Id5¡| d6|jL¡tFƒ|_M|jM Hd ¡|jM Id5¡| d7|jM¡t"d8ƒ} |  Dd,¡|  Nd ¡| d9| ¡|jB  |¡tƒ}!t|!ƒ}"t"d:ƒ}#|# Dd;¡|# Nd ¡|"  |#¡|" ¡|jB  |!¡|  |jB¡tOd<ƒ|_P|jP d ¡|  |jP¡|  |¡|  |¡t ƒ}$|$ ¡t2|jQr.d=nd>ƒ|_R|jR Dd?¡|jRj4 |jS¡|$  |jR¡t2d@ƒ}%|%j4 |jT¡|$  |%¡| |$¡| U¡| V¡| W¡| X¡dAS)BzSetup dialog UIzBasic Informationze.g., Daily ETL PipelinezSchedule Name:zOptional description...é<z Description:zWhat to ExecutezExecute Single JobTrzExecute Job Group (Chain)ézExecute Backup ProfileézExecute Sync ProfileézSelect a job...z Select Job:zUse Existing GroupzCreate New GroupzSelect a group...z Select Group:ze.g., Data Processing Chainz Group Name:zError Handling:)Ú stop_on_errorZcontinue_on_errorZskip_remainingzAvailable Jobs:éduAdd Selected ↓uRemove Selected ↑z Jobs in Group (execution order):u Move Up ↑u Move Down ↓zSelect a backup profile...zSelect Backup Profile:zSelect a sync profile...zSelect Sync Profile:zWhen to ExecutezSchedule Type:©ÚcronÚintervalÚmanualz$e.g., 0 2 * * * or 30 14 * * mon-frizCron Expression:u³Format: minute hour day month day_of_week Examples: • 0 8 * * * = Daily at 8:00 AM • 30 14 * * mon-fri = Weekdays at 2:30 PM • 0 9 * * mon,wed,fri = Mon/Wed/Fri at 9:00 AMzcolor: #666; font-size: 10px;zICron Expression Helper (crontab.guru)z color: #2196F3; font-size: 10px;é4zWeeks:imzDays:ézHours:é;zMinutes:zSeconds:z{Specify the interval duration. At least one value must be non-zero. Example: 2 weeks = execute every 2 weeks from first runÚz[Manual execution only - no automatic scheduling. Use 'Run Now' button to execute on demand.z color: #666; font-style: italic;z)Enabled (schedule will run automatically)ÚSaveÚCreatezNbackground-color: #4CAF50; color: white; padding: 8px 20px; font-weight: bold;ÚCancelN)Yrr rrÚ name_inputÚsetPlaceholderTextÚaddRowrÚdescription_inputÚsetMaximumHeightÚ setLayoutÚ addWidgetrrZtarget_button_groupr Ú job_radioÚ setCheckedZtoggledÚconnectÚ_on_target_type_changedZ addButtonÚ group_radioÚ backup_radioÚ sync_radioÚ addStretchÚ addLayoutrÚ target_stackrr Ú job_comboZcurrentIndexChangedÚ_on_job_selectedrZgroup_selection_button_groupÚexisting_group_radioÚ_on_group_selection_changedZnew_group_radioÚ group_stackÚexisting_group_comboÚgroup_name_inputÚgroup_error_comboÚaddItemsrÚavailable_jobs_listÚsetSelectionModerÚ SelectionModeZMultiSelectionr Zadd_to_group_btnÚclickedÚ_add_jobs_to_groupZremove_from_group_btnÚ_remove_jobs_from_groupÚgroup_jobs_listZ move_up_btnÚ _move_job_upZ move_down_btnÚ_move_job_downÚ backup_comboÚ sync_comboÚschedule_type_comboÚcurrentTextChangedÚ_on_schedule_type_changedÚschedule_config_stackÚ cron_inputÚ setStyleSheetÚsetOpenExternalLinksrÚinterval_weeksÚ setMinimumÚ setMaximumÚ interval_daysÚinterval_hoursÚinterval_minutesÚinterval_secondsZ setWordWraprÚenabled_checkboxr!Zsave_btnÚ_save_scheduleÚrejectÚ _load_jobsÚ _load_groupsÚ_load_backup_profilesÚ_load_sync_profiles)&r'ÚlayoutÚ basic_groupÚ basic_layoutZ target_groupZ target_layoutZ radio_layoutZjob_pageZjob_page_layoutZ group_pageZgroup_page_layoutZgroup_radio_layoutZexisting_group_widgetZexisting_group_layoutZnew_group_widgetZnew_group_layoutZ error_layoutZadd_remove_layoutZ order_layoutZ backup_pageZbackup_page_layoutZ sync_pageZsync_page_layoutÚschedule_groupÚschedule_layoutZ type_layoutZ cron_widgetZ cron_layoutÚ cron_helpZ cron_linkZinterval_widgetZinterval_layoutZ interval_helpZ manual_widgetZ manual_layoutZ manual_labelZactions_layoutZ cancel_btnr+r+r,r%*s¦                                                                                                  zScheduleEditorDialog._setup_uic CsÆzG|j ¡}|D] }|j |d|d¡q|D] }t|d›d|d›dƒ}| tjj|d¡|j  |¡q|j rE|  |j ¡WdSWdSt yb}zt  d|›¡WYd}~dSd}~ww)zLoad all jobs for selectionÚnameÚidz (ID: ú)zError loading jobs: N)rÚ get_all_jobsrOÚaddItemrÚsetDatarÚ ItemDataRoleÚUserRolerXrÚ_pre_populate_from_jobÚ ExceptionrÚerror)r'ÚjobsÚjobÚitemÚer+r+r,rt`s ÿ€ÿzScheduleEditorDialog._load_jobsc CsÄzF|j ¡}| d¡}dd„| ¡Dƒ}Wdƒn1swY|D]}|j |d¡}|j |d›dt|ƒ›d|d¡q&WdStya}zt   d |›¡WYd}~dSd}~ww) zLoad existing groupsz&SELECT * FROM job_groups ORDER BY namecSsg|]}t|ƒ‘qSr+)Údict)Ú.0Úrowr+r+r,Ú {sz5ScheduleEditorDialog._load_groups..Nrr~z (z jobs)zError loading groups: ) rÚ_get_connectionÚexecuteÚfetchallÚget_job_group_membersrTr‚Úlenr‡rrˆ)r'ÚconnÚcursorÚgroupsÚgroupÚmembersrŒr+r+r,ruvs   þþþ€ÿz!ScheduleEditorDialog._load_groupsc Csžz3|j ¡}|D]}|dr|j |d|d¡q|j ¡dkr1|j dd¡|j d¡WdSWdStyN}zt d|›¡WYd}~dSd}~ww) z"Load backup profiles for selectionÚenabledr~rrú(No enabled profiles)NFzError loading backup profiles: ) rÚget_all_backup_profilesrar‚ÚcountÚ setEnabledr‡rrˆ©r'ZprofilesZprofilerŒr+r+r,rv‡s €þ€ÿz*ScheduleEditorDialog._load_backup_profilesc CsÎz>|j ¡|j ¡}|D]}|dr|j |d|d¡q |j ¡dkr6|j dd¡|j d¡WdS|j d¡WdStyf}zt  d |›¡|j d d¡|j d¡WYd}~dSd}~ww) z Load sync profiles for selectionr›r~rrrœNFTzError loading sync profiles: z(Error loading profiles)) rbÚclearrÚget_all_sync_profilesr‚ržrŸr‡rrˆr r+r+r,rw—s"  €€ýz(ScheduleEditorDialog._load_sync_profilescCsl|j ¡r |j d¡dS|j ¡r|j d¡dS|j ¡r'|j d¡dS|j ¡r4|j d¡dSdS)zHandle target type changerr.r/r0N)rEÚ isCheckedrNÚsetCurrentIndexrIrJrK©r'r+r+r,rH¬s    ÿz,ScheduleEditorDialog._on_target_type_changedcCs*|j ¡r |j d¡dS|j d¡dS)z"Handle group selection type changerr.N)rQr£rSr¤r¥r+r+r,rR·s z0ScheduleEditorDialog._on_group_selection_changedÚ schedule_typecCsfddddœ}| |d¡}t d|›d|›¡|j |¡|j ¡ ¡t d|j ¡jj›¡dS) zHandle schedule type changerr.r/r3zSchedule type changed to 'z', switching to stack index zCurrent widget: N) ÚgetrÚdebugrfr¤Ú currentWidgetZupdateGeometryr*Ú__name__)r'r¦Útype_mapÚindexr+r+r,re¾s   z.ScheduleEditorDialog._on_schedule_type_changedcCs0|js|jrdS|j ¡}|r| |¡dSdS)z?When job is selected from combo, pre-populate schedule settingsN)r!rrOÚ currentDatar†)r'Újob_idr+r+r,rPÈs  ÿz%ScheduleEditorDialog._on_job_selectedcCój|j ¡}|s dS|D]'}t| ¡ƒ}| tjj| tjj¡¡|j   |¡|j  |¡}|j  |¡q dS)zAdd selected jobs to groupN) rXÚ selectedItemsrÚtextrƒrr„r…Údatar^r‚rÚtakeItem©r'Zselected_itemsr‹Znew_itemrr+r+r,r\Òó    úz'ScheduleEditorDialog._add_jobs_to_groupcCr¯)zRemove selected jobs from groupN) r^r°rr±rƒrr„r…r²rXr‚rr³r´r+r+r,r]àrµz,ScheduleEditorDialog._remove_jobs_from_groupcCsH|j ¡}|dkr"|j |¡}|j |d|¡|j |d¡dSdS)zMove selected job uprr.N)r^Ú currentRowr³Ú insertItemÚ setCurrentRow©r'Z current_rowr‹r+r+r,r_îs  ýz!ScheduleEditorDialog._move_job_upcCs^|j ¡}||j ¡dkr+|dkr-|j |¡}|j |d|¡|j |d¡dSdSdS)zMove selected job downr.rN)r^r¶ržr³r·r¸r¹r+r+r,r`ös  ýz#ScheduleEditorDialog._move_job_downc CsŒz|j |j¡}|st |dd|j›d¡| ¡WdS|j |d¡|dr2|j  |d¡|ddkrQ|j   d ¡|j   |d ¡}|d krP|j  |¡n\|dd krp|j  d ¡|j  |d ¡}|d kro|j |¡n=|dd kr|j  d ¡|j  |d ¡}|d krŽ|j |¡n|j  d ¡|j  d ¡|j  |d ¡}|d kr­|j |¡|j |d¡t|dtƒrÃt |d¡n|d}|ddkrØ|j | dd¡¡n9|ddkr|j | dd ¡¡|j  | dd ¡¡|j! | dd ¡¡|j" | dd ¡¡|j# | dd ¡¡|j$  |d¡WdSt%yE}zt& 'd|›¡t (|dd|›¡| ¡WYd}~dSd}~ww)z'Load existing schedule data for editingÚErrorz Schedule z not foundNr~Ú descriptionÚ target_typerŠTÚ target_idrÚbackup_profileÚ sync_profiler¦Úschedule_configr4r:r5ÚweeksÚdaysÚhoursÚminutesÚsecondsr›zError loading schedule data: zFailed to load schedule: ))rÚ get_schedulerr Úwarningrsr>ÚsetTextrAÚ setPlainTextrErFrOÚfindDatar¤rJrarKrbrIrQrTrcÚsetCurrentTextÚ isinstanceÚstrÚjsonÚloadsrgr§rjÚsetValuermrnrorprqr‡rrˆÚcritical)r'Úscheduler¬rrŒr+r+r,r&þsb   €   €   €   $ €ýz(ScheduleEditorDialog._load_schedule_dataÚreturncCsÐ|j ¡ ¡st |dd¡|j ¡dS|j ¡r+|j  ¡dkr*t |dd¡dSnp|j  ¡rH|j   ¡dks>|j   ¡durGt |dd¡dSnS|j  ¡re|j  ¡dks[|j  ¡durdt |dd¡dSn6|j ¡r{|j  ¡dkrzt |dd ¡dSn |j ¡ ¡s‹t |dd ¡dS|j ¡dkr›t |dd ¡dS|j ¡}|d kr¶|j ¡ ¡s´t |dd ¡dSdS|dkræ|j ¡dkræ|j ¡dkræ|j ¡dkræ|j ¡dkræ|j ¡dkræt |dd¡dSdS)zValidate form inputsúValidation ErrorzSchedule name is requiredFrzPlease select a jobNzPlease select a backup profileúPlease select a sync profilezPlease select a groupzGroup name is requiredz(Please add at least one job to the groupr4zCron expression is requiredr5z*Please specify at least one interval valueT)r>r±Ústripr rÇÚsetFocusrEr£rOZ currentIndexrJrar­rKrbrQrTrUr^ržrcÚ currentTextrgrjÚvaluermrnrorp)r'r¦r+r+r,Ú_validate_inputs7sb  þ þ þ þ  ÷ ÿ þ ý üz%ScheduleEditorDialog._validate_inputsr®c CsÒzÍ|j |¡}|st d|›d¡WdS|j d¡|j |¡}|dkr+|j |¡|j   ¡s;|j   d|d›¡|drÃ|j   |d¡zat|d tƒrVt |d ¡n|d }|dd kro| d d ¡}|rn|j  |¡n8|dd kr§|j | d d¡¡|j | dd¡¡|j | dd¡¡|j | dd¡¡|j | dd¡¡WntyÂ}zt d|›¡WYd}~nd}~wwt d|›¡WdStyè}zt d|›¡WYd}~dSd}~ww)z3Pre-populate schedule settings from an existing jobzJob z not found for pre-populationNTrz Schedule for r~r¦rÀr4r:r5rÁrÂrÃrÄrÅz*Could not parse schedule config from job: z'Pre-populated schedule editor from job zError pre-populating from job: )rÚget_jobrrÇrErFrOrÊr¤r>r±rÈrcrËrÌrÍrÎrÏr§rgrjrÐrmrnrorpr‡Úinforˆ)r'r®rŠr¬rÚ cron_exprrŒr+r+r,r†isH     $   € €€ÿ€ÿz+ScheduleEditorDialog._pre_populate_from_jobc CsÄ| ¡sdSz4|j ¡rd}|j ¡}n…|j ¡r"d}|j ¡}nx|j ¡r=d}|j ¡}|durrVrØrÚcreate_job_groupÚranger^ržr‹r²rr„r…Úadd_job_to_grouprrÜrcrgrjrÙrmrnrorprAÚ toPlainTextrqr!Úupdate_schedulerÚ informationÚcreate_scheduleÚacceptr‡rˆrÑ) r'r¼r½Z group_dataÚir‹r®r¦rÀZ schedule_datarrŒr+r+r,rr—s|      þ   ý  û ù  "€þz#ScheduleEditorDialog._save_schedule)NN)rªÚ __module__Ú __qualname__Ú__doc__rrÚintr r%rtrurvrwrHrRrÍrerPr\r]r_r`r&ÚboolrÚr†rrÚ __classcell__r+r+r)r,rs, 8   92.r) rìÚPyQt6.QtWidgetsrrrrrrrr r r r r rrrrrrrrÚ PyQt6.QtCorerÚlogururrÎÚ core.databaserÚ core.configrrr+r+r+r,ÚsX