feat: version 2.8.1 -- catalogo de logos de marca en Contraseñas (simple-icons, CC0)

Amplia el selector de icono de una credencial con ~70 logos reales de marcas conocidas
(Google, GitHub, Cloudflare, cPanel, MikroTik, WordPress, etc.), empaquetados localmente
desde el paquete simple-icons (dominio publico) y tree-shakeados por Vite. Se mantiene el
criterio de privacidad ya establecido: ninguna consulta externa de favicon por entrada.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-01 14:16:31 -05:00
parent 551ed9b790
commit 8214df62ae
9 changed files with 139 additions and 22 deletions
+46
View File
@@ -0,0 +1,46 @@
// Catalogo estatico de logos de marca para el icono de una entrada de Contraseñas.
// Fuente: paquete npm "simple-icons" (CC0-1.0 / dominio publico) -- se empaqueta localmente,
// NUNCA se consulta un servicio externo de favicon por entrada (eso filtraria a terceros que
// urls internas guarda cada usuario). simple-icons ya excluye marcas con politicas de marca mas
// estrictas (ej. Microsoft, LinkedIn, AWS, Windows no estan incluidas en el paquete).
// Debe coincidir con backend/src/modules/vault/service.js#BRAND_ICON_KEYS.
import {
siGoogle, siGmail, siGoogledrive, siGooglecloud, siProtonmail, siZoho,
siGithub, siGitlab, siDigitalocean, siCloudflare, siNamecheap, siGodaddy,
siOvh, siVultr, siNetlify, siVercel, siCpanel, siPlesk, siWebmin,
siNginx, siApache, siMysql, siMariadb, siPostgresql, siDocker,
siCisco, siFortinet, siMikrotik, siUbiquiti, siTplink, siOpenvpn, siWireguard,
siPfsense, siOpnsense, siQnap, siSynology, siNordvpn, siBitdefender,
siTrendmicro, siAvast, siKaspersky,
siWhatsapp, siTelegram, siZoom, siDiscord, siJitsi,
siFacebook, siInstagram, siX,
siPaypal, siStripe,
siTeamviewer, siAnydesk,
siDropbox, siBackblaze, siMega, siVeeam, siNextcloud,
siBitwarden, siLastpass,
siLinux, siUbuntu, siApple, siRaspberrypi,
siWordpress, siMailchimp, siFirebase,
} from 'simple-icons';
export const BRAND_ICONS = {
b_google: siGoogle, b_gmail: siGmail, b_googledrive: siGoogledrive, b_googlecloud: siGooglecloud,
b_protonmail: siProtonmail, b_zoho: siZoho,
b_github: siGithub, b_gitlab: siGitlab, b_digitalocean: siDigitalocean, b_cloudflare: siCloudflare,
b_namecheap: siNamecheap, b_godaddy: siGodaddy, b_ovh: siOvh, b_vultr: siVultr,
b_netlify: siNetlify, b_vercel: siVercel, b_cpanel: siCpanel, b_plesk: siPlesk, b_webmin: siWebmin,
b_nginx: siNginx, b_apache: siApache, b_mysql: siMysql, b_mariadb: siMariadb,
b_postgresql: siPostgresql, b_docker: siDocker,
b_cisco: siCisco, b_fortinet: siFortinet, b_mikrotik: siMikrotik, b_ubiquiti: siUbiquiti,
b_tplink: siTplink, b_openvpn: siOpenvpn, b_wireguard: siWireguard, b_pfsense: siPfsense,
b_opnsense: siOpnsense, b_qnap: siQnap, b_synology: siSynology,
b_nordvpn: siNordvpn, b_bitdefender: siBitdefender, b_trendmicro: siTrendmicro,
b_avast: siAvast, b_kaspersky: siKaspersky,
b_whatsapp: siWhatsapp, b_telegram: siTelegram, b_zoom: siZoom, b_discord: siDiscord, b_jitsi: siJitsi,
b_facebook: siFacebook, b_instagram: siInstagram, b_x: siX,
b_paypal: siPaypal, b_stripe: siStripe,
b_teamviewer: siTeamviewer, b_anydesk: siAnydesk,
b_dropbox: siDropbox, b_backblaze: siBackblaze, b_mega: siMega, b_veeam: siVeeam, b_nextcloud: siNextcloud,
b_bitwarden: siBitwarden, b_lastpass: siLastpass,
b_linux: siLinux, b_ubuntu: siUbuntu, b_apple: siApple, b_raspberrypi: siRaspberrypi,
b_wordpress: siWordpress, b_mailchimp: siMailchimp, b_firebase: siFirebase,
};
@@ -18,25 +18,36 @@ import { PasswordInput } from '../../components/PasswordInput.jsx';
import { DetailPopup } from '../../components/DetailPopup.jsx';
import { Flyout, FlyoutItem } from '../../components/Flyout.jsx';
import { SearchInput } from '../../components/SearchInput.jsx';
import { BRAND_ICONS } from '../../data/brandIcons.js';
const EMPTY_FORM = { title: '', url: '', username: '', password: '', notes: '', iconKey: '' };
const AUTO_LOCK_MS = 3 * 60 * 1000; // 3 minutos: salir solo del menu si queda abierto sin atencion
// Catalogo fijo de iconos (debe coincidir con backend/src/modules/vault/service.js#ICON_KEYS) --
// Catalogo fijo de iconos genericos (debe coincidir con backend/src/modules/vault/service.js#ICON_KEYS) --
// a proposito NO se usa ningun servicio externo de favicon (ej. google/s2/favicons): revelaria a
// terceros que urls internas tiene guardadas cada usuario, inaceptable para un repositorio de
// credenciales. El usuario elige uno de esta lista, o se deja "Auto" (inicial del nombre).
// credenciales. El usuario elige uno de esta lista, o uno de marca (BRAND_ICONS), o se deja
// "Auto" (inicial del nombre).
const ICONS = {
mail: Mail, globe: Globe, server: Server, database: Database, cloud: Cloud, shield: Shield,
lock: Lock, wifi: Wifi, card: CreditCard, building: Building2, printer: Printer,
phone: Smartphone, key: KeyRound, disk: HardDrive, router: Router, user: UserCog,
};
function BrandSvg({ icon, size = 22 }) {
return (
<svg viewBox="0 0 24 24" width={size} height={size} fill={`#${icon.hex}`}>
<path d={icon.path} />
</svg>
);
}
function EntryIcon({ iconKey, title, size = 22 }) {
const Icon = ICONS[iconKey];
const brand = BRAND_ICONS[iconKey];
return (
<div className="vault-card-icon">
{Icon ? <Icon size={size} /> : <span>{(title || '?').trim().charAt(0).toUpperCase()}</span>}
<div className={`vault-card-icon ${brand ? 'brand' : ''}`}>
{Icon ? <Icon size={size} /> : brand ? <BrandSvg icon={brand} size={size} /> : <span>{(title || '?').trim().charAt(0).toUpperCase()}</span>}
</div>
);
}
@@ -344,6 +355,19 @@ export default function ContrasenasPage() {
</button>
))}
</div>
<div className="vault-icon-picker vault-icon-picker-brands">
{Object.entries(BRAND_ICONS).map(([key, icon]) => (
<button
type="button"
key={key}
className={`vault-icon-option brand ${form.iconKey === key ? 'selected' : ''}`}
title={icon.title}
onClick={() => setForm((f) => ({ ...f, iconKey: key }))}
>
<BrandSvg icon={icon} size={16} />
</button>
))}
</div>
</Field>
)}
+3
View File
@@ -379,6 +379,7 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
text-overflow: ellipsis; white-space: nowrap;
}
.vault-icon-picker { display: flex; flex-wrap: wrap; gap: 8px; }
.vault-icon-picker-brands { max-height: 150px; overflow-y: auto; padding-top: 8px; margin-top: 4px; border-top: 1px dashed rgb(var(--line)); }
.vault-icon-option {
width: 36px; height: 36px; border-radius: 999px; border: 1px solid rgb(var(--line));
background: rgb(var(--field)); color: rgb(var(--body)); cursor: pointer;
@@ -386,6 +387,8 @@ table.list-table { width: 100%; border-collapse: collapse; font-size: .8rem; }
}
.vault-icon-option:hover { border-color: rgb(var(--accent) / .6); }
.vault-icon-option.selected { border-color: rgb(var(--accent)); background: rgb(var(--accent) / .15); color: rgb(var(--accent)); }
.vault-icon-option.brand, .vault-card-icon.brand { background: #fff; }
.vault-icon-option.brand.selected { border-color: rgb(var(--accent)); }
/* --- DetailPopup (ver DetailPopup.jsx) ---
Al elegir un item de un listado, la lista queda inactiva detras del overlay y el popup