({ config, dark })
| 904 | } |
| 905 | |
| 906 | function renderAdminEncryptionSection({ config, dark }) { |
| 907 | const host = document.getElementById("encryptionContent"); |
| 908 | if (!host) return; |
| 909 | |
| 910 | const enc = (config && config.encryption && typeof config.encryption === 'object') ? config.encryption : {}; |
| 911 | const supported = !!enc.supported; |
| 912 | const hasMasterKey = !!enc.hasMasterKey; |
| 913 | const source = String(enc.source || 'missing'); |
| 914 | const lockedByEnv = !!enc.lockedByEnv; |
| 915 | const envPresent = !!enc.envPresent; |
| 916 | const filePresent = !!enc.filePresent; |
| 917 | const canGenerateKey = !lockedByEnv && !filePresent; |
| 918 | |
| 919 | const statusPill = (ok, label) => ` |
| 920 | <span class="badge badge-pill ${ok ? 'badge-success' : 'badge-secondary'}" style="margin-left:6px;"> |
| 921 | ${label} |
| 922 | </span> |
| 923 | `; |
| 924 | |
| 925 | const sourceLabel = (() => { |
| 926 | if (source === 'env') return 'Env (FR_ENCRYPTION_MASTER_KEY)'; |
| 927 | if (source === 'env_invalid') return 'Env present but invalid'; |
| 928 | if (source === 'file') return 'Key file (META_DIR/encryption_master.key)'; |
| 929 | if (source === 'file_invalid') return 'Key file present but invalid'; |
| 930 | return 'Missing'; |
| 931 | })(); |
| 932 | |
| 933 | host.innerHTML = ` |
| 934 | <div class="card" style="border:1px solid ${dark ? '#3a3a3a' : '#eaeaea'}; border-radius:10px; padding:12px; background:${dark ? '#1f1f1f' : '#fdfdfd'};"> |
| 935 | <div class="d-flex align-items-center" style="gap:10px; margin-bottom:6px;"> |
| 936 | <i class="material-icons" aria-hidden="true">enhanced_encryption</i> |
| 937 | <div style="font-weight:600;"> |
| 938 | ${tf("encryption_at_rest", "Encryption at rest")} |
| 939 | ${statusPill(supported, supported ? tf("supported", "Supported") : tf("not_supported", "Not supported"))} |
| 940 | ${statusPill(hasMasterKey, hasMasterKey ? tf("configured", "Configured") : tf("missing", "Missing"))} |
| 941 | </div> |
| 942 | </div> |
| 943 | |
| 944 | <div class="small text-muted" style="margin-bottom:8px;"> |
| 945 | ${tf("encryption_help_short", "Folder encryption requires a server master key. Env overrides the key file.")} |
| 946 | </div> |
| 947 | |
| 948 | <div class="small" style="line-height:1.5;"> |
| 949 | <div><strong>${tf("master_key_source", "Master key source")}:</strong> ${escapeHTML(sourceLabel)}</div> |
| 950 | <div><strong>${tf("env_present", "Env present")}:</strong> ${envPresent ? 'Yes' : 'No'}${lockedByEnv ? ' (locked)' : ''}</div> |
| 951 | <div><strong>${tf("key_file_present", "Key file present")}:</strong> ${filePresent ? 'Yes' : 'No'}</div> |
| 952 | </div> |
| 953 | |
| 954 | <hr class="admin-divider" style="margin:10px 0;"> |
| 955 | |
| 956 | <div class="d-flex flex-wrap" style="gap:8px; align-items:center;"> |
| 957 | <button type="button" class="btn btn-sm btn-secondary" id="frEncGenerateKeyBtn" ${canGenerateKey ? '' : 'disabled'}> |
| 958 | ${tf("generate_key_file", "Generate key file")} |
| 959 | </button> |
| 960 | <button type="button" class="btn btn-sm btn-outline-danger" id="frEncClearKeyBtn" ${lockedByEnv ? 'disabled' : ''}> |
| 961 | ${tf("clear_key_file", "Clear key file")} |
| 962 | </button> |
| 963 | ${lockedByEnv ? `<div class="small text-warning">${tf("locked_by_env", "Locked by FR_ENCRYPTION_MASTER_KEY env override.")}</div>` : ''} |
no test coverage detected