@@ -235,15 +235,21 @@ import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/comp
235
235
import { List , ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js" ;
236
236
import { ExclamationTriangleIcon , InfoIcon , HelpIcon , EyeIcon , EyeSlashIcon } from "@patternfly/react-icons" ;
237
237
import { InputGroup } from "@patternfly/react-core/dist/esm/components/InputGroup/index.js" ;
238
+ import { Table , Tbody , Tr , Td } from '@patternfly/react-table' ;
238
239
239
240
import { show_modal_dialog , apply_modal_dialog } from "cockpit-components-dialog.jsx" ;
240
241
import { ListingTable } from "cockpit-components-table.jsx" ;
241
242
import { FormHelper } from "cockpit-components-form-helper" ;
242
243
243
- import { fmt_size , block_name , format_size_and_text , format_delay , for_each_async , get_byte_units } from "./utils.js" ;
244
+ import {
245
+ decode_filename , fmt_size , block_name , format_size_and_text , format_delay , for_each_async , get_byte_units ,
246
+ is_available_block
247
+ } from "./utils.js" ;
244
248
import { fmt_to_fragments } from "utils.jsx" ;
245
249
import client from "./client.js" ;
246
250
251
+ import fsys_is_empty_sh from "./fsys-is-empty.sh" ;
252
+
247
253
const _ = cockpit . gettext ;
248
254
249
255
function make_rows ( fields , values , errors , onChange ) {
@@ -328,6 +334,19 @@ const Body = ({ body, teardown, fields, values, errors, isFormHorizontal, onChan
328
334
) ;
329
335
} ;
330
336
337
+ const ExtraConfirmation = ( { text, onChange } ) => {
338
+ const [ confirmed , setConfirmed ] = useState ( false ) ;
339
+
340
+ return (
341
+ < Checkbox isChecked = { confirmed }
342
+ id = "dialog-confirm"
343
+ label = { text }
344
+ onChange = { ( _ , val ) => {
345
+ setConfirmed ( val ) ;
346
+ onChange ( val ) ;
347
+ } } /> ) ;
348
+ } ;
349
+
331
350
function flatten_fields ( fields ) {
332
351
return fields . reduce (
333
352
( acc , val ) => acc . concat ( [ val ] ) . concat ( val . options && val . options . nested_fields
@@ -340,6 +359,8 @@ export const dialog_open = (def) => {
340
359
const nested_fields = def . Fields || [ ] ;
341
360
const fields = flatten_fields ( nested_fields ) ;
342
361
const values = { } ;
362
+ let confirmation = null ;
363
+ let confirmed = false ;
343
364
let errors = null ;
344
365
345
366
fields . forEach ( f => { values [ f . tag ] = f . initial_value } ) ;
@@ -415,8 +436,10 @@ export const dialog_open = (def) => {
415
436
caption : variant . Title ,
416
437
style : actions . length == 0 ? "primary" : "secondary" ,
417
438
danger : def . Action . Danger || def . Action . DangerButton ,
418
- disabled : running_promise != null || ( def . Action . disable_on_error &&
419
- errors && errors . toString ( ) != "[object Object]" ) ,
439
+ disabled : ( running_promise != null ||
440
+ ( def . Action . disable_on_error &&
441
+ errors && errors . toString ( ) != "[object Object]" ) ||
442
+ ( confirmation && ! confirmed ) ) ,
420
443
clicked : progress_callback => run_action ( progress_callback , variant . tag ) ,
421
444
} ) ;
422
445
}
@@ -436,13 +459,19 @@ export const dialog_open = (def) => {
436
459
}
437
460
}
438
461
439
- const extra = (
440
- < div >
441
- { def . Action && def . Action . Danger
442
- ? < HelperText > < HelperTextItem variant = "error" > { def . Action . Danger } </ HelperTextItem > </ HelperText >
443
- : null
444
- }
445
- </ div > ) ;
462
+ let extra = null ;
463
+ if ( confirmation ) {
464
+ extra = < ExtraConfirmation text = { confirmation }
465
+ onChange = { val => {
466
+ confirmed = val ;
467
+ update_footer ( ) ;
468
+ } } /> ;
469
+ } else if ( def . Action && def . Action . Danger ) {
470
+ extra = (
471
+ < div >
472
+ < HelperText > < HelperTextItem variant = "error" > { def . Action . Danger } </ HelperTextItem > </ HelperText >
473
+ </ div > ) ;
474
+ }
446
475
447
476
return {
448
477
idle_message : ( running_promise
@@ -537,6 +566,14 @@ export const dialog_open = (def) => {
537
566
update ( ) ;
538
567
} ,
539
568
569
+ need_confirmation : ( conf ) => {
570
+ confirmation = conf ;
571
+ confirmed = false ;
572
+ def . Action . Danger = null ;
573
+ def . Action . DangerButton = true ;
574
+ update_footer ( ) ;
575
+ } ,
576
+
540
577
close : ( ) => {
541
578
dlg . footerProps . dialog_done ( ) ;
542
579
}
@@ -1204,13 +1241,16 @@ const teardown_block_name = use => {
1204
1241
name = block_name ( client . blocks [ use . block . CryptoBackingDevice ] || use . block ) ;
1205
1242
}
1206
1243
1207
- return name ;
1244
+ return name . replace ( / ^ \/ d e v \/ / , "" ) ;
1208
1245
} ;
1209
1246
1210
1247
export const TeardownMessage = ( usage , expect_single_unmount ) => {
1211
- if ( usage . length == 0 )
1248
+ if ( ! usage . Teardown )
1212
1249
return null ;
1213
1250
1251
+ if ( client . in_anaconda_mode ( ) && ! expect_single_unmount )
1252
+ return < AnacondaTeardownMessage usage = { usage } /> ;
1253
+
1214
1254
if ( is_expected_unmount ( usage , expect_single_unmount ) )
1215
1255
return < StopProcessesMessage mount_point = { expect_single_unmount } users = { usage [ 0 ] . users } /> ;
1216
1256
@@ -1251,6 +1291,36 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
1251
1291
</ div > ) ;
1252
1292
} ;
1253
1293
1294
+ const AnacondaTeardownMessage = ( { usage } ) => {
1295
+ const rows = [ ] ;
1296
+
1297
+ usage . forEach ( ( use , index ) => {
1298
+ if ( use . data_warning ) {
1299
+ const name = teardown_block_name ( use ) ;
1300
+ const location = client . strip_mount_point_prefix ( use . location ) || use . block . IdLabel || "-" ;
1301
+
1302
+ rows . push (
1303
+ < Tr key = { index } >
1304
+ < Td className = "pf-v5-u-font-weight-bold" > { name } </ Td >
1305
+ < Td > { location } </ Td >
1306
+ < Td > { use . data_warning } </ Td >
1307
+ </ Tr > ) ;
1308
+ }
1309
+ } ) ;
1310
+
1311
+ if ( rows . length > 0 ) {
1312
+ return (
1313
+ < div className = "modal-footer-teardown" >
1314
+ < HelperText >
1315
+ < HelperTextItem variant = "error" >
1316
+ { _ ( "Important data might be deleted:" ) }
1317
+ </ HelperTextItem >
1318
+ </ HelperText >
1319
+ < Table variant = "compact" borders = { false } > < Tbody > { rows } </ Tbody > </ Table >
1320
+ </ div > ) ;
1321
+ }
1322
+ } ;
1323
+
1254
1324
export function teardown_danger_message ( usage , expect_single_unmount ) {
1255
1325
if ( is_expected_unmount ( usage , expect_single_unmount ) )
1256
1326
return stop_processes_danger_message ( usage [ 0 ] . users ) ;
@@ -1269,24 +1339,51 @@ export function teardown_danger_message(usage, expect_single_unmount) {
1269
1339
}
1270
1340
}
1271
1341
1272
- export function init_active_usage_processes ( client , usage , expect_single_unmount ) {
1342
+ export function init_teardown_usage ( client , usage , expect_single_unmount ) {
1273
1343
return {
1274
- title : _ ( "Checking related processes" ) ,
1275
- func : dlg => {
1276
- return for_each_async ( usage , u => {
1344
+ title : _ ( "Checking filesystem usage" ) ,
1345
+ func : async function ( dlg ) {
1346
+ let have_data = false ;
1347
+ for ( const u of usage ) {
1277
1348
if ( u . usage == "mounted" ) {
1278
- return client . find_mount_users ( u . location )
1279
- . then ( users => {
1280
- u . users = users ;
1281
- } ) ;
1282
- } else
1283
- return Promise . resolve ( ) ;
1284
- } ) . then ( ( ) => {
1285
- dlg . set_attribute ( "Teardown" , TeardownMessage ( usage , expect_single_unmount ) ) ;
1349
+ u . users = await client . find_mount_users ( u . location ) ;
1350
+ }
1351
+ if ( client . in_anaconda_mode ( ) && ! expect_single_unmount && u . block ) {
1352
+ if ( u . block . IdUsage == "filesystem" &&
1353
+ [ "xfs" , "ext2" , "ext3" , "ext4" , "btrfs" , "vfat" , "ntfs" ] . indexOf ( u . block . IdType ) >= 0 ) {
1354
+ const empty = await cockpit . script ( fsys_is_empty_sh ,
1355
+ [ decode_filename ( u . block . PreferredDevice ) ] ,
1356
+ { superuser : true , err : "message" } ) ;
1357
+ if ( empty . trim ( ) != "yes" ) {
1358
+ console . log ( "INFO" , empty ) ;
1359
+ const info = JSON . parse ( empty ) ;
1360
+ u . data_warning = cockpit . format ( _ ( "$0 used, $1 total" ) ,
1361
+ fmt_size ( ( info . total - info . free ) * info . unit ) ,
1362
+ fmt_size ( info . total * info . unit ) ) ;
1363
+ }
1364
+ } else if ( u . block . IdUsage == "crypto" && ! client . blocks_cleartext [ u . block . path ] ) {
1365
+ u . data_warning = _ ( "Locked encrypted device might contain data" ) ;
1366
+ } else if ( ! client . blocks_ptable [ u . block . path ] &&
1367
+ u . block . IdUsage != "raid" &&
1368
+ ! is_available_block ( client , u . block ) ) {
1369
+ u . data_warning = _ ( "Device contains unrecognized data" ) ;
1370
+ }
1371
+ if ( u . data_warning )
1372
+ have_data = true ;
1373
+ }
1374
+ }
1375
+
1376
+ if ( have_data ) {
1377
+ usage . Teardown = true ;
1378
+ dlg . need_confirmation ( _ ( "I confirm I want to lose this data forever" ) ) ;
1379
+ } else if ( client . in_anaconda_mode ( ) && ! expect_single_unmount ) {
1380
+ dlg . need_confirmation ( null ) ;
1381
+ } else {
1286
1382
const msg = teardown_danger_message ( usage , expect_single_unmount ) ;
1287
1383
if ( msg )
1288
1384
dlg . add_danger ( msg ) ;
1289
- } ) ;
1385
+ }
1386
+ dlg . set_attribute ( "Teardown" , TeardownMessage ( usage , expect_single_unmount ) ) ;
1290
1387
}
1291
1388
} ;
1292
1389
}
0 commit comments