@@ -26,20 +26,137 @@ async function loadEnv() {
2626 data . forEach ( item => {
2727 const div = document . createElement ( 'div' ) ;
2828 div . className = 'env-item' ;
29-
29+
30+ const key = item . key ;
31+ const val = item . value == null ? '' : item . value ;
32+ const desc = item . desc || '' ;
33+
34+ // Infer type from key name and value
35+ function inferType ( key , value ) {
36+ const k = key . toUpperCase ( ) ;
37+ const v = ( value || '' ) . toString ( ) . toLowerCase ( ) ;
38+ if ( / ( _ D I R | _ P A T H | _ F I L E | V A U L T | C O N F I G | D B | K E Y _ F I L E ) / i. test ( k ) ) {
39+ if ( / _ F I L E $ / i. test ( k ) || / _ K E Y _ F I L E $ / i. test ( k ) ) return { type : 'path' , mode : 'file' } ;
40+ if ( / _ D I R $ / i. test ( k ) || / _ D I R \b / i. test ( k ) ) return { type : 'path' , mode : 'dir' } ;
41+ return { type : 'path' , mode : 'any' } ;
42+ }
43+ if ( v === 'true' || v === 'false' || v === '0' || v === '1' ) return { type : 'boolean' } ;
44+ if ( k . match ( / ( H O U R | M I N U T E | P O R T | C O U N T | N U M | S I Z E | S E C O N D S | D A Y S ) / ) ) return { type : 'number' } ;
45+ if ( / ^ \d + $ / . test ( v ) ) return { type : 'number' } ;
46+ return { type : 'string' } ;
47+ }
48+
49+ const info = inferType ( key , val ) ;
50+
51+ // Build inner HTML per type
52+ let inputHtml = '' ;
53+ if ( info . type === 'boolean' ) {
54+ const checked = ( val === 'true' || val === '1' ) ? 'checked' : '' ;
55+ inputHtml = `<input type="checkbox" data-key="${ key } " ${ checked } >` ;
56+ } else if ( info . type === 'number' ) {
57+ let attrs = '' ;
58+ if ( key . toUpperCase ( ) . includes ( 'HOUR' ) ) attrs = 'min="0" max="23"' ;
59+ if ( key . toUpperCase ( ) . includes ( 'MINUTE' ) ) attrs = 'min="0" max="59"' ;
60+ inputHtml = `<input type="number" data-key="${ key } " value="${ val } " ${ attrs } >` ;
61+ } else if ( info . type === 'path' ) {
62+ // show text input + browse button (browse uses prompt as fallback)
63+ inputHtml = `<div style="display:flex;gap:8px;align-items:center;"><input type="text" data-key="${ key } " value="${ val } " style="flex:1"><button type="button" class="secondary" data-browse-for="${ key } ">Parcourir</button></div>` ;
64+ } else {
65+ inputHtml = `<input type="text" data-key="${ key } " value="${ val } ">` ;
66+ }
67+
3068 div . innerHTML = `
31- <label class="env-label">${ item . key } </label>
32- <div class="env-desc">${ item . desc || '' } </div>
33- <input type="text" data-key=" ${ item . key } " value=" ${ item . value || '' } ">
69+ <label class="env-label">${ key } </label>
70+ <div class="env-desc">${ desc } </div>
71+ ${ inputHtml }
3472 ` ;
3573 container . appendChild ( div ) ;
3674 } ) ;
75+
76+ // Attach browse handlers. Prefer native pywebview dialogs when available,
77+ // otherwise fall back to a simple `prompt`.
78+ container . querySelectorAll ( 'button[data-browse-for]' ) . forEach ( btn => {
79+ btn . addEventListener ( 'click' , async ( e ) => {
80+ const key = btn . dataset . browseFor ;
81+ const input = container . querySelector ( `input[data-key="${ key } "]` ) ;
82+ if ( ! input ) return ;
83+
84+ const isFile = / _ F I L E $ | K E Y _ F I L E / i. test ( key ) ;
85+ const isDir = / _ D I R $ | V A U L T | C O N F I G | S Y N C _ D I R / i. test ( key ) ;
86+
87+ // If running inside pywebview, use the exposed API
88+ if ( window . pywebview && window . pywebview . api ) {
89+ try {
90+ let path = '' ;
91+ if ( isDir ) {
92+ path = await window . pywebview . api . open_dir ( `Select folder for ${ key } ` ) ;
93+ } else {
94+ path = await window . pywebview . api . open_file ( `Select file for ${ key } ` ) ;
95+ }
96+ if ( path ) input . value = path ;
97+ return ;
98+ } catch ( err ) {
99+ // fall through to prompt fallback
100+ }
101+ }
102+
103+ // Browser or fallback
104+ const current = input . value || '' ;
105+ const chosen = prompt ( 'Entrez le chemin pour ' + key , current ) ;
106+ if ( chosen !== null ) input . value = chosen ;
107+ } ) ;
108+ } ) ;
109+
110+ // If .env.local does not exist, show init button
111+ try {
112+ const existsRes = await fetch ( '/api/env/exists' ) ;
113+ const existsData = await existsRes . json ( ) ;
114+ if ( ! existsData . exists ) {
115+ const initDiv = document . createElement ( 'div' ) ;
116+ initDiv . style . marginTop = '10px' ;
117+ initDiv . innerHTML = `<button id="btn-init-env" class="secondary">Initialiser .env.local</button> <span style="color:#888; font-size:12px; margin-left:8px;">Crée .env.local à partir de .env.example</span>` ;
118+ container . prepend ( initDiv ) ;
119+ document . getElementById ( 'btn-init-env' ) . addEventListener ( 'click' , async ( ) => {
120+ if ( ! confirm ( 'Créer .env.local à partir de .env.example ?' ) ) return ;
121+ const btn = document . getElementById ( 'btn-init-env' ) ;
122+ btn . disabled = true ;
123+ btn . innerText = 'Initialisation...' ;
124+ try {
125+ const r = await fetch ( '/api/env/init' , { method : 'POST' } ) ;
126+ const j = await r . json ( ) ;
127+ if ( j . error ) alert ( 'Erreur: ' + j . error ) ;
128+ else {
129+ alert ( '.env.local créé' ) ;
130+ // reload form
131+ loadEnv ( ) ;
132+ }
133+ } catch ( e ) {
134+ alert ( 'Request failed: ' + e ) ;
135+ } finally {
136+ btn . disabled = false ;
137+ btn . innerText = 'Initialiser .env.local' ;
138+ }
139+ } ) ;
140+ }
141+ } catch ( e ) {
142+ // ignore existence check failures
143+ }
37144}
38145
39146async function saveEnv ( ) {
40147 const inputs = document . querySelectorAll ( '#env-form input' ) ;
41148 const data = { } ;
42- inputs . forEach ( i => data [ i . dataset . key ] = i . value ) ;
149+ inputs . forEach ( i => {
150+ const key = i . dataset . key ;
151+ if ( ! key ) return ;
152+ if ( i . type === 'checkbox' ) {
153+ data [ key ] = i . checked ? 'true' : 'false' ;
154+ } else if ( i . type === 'number' ) {
155+ data [ key ] = i . value . toString ( ) ;
156+ } else {
157+ data [ key ] = i . value ;
158+ }
159+ } ) ;
43160
44161 await fetch ( '/api/env' , {
45162 method : 'POST' ,
@@ -130,8 +247,24 @@ async function runUpdate() {
130247}
131248
132249async function importInstalled ( ) {
133- // Placeholder: currently calls update.sh? No, we need import_installed.sh
134- alert ( "This feature triggers 'src/import_installed.sh' (not fully wired in API yet, assuming bash access)" ) ;
250+ const btn = document . querySelector ( 'button[onclick="importInstalled()"]' ) ;
251+ const originalText = btn ? btn . innerText : '' ;
252+ if ( btn ) { btn . disabled = true ; btn . innerText = t ( 'running' ) ; }
253+
254+ try {
255+ const res = await fetch ( '/api/action/import-installed' , { method : 'POST' } ) ;
256+ const data = await res . json ( ) ;
257+ console . log ( data ) ;
258+ if ( data . error ) {
259+ alert ( 'Error: ' + data . error ) ;
260+ } else {
261+ alert ( 'Exit Code: ' + data . code + '\n\nSTDOUT:\n' + ( data . stdout || '' ) . slice ( 0 , 2000 ) + '\n\nSTDERR:\n' + ( data . stderr || '' ) . slice ( 0 , 2000 ) ) ;
262+ }
263+ } catch ( e ) {
264+ alert ( 'Request failed: ' + e ) ;
265+ } finally {
266+ if ( btn ) { btn . disabled = false ; btn . innerText = originalText ; }
267+ }
135268}
136269
137270// SEARCH
0 commit comments