diff --git a/extension/chrome/settings/index.ts b/extension/chrome/settings/index.ts
index 40128469777..c2342edd23a 100644
--- a/extension/chrome/settings/index.ts
+++ b/extension/chrome/settings/index.ts
@@ -496,24 +496,67 @@ View.run(
private addKeyRowsHtml = async (privateKeys: KeyInfoWithIdentity[]) => {
let html = '';
const canRemoveKey = !this.clientConfiguration?.usesKeyManager();
+
+ // 1. Parse and prepare data
+ const rows = [];
for (let i = 0; i < privateKeys.length; i++) {
const ki = privateKeys[i];
const prv = await KeyUtil.parse(ki.private);
const created = new Date(prv.created);
const date = Str.monthName(created.getMonth()) + ' ' + created.getDate() + ', ' + created.getFullYear();
+ const isExpired = KeyUtil.expired(prv);
+ rows.push({
+ ki,
+ prv,
+ createdTime: prv.created,
+ dateStr: date,
+ isExpired,
+ originalIndex: i,
+ });
+ }
+
+ // 2. Sort: newest to oldest
+ rows.sort((a, b) => b.createdTime - a.createdTime);
+
+ // 3. Build HTML
+ // Header row
+ html += `
+
+ `;
+
+ for (const row of rows) {
+ const { ki, prv, dateStr, isExpired, originalIndex } = row;
+
let removeKeyBtn = '';
if (canRemoveKey && privateKeys.length > 1) {
- removeKeyBtn = `(`;
- html += `
${escapedLink} from ${Xss.escape(date)}${space}${fpHtml}${space}${KeyUtil.statusHtml(ki.id, prv)}${space}${removeKeyBtn}
`;
+ const escapedLink = `
${escapedEmail}`;
+ const fpHtml = `
${Str.spaced(Xss.escape(ki.fingerprints[0]))}`;
+
+ const rowClass = isExpired ? 'key-content-row expired' : 'key-content-row';
+ const opacityStyle = isExpired ? 'opacity: 0.7;' : '';
+
+ html += `
`;
+ html += `
${escapedLink}
`;
+ html += `
${Xss.escape(dateStr)}
`;
+ html += `
${fpHtml}
`;
+ html += `
+ ${KeyUtil.statusHtml(ki.id, prv)}
+ ${removeKeyBtn}
+
`;
html += `
`;
}
+
Xss.sanitizeAppend('.key_list', html);
+
$('.action_show_key').on(
'click',
this.setHandler(async target => {
diff --git a/extension/css/settings.css b/extension/css/settings.css
index 1afaa0c5849..47ec9699932 100644
--- a/extension/css/settings.css
+++ b/extension/css/settings.css
@@ -533,7 +533,7 @@ body#settings .settings-border .row {
}
body#settings .settings-border .key-content-row {
- padding: 7px 0;
+ padding: 4px 0;
border-bottom: 1px solid #e6e6e6;
}
@@ -544,6 +544,10 @@ body#settings .settings-border .key_list {
height: 219px;
}
+.key-content-row.expired {
+ opacity: 0.7;
+}
+
#settings-row .box img.security-icon {
width: 70px;
}
@@ -847,4 +851,4 @@ span.fc-badge {
.fc-badge-light-gray {
background-color: #bcbcbc;
color: #444 !important;
-}
+}
\ No newline at end of file
diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts
index ddea1b21bbf..720618cefa0 100644
--- a/test/source/tests/setup.ts
+++ b/test/source/tests/setup.ts
@@ -851,7 +851,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
await addKeyPopup.waitAndType('@input-armored-key', updatedKey.privateKey);
await addKeyPopup.waitAndType('#input_passphrase', passphrase);
await addKeyPopup.waitAndClick('.action_add_private_key', { delay: 1 });
- await Util.sleep(1);
+ await settingsPage.waitAll('@action-show-key-1');
await gmailPage.page.reload();
await Util.sleep(3);
await gmailPage.waitTillGone('@webmail-notification-notify_expiring_keys');
@@ -2250,7 +2250,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
const title = await settingsPage.read('@container-overlay-prompt-text');
expect(title).to.contain(
'Failed to store newly generated key on FlowCrypt Email Key Manager, ' +
- 'No key has been generated for reject.client.keypair@key-manager-autogen.flowcrypt.test yet. Please ask your administrator.'
+ 'No key has been generated for reject.client.keypair@key-manager-autogen.flowcrypt.test yet. Please ask your administrator.'
);
await settingsPage.click('@action-show-overlay-details');
await settingsPage.waitAll('@container-overlay-details');
@@ -2258,7 +2258,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
const details = await settingsPage.read('@container-overlay-details');
expect(details).to.contain(
`405 when PUT-ing https://localhost:${t.context.urls?.port}/flowcrypt-email-key-manager/v1/keys/private string: ` +
- 'privateKey -> No key has been generated for reject.client.keypair@key-manager-autogen.flowcrypt.test yet'
+ 'privateKey -> No key has been generated for reject.client.keypair@key-manager-autogen.flowcrypt.test yet'
);
expect(details).to.not.contain('PRIVATE KEY');
})