@@ -235,15 +235,22 @@ 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 } from "./utils.js" ;
244
+ import {
245
+ decode_filename , fmt_size , block_name , format_size_and_text , format_delay , for_each_async ,
246
+ is_available_block
247
+ } from "./utils.js" ;
244
248
import { fmt_to_fragments } from "utils.jsx" ;
249
+
245
250
import client from "./client.js" ;
246
251
252
+ import fsys_is_empty_sh from "./fsys-is-empty.sh" ;
253
+
247
254
const _ = cockpit . gettext ;
248
255
249
256
function make_rows ( fields , values , errors , onChange ) {
@@ -328,6 +335,19 @@ const Body = ({ body, teardown, fields, values, errors, isFormHorizontal, onChan
328
335
) ;
329
336
} ;
330
337
338
+ const ExtraConfirmation = ( { text, onChange } ) => {
339
+ const [ confirmed , setConfirmed ] = useState ( false ) ;
340
+
341
+ return (
342
+ < Checkbox isChecked = { confirmed }
343
+ id = "dialog-confirm"
344
+ label = { text }
345
+ onChange = { ( _ , val ) => {
346
+ setConfirmed ( val ) ;
347
+ onChange ( val ) ;
348
+ } } /> ) ;
349
+ } ;
350
+
331
351
function flatten_fields ( fields ) {
332
352
return fields . reduce (
333
353
( acc , val ) => acc . concat ( [ val ] ) . concat ( val . options && val . options . nested_fields
@@ -340,6 +360,8 @@ export const dialog_open = (def) => {
340
360
const nested_fields = def . Fields || [ ] ;
341
361
const fields = flatten_fields ( nested_fields ) ;
342
362
const values = { } ;
363
+ let confirmation = null ;
364
+ let confirmed = false ;
343
365
let errors = null ;
344
366
345
367
fields . forEach ( f => { values [ f . tag ] = f . initial_value } ) ;
@@ -415,8 +437,10 @@ export const dialog_open = (def) => {
415
437
caption : variant . Title ,
416
438
style : actions . length == 0 ? "primary" : "secondary" ,
417
439
danger : def . Action . Danger || def . Action . DangerButton ,
418
- disabled : running_promise != null || ( def . Action . disable_on_error &&
419
- errors && errors . toString ( ) != "[object Object]" ) ,
440
+ disabled : ( running_promise != null ||
441
+ ( def . Action . disable_on_error &&
442
+ errors && errors . toString ( ) != "[object Object]" ) ||
443
+ ( confirmation && ! confirmed ) ) ,
420
444
clicked : progress_callback => run_action ( progress_callback , variant . tag ) ,
421
445
} ) ;
422
446
}
@@ -436,13 +460,19 @@ export const dialog_open = (def) => {
436
460
}
437
461
}
438
462
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 > ) ;
463
+ let extra = null ;
464
+ if ( confirmation ) {
465
+ extra = < ExtraConfirmation text = { confirmation }
466
+ onChange = { val => {
467
+ confirmed = val ;
468
+ update_footer ( ) ;
469
+ } } /> ;
470
+ } else if ( def . Action && def . Action . Danger ) {
471
+ extra = (
472
+ < div >
473
+ < HelperText > < HelperTextItem variant = "error" > { def . Action . Danger } </ HelperTextItem > </ HelperText >
474
+ </ div > ) ;
475
+ }
446
476
447
477
return {
448
478
idle_message : ( running_promise
@@ -537,6 +567,14 @@ export const dialog_open = (def) => {
537
567
update ( ) ;
538
568
} ,
539
569
570
+ need_confirmation : ( conf ) => {
571
+ confirmation = conf ;
572
+ confirmed = false ;
573
+ def . Action . Danger = null ;
574
+ def . Action . DangerButton = true ;
575
+ update_footer ( ) ;
576
+ } ,
577
+
540
578
close : ( ) => {
541
579
dlg . footerProps . dialog_done ( ) ;
542
580
}
@@ -1203,13 +1241,16 @@ const teardown_block_name = use => {
1203
1241
name = block_name ( client . blocks [ use . block . CryptoBackingDevice ] || use . block ) ;
1204
1242
}
1205
1243
1206
- return name ;
1244
+ return name . replace ( / ^ \/ d e v \/ / , "" ) ;
1207
1245
} ;
1208
1246
1209
1247
export const TeardownMessage = ( usage , expect_single_unmount ) => {
1210
- if ( usage . length == 0 )
1248
+ if ( ! usage . Teardown )
1211
1249
return null ;
1212
1250
1251
+ if ( client . in_anaconda_mode ( ) && ! expect_single_unmount )
1252
+ return < AnacondaTeardownMessage usage = { usage } /> ;
1253
+
1213
1254
if ( is_expected_unmount ( usage , expect_single_unmount ) )
1214
1255
return < StopProcessesMessage mount_point = { expect_single_unmount } users = { usage [ 0 ] . users } /> ;
1215
1256
@@ -1250,6 +1291,36 @@ export const TeardownMessage = (usage, expect_single_unmount) => {
1250
1291
</ div > ) ;
1251
1292
} ;
1252
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
+
1253
1324
export function teardown_danger_message ( usage , expect_single_unmount ) {
1254
1325
if ( is_expected_unmount ( usage , expect_single_unmount ) )
1255
1326
return stop_processes_danger_message ( usage [ 0 ] . users ) ;
@@ -1268,24 +1339,51 @@ export function teardown_danger_message(usage, expect_single_unmount) {
1268
1339
}
1269
1340
}
1270
1341
1271
- export function init_active_usage_processes ( client , usage , expect_single_unmount ) {
1342
+ export function init_teardown_usage ( client , usage , expect_single_unmount ) {
1272
1343
return {
1273
- title : _ ( "Checking related processes" ) ,
1274
- func : dlg => {
1275
- 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 ) {
1276
1348
if ( u . usage == "mounted" ) {
1277
- return client . find_mount_users ( u . location )
1278
- . then ( users => {
1279
- u . users = users ;
1280
- } ) ;
1281
- } else
1282
- return Promise . resolve ( ) ;
1283
- } ) . then ( ( ) => {
1284
- 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 {
1285
1382
const msg = teardown_danger_message ( usage , expect_single_unmount ) ;
1286
1383
if ( msg )
1287
1384
dlg . add_danger ( msg ) ;
1288
- } ) ;
1385
+ }
1386
+ dlg . set_attribute ( "Teardown" , TeardownMessage ( usage , expect_single_unmount ) ) ;
1289
1387
}
1290
1388
} ;
1291
1389
}
0 commit comments