@@ -975,7 +975,7 @@ <h3 class="text-lg font-semibold text-gray-800">Backups</h3>
975975 < span class ="text-xs text-gray-500 " x-text ="snapshotContentsSummary(snap) "> </ span >
976976 </ div >
977977 < div class ="flex items-center gap-1.5 shrink-0 " @click.stop >
978- < button @click ="if(confirm('Download this snapshot as a ZIP file? This may take a while for large backups.')) downloadSelectedBackupSnapshot (snap.id) "
978+ < button @click ="confirmDownloadSnapshot (snap.id) "
979979 :disabled ="backupSnapshotDownloading "
980980 class ="p-1.5 text-gray-400 hover:text-gray-700 hover:bg-gray-100 rounded-md disabled:opacity-40 "
981981 title ="Download ZIP ">
@@ -1023,7 +1023,7 @@ <h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide mb-2">
10231023 < div class ="flex items-center justify-between gap-2 bg-white border border-gray-200 rounded-lg px-3 py-2 ">
10241024 < span class ="text-sm text-gray-800 truncate " x-text ="domain "> </ span >
10251025 < template x-if ="snapshotRestoreSiteByDomain(snap, domain) ">
1026- < button @click ="if(confirm('Restore ' + domain + ' from this snapshot? This will overwrite the current site files.')) restoreSiteFromSnapshot(snapshotRestoreSiteByDomain( snap, domain).id, snap.id ) "
1026+ < button @click ="confirmRestoreSiteSnapshot( snap, domain) "
10271027 :disabled ="restoreLoading "
10281028 class ="shrink-0 px-2.5 py-1 text-xs font-medium text-primary-600 bg-primary-50 rounded-md hover:bg-primary-100 disabled:opacity-50 ">
10291029 Restore
@@ -1049,7 +1049,7 @@ <h4 class="text-xs font-semibold text-gray-700 uppercase tracking-wide mb-2">
10491049 < div class ="flex items-center justify-between gap-2 bg-white border border-gray-200 rounded-lg px-3 py-2 ">
10501050 < span class ="text-sm text-gray-800 font-mono truncate " x-text ="dbName "> </ span >
10511051 < template x-if ="snapshotRestoreDBByName(snap, dbName) ">
1052- < button @click ="if(confirm('Restore database \'' + dbName + '\' from this snapshot? This will overwrite the current database.')) restoreDatabaseFromSnapshot(snapshotRestoreDBByName( snap, dbName).id, snap.id ) "
1052+ < button @click ="confirmRestoreDatabaseSnapshot( snap, dbName) "
10531053 :disabled ="restoreLoading "
10541054 class ="shrink-0 px-2.5 py-1 text-xs font-medium text-emerald-600 bg-emerald-50 rounded-md hover:bg-emerald-100 disabled:opacity-50 ">
10551055 Restore
@@ -2591,6 +2591,26 @@ <h3 class="text-lg font-semibold text-gray-900" x-text="confirmModalTitle"></h3>
25912591 </ div >
25922592 </ div >
25932593
2594+ <!-- Notice Modal -->
2595+ < div x-show ="showNoticeModal " class ="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 " x-transition @keydown.escape.window ="closeNotice() ">
2596+ < div class ="bg-white rounded-2xl shadow-xl p-6 w-full max-w-md mx-4 " @click.away ="closeNotice() ">
2597+ < div class ="flex items-start ">
2598+ < div class ="w-11 h-11 rounded-full bg-blue-100 flex items-center justify-center mr-3 flex-shrink-0 ">
2599+ < svg class ="w-6 h-6 text-blue-600 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 ">
2600+ < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M13 16h-1v-4h-1m1-4h.01M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10z "> </ path >
2601+ </ svg >
2602+ </ div >
2603+ < div >
2604+ < h3 class ="text-lg font-semibold text-gray-900 " x-text ="noticeModalTitle "> </ h3 >
2605+ < p class ="text-sm text-gray-600 mt-1 whitespace-pre-line " x-text ="noticeModalMessage "> </ p >
2606+ </ div >
2607+ </ div >
2608+ < div class ="mt-6 ">
2609+ < button @click ="closeNotice() " class ="w-full px-4 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 font-medium " x-text ="noticeModalButtonText "> </ button >
2610+ </ div >
2611+ </ div >
2612+ </ div >
2613+
25942614 <!-- Impersonate User Modal -->
25952615 < div x-show ="showImpersonateUserModal " class ="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 " x-transition >
25962616 < div class ="bg-white rounded-2xl shadow-xl p-6 w-full max-w-md mx-4 " @click.away ="if(!impersonationStarting) { showImpersonateUserModal = false; impersonationError = ''; } ">
@@ -2675,6 +2695,11 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
26752695 confirmModalMessage : '' ,
26762696 confirmModalConfirmText : 'Delete' ,
26772697 confirmModalResolver : null ,
2698+ showNoticeModal : false ,
2699+ noticeModalTitle : '' ,
2700+ noticeModalMessage : '' ,
2701+ noticeModalButtonText : 'OK' ,
2702+ noticeModalResolver : null ,
26782703 sites : [ ] ,
26792704 sitesTotal : 0 ,
26802705 sitesPage : 1 ,
@@ -3220,7 +3245,12 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
32203245 return ;
32213246 }
32223247 const targetSnapshotID = snapshotID ;
3223- if ( ! confirm ( 'Delete this snapshot? This action cannot be undone.' ) ) {
3248+ const confirmed = await this . askConfirmation ( {
3249+ title : 'Delete Snapshot' ,
3250+ message : 'Delete this snapshot? This action cannot be undone.' ,
3251+ confirmText : 'Delete Snapshot'
3252+ } ) ;
3253+ if ( ! confirmed ) {
32243254 return ;
32253255 }
32263256 this . backupSnapshotDeleting = true ;
@@ -3236,6 +3266,37 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
32363266 this . backupSnapshotDeleting = false ;
32373267 }
32383268 } ,
3269+ async confirmDownloadSnapshot ( snapshotID = '' ) {
3270+ const confirmed = await this . askConfirmation ( {
3271+ title : 'Download Snapshot ZIP' ,
3272+ message : 'Download this snapshot as a ZIP file? This may take a while for large backups.' ,
3273+ confirmText : 'Download ZIP'
3274+ } ) ;
3275+ if ( ! confirmed ) return ;
3276+ await this . downloadSelectedBackupSnapshot ( snapshotID ) ;
3277+ } ,
3278+ async confirmRestoreSiteSnapshot ( snapshot , domain ) {
3279+ const target = this . snapshotRestoreSiteByDomain ( snapshot , domain ) ;
3280+ if ( ! target ) return ;
3281+ const confirmed = await this . askConfirmation ( {
3282+ title : 'Restore Website' ,
3283+ message : `Restore ${ domain } from this snapshot? This will overwrite the current site files.` ,
3284+ confirmText : 'Restore Website'
3285+ } ) ;
3286+ if ( ! confirmed ) return ;
3287+ await this . restoreSiteFromSnapshot ( target . id , snapshot . id ) ;
3288+ } ,
3289+ async confirmRestoreDatabaseSnapshot ( snapshot , dbName ) {
3290+ const target = this . snapshotRestoreDBByName ( snapshot , dbName ) ;
3291+ if ( ! target ) return ;
3292+ const confirmed = await this . askConfirmation ( {
3293+ title : 'Restore Database' ,
3294+ message : `Restore database "${ dbName } " from this snapshot? This will overwrite the current database.` ,
3295+ confirmText : 'Restore Database'
3296+ } ) ;
3297+ if ( ! confirmed ) return ;
3298+ await this . restoreDatabaseFromSnapshot ( target . id , snapshot . id ) ;
3299+ } ,
32393300 snapshotTagValuesFor ( snapshot , prefix ) {
32403301 if ( ! snapshot || ! Array . isArray ( snapshot . tags ) ) return [ ] ;
32413302 const values = [ ] ;
@@ -3921,6 +3982,22 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
39213982 this . confirmModalResolver = null ;
39223983 }
39233984 } ,
3985+ showNotice ( { title, message, buttonText = 'OK' } ) {
3986+ return new Promise ( ( resolve ) => {
3987+ this . noticeModalTitle = title || 'Notice' ;
3988+ this . noticeModalMessage = message || '' ;
3989+ this . noticeModalButtonText = buttonText ;
3990+ this . noticeModalResolver = resolve ;
3991+ this . showNoticeModal = true ;
3992+ } ) ;
3993+ } ,
3994+ closeNotice ( ) {
3995+ this . showNoticeModal = false ;
3996+ if ( this . noticeModalResolver ) {
3997+ this . noticeModalResolver ( true ) ;
3998+ this . noticeModalResolver = null ;
3999+ }
4000+ } ,
39244001
39254002 async deleteSite ( site ) {
39264003 const confirmed = await this . askConfirmation ( {
@@ -3934,7 +4011,10 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
39344011 await this . api ( `/sites/${ site . id } ` , 'DELETE' ) ;
39354012 await this . loadSites ( ) ;
39364013 } catch ( e ) {
3937- alert ( 'Failed to delete site: ' + ( e . message || 'Unknown error' ) ) ;
4014+ await this . showNotice ( {
4015+ title : 'Delete Site Failed' ,
4016+ message : e . message || 'Unknown error'
4017+ } ) ;
39384018 } finally {
39394019 this . deletingSiteId = null ;
39404020 }
@@ -4170,7 +4250,10 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
41704250 await this . api ( `/databases/${ db . id } ` , 'DELETE' ) ;
41714251 await this . loadDatabases ( ) ;
41724252 } catch ( e ) {
4173- alert ( 'Failed to delete database: ' + ( e . message || 'Unknown error' ) ) ;
4253+ await this . showNotice ( {
4254+ title : 'Delete Database Failed' ,
4255+ message : e . message || 'Unknown error'
4256+ } ) ;
41744257 } finally {
41754258 this . deletingDatabaseId = null ;
41764259 }
@@ -4193,7 +4276,10 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
41934276 window . open ( resp . url , '_blank' ) ;
41944277 }
41954278 } catch ( e ) {
4196- alert ( 'Could not open phpMyAdmin: ' + e . message + '\n\nNote: Databases created before this feature do not have stored credentials.' ) ;
4279+ await this . showNotice ( {
4280+ title : 'Could Not Open phpMyAdmin' ,
4281+ message : ( e . message || 'Request failed' ) + '\n\nNote: databases created before this feature do not have stored credentials.'
4282+ } ) ;
41974283 }
41984284 } ,
41994285
@@ -4496,21 +4582,32 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
44964582 await this . api ( `/users/${ u . username } ` , 'DELETE' ) ;
44974583 this . loadUsers ( ) ;
44984584 } catch ( e ) {
4499- alert ( 'Failed to delete user: ' + e . message ) ;
4585+ await this . showNotice ( {
4586+ title : 'Delete User Failed' ,
4587+ message : e . message || 'Request failed'
4588+ } ) ;
45004589 } finally {
45014590 this . deletingUser = null ;
45024591 }
45034592 } ,
45044593
45054594 async toggleUserSuspension ( u ) {
45064595 const action = u . is_suspended ? 'unsuspend' : 'suspend' ;
4507- if ( ! confirm ( `${ action . charAt ( 0 ) . toUpperCase ( ) + action . slice ( 1 ) } user "${ u . username } "?` ) ) return ;
4596+ const confirmed = await this . askConfirmation ( {
4597+ title : `${ action . charAt ( 0 ) . toUpperCase ( ) + action . slice ( 1 ) } User` ,
4598+ message : `${ action . charAt ( 0 ) . toUpperCase ( ) + action . slice ( 1 ) } user "${ u . username } "?` ,
4599+ confirmText : `${ action . charAt ( 0 ) . toUpperCase ( ) + action . slice ( 1 ) } User`
4600+ } ) ;
4601+ if ( ! confirmed ) return ;
45084602 this . suspendingUser = u . username ;
45094603 try {
45104604 await this . api ( `/users/${ u . username } /toggle-suspend` , 'POST' ) ;
45114605 this . loadUsers ( ) ;
45124606 } catch ( e ) {
4513- alert ( 'Failed to ' + action + ' user: ' + e . message ) ;
4607+ await this . showNotice ( {
4608+ title : `${ action . charAt ( 0 ) . toUpperCase ( ) + action . slice ( 1 ) } User Failed` ,
4609+ message : e . message || 'Request failed'
4610+ } ) ;
45144611 } finally {
45154612 this . suspendingUser = null ;
45164613 }
@@ -4533,7 +4630,12 @@ <h3 class="text-xl font-semibold text-gray-800">Login as User</h3>
45334630 } ,
45344631
45354632 async performUpdate ( ) {
4536- if ( ! confirm ( `Update FastCP to ${ this . updateInfo . latest_version } ? The control panel will restart.` ) ) return ;
4633+ const confirmed = await this . askConfirmation ( {
4634+ title : 'Update FastCP' ,
4635+ message : `Update FastCP to ${ this . updateInfo . latest_version } ? The control panel will restart.` ,
4636+ confirmText : 'Update Now'
4637+ } ) ;
4638+ if ( ! confirmed ) return ;
45374639 this . updateLoading = true ;
45384640 this . updateError = '' ;
45394641 this . updateProgress = 5 ;
0 commit comments