8787 }
8888 .tab-content { display : none; flex-direction : column; gap : 1.5rem ; }
8989 .tab-content .active { display : flex; }
90+ .alert-banner {
91+ background : rgba (245 , 158 , 11 , 0.1 );
92+ border : 1px solid rgba (245 , 158 , 11 , 0.3 );
93+ color : var (--warning );
94+ padding : 1rem 1.25rem ;
95+ border-radius : 8px ;
96+ display : flex;
97+ justify-content : space-between;
98+ align-items : center;
99+ font-size : 0.95rem ;
100+ }
101+ .alert-banner .danger {
102+ background : rgba (244 , 63 , 94 , 0.1 );
103+ border-color : rgba (244 , 63 , 94 , 0.3 );
104+ color : var (--danger );
105+ }
90106 .grid { display : grid; grid-template-columns : repeat (auto-fill, minmax (340px , 1fr )); gap : 1.5rem ; }
91107 .card {
92108 background : var (--card-bg );
132148 align-items : center;
133149 justify-content : center;
134150 gap : 0.5rem ;
135- transition : background 0.2s ease;
151+ transition : background 0.2s ease, opacity 0.2 s ease ;
136152 }
153+ .btn : disabled { opacity : 0.5 ; cursor : not-allowed; }
137154 .btn-primary { background : var (--primary ); color : # 0f172a ; }
138- .btn-primary : hover { background : var (--primary-hover ); }
155+ .btn-primary : hover : not ( : disabled ) { background : var (--primary-hover ); }
139156 .btn-danger { background : rgba (244 , 63 , 94 , 0.15 ); color : var (--danger ); border : 1px solid rgba (244 , 63 , 94 , 0.3 ); }
140- .btn-danger : hover { background : rgba (244 , 63 , 94 , 0.3 ); }
157+ .btn-danger : hover : not ( : disabled ) { background : rgba (244 , 63 , 94 , 0.3 ); }
141158 .btn-secondary { background : var (--card-border ); color : var (--text ); }
142- .btn-secondary : hover { background : # 334155 ; }
159+ .btn-secondary : hover : not ( : disabled ) { background : # 334155 ; }
143160 .log-box {
144161 background : # 090d16 ;
145162 border : 1px solid var (--card-border );
163180 border-radius : 8px ;
164181 }
165182 .health-check .ok { border-left : 4px solid var (--success ); }
166- .health-check .warn { border-left : 4px solid var (--warning ); }
167- .health-check .error { border-left : 4px solid var (--danger ); }
168183 </ style >
169184</ head >
170185< body >
174189 < span class ="logo-badge "> DESKTOP GUI</ span >
175190 </ div >
176191 < nav >
177- < button class ="nav-btn active " onclick ="switchTab('projects') "> Projects</ button >
178- < button class ="nav-btn " onclick ="switchTab('packs') "> Runtime Packs</ button >
179- < button class ="nav-btn " onclick ="switchTab('logs') "> Live Logs</ button >
180- < button class ="nav-btn " onclick ="switchTab('doctor') "> Doctor</ button >
192+ < button class ="nav-btn active " onclick ="switchTab('projects', event ) "> Projects</ button >
193+ < button class ="nav-btn " onclick ="switchTab('packs', event ) "> Runtime Packs</ button >
194+ < button class ="nav-btn " onclick ="switchTab('logs', event ) "> Live Logs</ button >
195+ < button class ="nav-btn " onclick ="switchTab('doctor', event ) "> Doctor</ button >
181196 </ nav >
182197 </ header >
183198
184199 < main >
200+ <!-- Notification Banner -->
201+ < div id ="notice-banner " class ="alert-banner " style ="display:none; ">
202+ < span id ="notice-message "> No runtime.yml found in current directory.</ span >
203+ < button class ="btn btn-primary " onclick ="initProject() " style ="flex:none; "> Create runtime.yml</ button >
204+ </ div >
205+
206+ <!-- Error Banner -->
207+ < div id ="error-banner " class ="alert-banner danger " style ="display:none; ">
208+ < span id ="error-message "> </ span >
209+ < button class ="btn btn-secondary " onclick ="hideError() " style ="flex:none; "> Dismiss</ button >
210+ </ div >
211+
185212 <!-- Projects Tab -->
186213 < section id ="tab-projects " class ="tab-content active ">
187214 < h2 > Active Projects & Services</ h2 >
@@ -197,8 +224,8 @@ <h2>Active Projects & Services</h2>
197224 < div class ="meta-item "> < span > Database:</ span > < span class ="meta-value " id ="current-db "> -</ span > </ div >
198225 </ div >
199226 < div class ="btn-group ">
200- < button class ="btn btn-primary " onclick ="startServices() "> Start Services</ button >
201- < button class ="btn btn-danger " onclick ="stopServices() "> Stop Services</ button >
227+ < button class ="btn btn-primary " id =" btn-start " onclick ="startServices() "> Start Services</ button >
228+ < button class ="btn btn-danger " id =" btn-stop " onclick ="stopServices() "> Stop Services</ button >
202229 </ div >
203230 </ div >
204231 </ div >
@@ -248,19 +275,43 @@ <h2>Environment Diagnostics</h2>
248275 </ main >
249276
250277 < script >
251- function switchTab ( tabId ) {
278+ function switchTab ( tabId , evt ) {
252279 document . querySelectorAll ( '.nav-btn' ) . forEach ( btn => btn . classList . remove ( 'active' ) ) ;
253280 document . querySelectorAll ( '.tab-content' ) . forEach ( tab => tab . classList . remove ( 'active' ) ) ;
254- event . target . classList . add ( 'active' ) ;
281+ if ( evt && evt . target ) evt . target . classList . add ( 'active' ) ;
255282 document . getElementById ( 'tab-' + tabId ) . classList . add ( 'active' ) ;
256283 if ( tabId === 'logs' ) fetchLogs ( ) ;
257284 if ( tabId === 'doctor' ) fetchDoctor ( ) ;
258285 }
259286
287+ function showError ( msg ) {
288+ const banner = document . getElementById ( 'error-banner' ) ;
289+ document . getElementById ( 'error-message' ) . innerText = msg ;
290+ banner . style . display = 'flex' ;
291+ }
292+
293+ function hideError ( ) {
294+ document . getElementById ( 'error-banner' ) . style . display = 'none' ;
295+ }
296+
260297 async function fetchStatus ( ) {
261298 try {
262299 const res = await fetch ( '/api/status' ) ;
263300 const data = await res . json ( ) ;
301+
302+ if ( ! data . manifest_found ) {
303+ document . getElementById ( 'notice-banner' ) . style . display = 'flex' ;
304+ document . getElementById ( 'notice-message' ) . innerText = data . error || 'No runtime.yml found in current directory.' ;
305+ document . getElementById ( 'current-project-name' ) . innerText = 'No Manifest' ;
306+ document . getElementById ( 'btn-start' ) . disabled = true ;
307+ document . getElementById ( 'btn-stop' ) . disabled = true ;
308+ return ;
309+ }
310+
311+ document . getElementById ( 'notice-banner' ) . style . display = 'none' ;
312+ document . getElementById ( 'btn-start' ) . disabled = false ;
313+ document . getElementById ( 'btn-stop' ) . disabled = false ;
314+
264315 if ( data . project ) {
265316 document . getElementById ( 'current-project-name' ) . innerText = data . project . name || 'Local Project' ;
266317 document . getElementById ( 'current-php' ) . innerText = 'PHP ' + ( data . project . php || '8.3' ) ;
@@ -277,14 +328,60 @@ <h2>Environment Diagnostics</h2>
277328 }
278329 }
279330
331+ async function initProject ( ) {
332+ hideError ( ) ;
333+ try {
334+ const res = await fetch ( '/api/init' , { method : 'POST' } ) ;
335+ const data = await res . json ( ) ;
336+ if ( data . success ) {
337+ document . getElementById ( 'notice-banner' ) . style . display = 'none' ;
338+ fetchStatus ( ) ;
339+ } else {
340+ showError ( data . error || 'Failed to create runtime.yml' ) ;
341+ }
342+ } catch ( err ) {
343+ showError ( 'Network error while initializing project.' ) ;
344+ }
345+ }
346+
280347 async function startServices ( ) {
281- await fetch ( '/api/up' , { method : 'POST' } ) ;
282- fetchStatus ( ) ;
348+ hideError ( ) ;
349+ const startBtn = document . getElementById ( 'btn-start' ) ;
350+ startBtn . disabled = true ;
351+ startBtn . innerText = 'Starting...' ;
352+ try {
353+ const res = await fetch ( '/api/up' , { method : 'POST' } ) ;
354+ const data = await res . json ( ) ;
355+ if ( ! data . success ) {
356+ showError ( data . error || 'Failed to start services.' ) ;
357+ }
358+ } catch ( err ) {
359+ showError ( 'Network error while starting services.' ) ;
360+ } finally {
361+ startBtn . disabled = false ;
362+ startBtn . innerText = 'Start Services' ;
363+ fetchStatus ( ) ;
364+ }
283365 }
284366
285367 async function stopServices ( ) {
286- await fetch ( '/api/down' , { method : 'POST' } ) ;
287- fetchStatus ( ) ;
368+ hideError ( ) ;
369+ const stopBtn = document . getElementById ( 'btn-stop' ) ;
370+ stopBtn . disabled = true ;
371+ stopBtn . innerText = 'Stopping...' ;
372+ try {
373+ const res = await fetch ( '/api/down' , { method : 'POST' } ) ;
374+ const data = await res . json ( ) ;
375+ if ( ! data . success ) {
376+ showError ( data . error || 'Failed to stop services.' ) ;
377+ }
378+ } catch ( err ) {
379+ showError ( 'Network error while stopping services.' ) ;
380+ } finally {
381+ stopBtn . disabled = false ;
382+ stopBtn . innerText = 'Stop Services' ;
383+ fetchStatus ( ) ;
384+ }
288385 }
289386
290387 async function fetchLogs ( ) {
0 commit comments