mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-29 06:10:47 +00:00
Compare commits
6 Commits
crowdin_de
...
316b7e5653
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
316b7e5653 | ||
|
|
00fc1da33c | ||
|
|
9ef93df54f | ||
|
|
fd9fdf6399 | ||
|
|
8fa1701e06 | ||
|
|
4abe83f8a9 |
@@ -35,6 +35,12 @@
|
||||
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://docs.pangolin.net/careers/join-us">
|
||||
<img src="https://img.shields.io/badge/🚀_We're_Hiring!-Join_Our_Team-brightgreen?style=for-the-badge" alt="We're Hiring!" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Start testing Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Активирана защитна стена.",
|
||||
"autoUpdatesEnabled": "Активирани автоматични актуализации.",
|
||||
"tpmAvailable": "TPM е на разположение.",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Protection на системната цялост (SIP).",
|
||||
"macosGatekeeperEnabled": "Gatekeeper.",
|
||||
"macosFirewallStealthMode": "Скрит режим на защитната стена.",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Firewall povolen",
|
||||
"autoUpdatesEnabled": "Automatické aktualizace povoleny",
|
||||
"tpmAvailable": "TPM k dispozici",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Firewall aktiviert",
|
||||
"autoUpdatesEnabled": "Automatische Updates aktiviert",
|
||||
"tpmAvailable": "TPM verfügbar",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Schutz der Systemintegrität (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Firewall Stealth-Modus",
|
||||
|
||||
@@ -2510,7 +2510,7 @@
|
||||
"firewallEnabled": "Firewall Enabled",
|
||||
"autoUpdatesEnabled": "Auto Updates Enabled",
|
||||
"tpmAvailable": "TPM Available",
|
||||
"windowsDefenderEnabled": "Windows Defender Enabled",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "System Integrity Protection (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Firewall Stealth Mode",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Cortafuegos activado",
|
||||
"autoUpdatesEnabled": "Actualizaciones automáticas habilitadas",
|
||||
"tpmAvailable": "TPM disponible",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Protección de integridad del sistema (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Modo Sigilo Firewall",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Pare-feu activé",
|
||||
"autoUpdatesEnabled": "Mises à jour automatiques activées",
|
||||
"tpmAvailable": "TPM disponible",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Protection contre l'intégrité du système (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Mode furtif du pare-feu",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Firewall Abilitato",
|
||||
"autoUpdatesEnabled": "Aggiornamenti Automatici Abilitati",
|
||||
"tpmAvailable": "TPM Disponibile",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Protezione Dell'Integrità Del Sistema (Sip)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Modo Furtivo Del Firewall",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "방화벽 활성화",
|
||||
"autoUpdatesEnabled": "자동 업데이트 활성화",
|
||||
"tpmAvailable": "TPM 사용 가능",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "시스템 무결성 보호 (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "방화벽 스텔스 모드",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Brannmur aktivert",
|
||||
"autoUpdatesEnabled": "Automatiske oppdateringer aktivert",
|
||||
"tpmAvailable": "TPM tilgjengelig",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "System Integritetsbeskyttelse (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Brannmur Usynlig Modus",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Firewall ingeschakeld",
|
||||
"autoUpdatesEnabled": "Auto Updates Ingeschakeld",
|
||||
"tpmAvailable": "TPM beschikbaar",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Systeemintegriteitsbescherming (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Firewall Verberg Modus",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Zapora włączona",
|
||||
"autoUpdatesEnabled": "Automatyczne aktualizacje włączone",
|
||||
"tpmAvailable": "TPM dostępne",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Ochrona integralności systemu (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Tryb Stealth zapory",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Firewall habilitado",
|
||||
"autoUpdatesEnabled": "Atualizações Automáticas Habilitadas",
|
||||
"tpmAvailable": "TPM disponível",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Proteção da Integridade do Sistema (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Modo Furtivo do Firewall",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Брандмауэр включен",
|
||||
"autoUpdatesEnabled": "Автоматические обновления включены",
|
||||
"tpmAvailable": "Доступно TPM",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Защита целостности системы (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Стилс-режим брандмауэра",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "Güvenlik Duvarı Etkin",
|
||||
"autoUpdatesEnabled": "Otomatik Güncellemeler Etkin",
|
||||
"tpmAvailable": "TPM Mevcut",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "Sistem Bütünlüğü Koruması (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Güvenlik Duvarı Gizlilik Modu",
|
||||
|
||||
@@ -2510,7 +2510,6 @@
|
||||
"firewallEnabled": "防火墙已启用",
|
||||
"autoUpdatesEnabled": "启用自动更新",
|
||||
"tpmAvailable": "TPM 可用",
|
||||
"windowsAntivirusEnabled": "Antivirus Enabled",
|
||||
"macosSipEnabled": "系统完整性保护 (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "防火墙隐形模式",
|
||||
|
||||
@@ -778,7 +778,7 @@ export const currentFingerprint = pgTable("currentFingerprint", {
|
||||
|
||||
// Windows-specific posture check information
|
||||
|
||||
windowsDefenderEnabled: boolean("windowsDefenderEnabled")
|
||||
windowsAntivirusEnabled: boolean("windowsAntivirusEnabled")
|
||||
.notNull()
|
||||
.default(false),
|
||||
|
||||
@@ -830,7 +830,7 @@ export const fingerprintSnapshots = pgTable("fingerprintSnapshots", {
|
||||
|
||||
// Windows-specific posture check information
|
||||
|
||||
windowsDefenderEnabled: boolean("windowsDefenderEnabled")
|
||||
windowsAntivirusEnabled: boolean("windowsAntivirusEnabled")
|
||||
.notNull()
|
||||
.default(false),
|
||||
|
||||
|
||||
@@ -475,7 +475,7 @@ export const currentFingerprint = sqliteTable("currentFingerprint", {
|
||||
|
||||
// Windows-specific posture check information
|
||||
|
||||
windowsDefenderEnabled: integer("windowsDefenderEnabled", {
|
||||
windowsAntivirusEnabled: integer("windowsAntivirusEnabled", {
|
||||
mode: "boolean"
|
||||
})
|
||||
.notNull()
|
||||
@@ -549,7 +549,7 @@ export const fingerprintSnapshots = sqliteTable("fingerprintSnapshots", {
|
||||
|
||||
// Windows-specific posture check information
|
||||
|
||||
windowsDefenderEnabled: integer("windowsDefenderEnabled", {
|
||||
windowsAntivirusEnabled: integer("windowsAntivirusEnabled", {
|
||||
mode: "boolean"
|
||||
})
|
||||
.notNull()
|
||||
|
||||
@@ -12,6 +12,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { getUserDeviceName } from "@server/db/names";
|
||||
import { build } from "@server/build";
|
||||
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||
|
||||
const getClientSchema = z.strictObject({
|
||||
clientId: z
|
||||
@@ -58,7 +59,7 @@ type PostureData = {
|
||||
firewallEnabled?: boolean | null;
|
||||
autoUpdatesEnabled?: boolean | null;
|
||||
tpmAvailable?: boolean | null;
|
||||
windowsDefenderEnabled?: boolean | null;
|
||||
windowsAntivirusEnabled?: boolean | null;
|
||||
macosSipEnabled?: boolean | null;
|
||||
macosGatekeeperEnabled?: boolean | null;
|
||||
macosFirewallStealthMode?: boolean | null;
|
||||
@@ -75,7 +76,7 @@ function getPlatformPostureData(
|
||||
const normalizedPlatform = platform?.toLowerCase() || "unknown";
|
||||
const posture: PostureData = {};
|
||||
|
||||
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Defender
|
||||
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Antivirus status
|
||||
if (normalizedPlatform === "windows") {
|
||||
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||
@@ -83,14 +84,11 @@ function getPlatformPostureData(
|
||||
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||
}
|
||||
if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) {
|
||||
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
||||
}
|
||||
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||
}
|
||||
if (fingerprint.windowsDefenderEnabled !== null && fingerprint.windowsDefenderEnabled !== undefined) {
|
||||
posture.windowsDefenderEnabled = fingerprint.windowsDefenderEnabled;
|
||||
if (fingerprint.windowsAntivirusEnabled !== null && fingerprint.windowsAntivirusEnabled !== undefined) {
|
||||
posture.windowsAntivirusEnabled = fingerprint.windowsAntivirusEnabled;
|
||||
}
|
||||
}
|
||||
// macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode
|
||||
@@ -137,15 +135,10 @@ function getPlatformPostureData(
|
||||
}
|
||||
// iOS: Biometric configuration
|
||||
else if (normalizedPlatform === "ios") {
|
||||
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||
}
|
||||
// none supported yet
|
||||
}
|
||||
// Android: Screen lock, Biometric configuration, Hard drive encryption
|
||||
else if (normalizedPlatform === "android") {
|
||||
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||
}
|
||||
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||
}
|
||||
@@ -258,12 +251,18 @@ export async function getClient(
|
||||
: null;
|
||||
|
||||
// Build posture data if available (platform-specific)
|
||||
// Only return posture data if org is licensed/subscribed
|
||||
let postureData: PostureData | null = null;
|
||||
if (build !== "oss") {
|
||||
postureData = getPlatformPostureData(
|
||||
client.currentFingerprint?.platform || null,
|
||||
client.currentFingerprint
|
||||
const isOrgLicensed = await isLicensedOrSubscribed(
|
||||
client.clients.orgId
|
||||
);
|
||||
if (isOrgLicensed) {
|
||||
postureData = getPlatformPostureData(
|
||||
client.currentFingerprint?.platform || null,
|
||||
client.currentFingerprint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const data: GetClientResponse = {
|
||||
|
||||
@@ -22,7 +22,7 @@ function fingerprintSnapshotHash(fingerprint: any, postures: any): string {
|
||||
autoUpdatesEnabled: postures.autoUpdatesEnabled ?? false,
|
||||
tpmAvailable: postures.tpmAvailable ?? false,
|
||||
|
||||
windowsDefenderEnabled: postures.windowsDefenderEnabled ?? false,
|
||||
windowsAntivirusEnabled: postures.windowsAntivirusEnabled ?? false,
|
||||
|
||||
macosSipEnabled: postures.macosSipEnabled ?? false,
|
||||
macosGatekeeperEnabled: postures.macosGatekeeperEnabled ?? false,
|
||||
@@ -87,7 +87,7 @@ export async function handleFingerprintInsertion(
|
||||
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||
tpmAvailable: postures.tpmAvailable,
|
||||
|
||||
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||
windowsAntivirusEnabled: postures.windowsAntivirusEnabled,
|
||||
|
||||
macosSipEnabled: postures.macosSipEnabled,
|
||||
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||
@@ -117,7 +117,7 @@ export async function handleFingerprintInsertion(
|
||||
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||
tpmAvailable: postures.tpmAvailable,
|
||||
|
||||
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||
windowsAntivirusEnabled: postures.windowsAntivirusEnabled,
|
||||
|
||||
macosSipEnabled: postures.macosSipEnabled,
|
||||
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||
@@ -162,7 +162,7 @@ export async function handleFingerprintInsertion(
|
||||
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||
tpmAvailable: postures.tpmAvailable,
|
||||
|
||||
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||
windowsAntivirusEnabled: postures.windowsAntivirusEnabled,
|
||||
|
||||
macosSipEnabled: postures.macosSipEnabled,
|
||||
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||
@@ -197,7 +197,7 @@ export async function handleFingerprintInsertion(
|
||||
autoUpdatesEnabled: postures.autoUpdatesEnabled,
|
||||
tpmAvailable: postures.tpmAvailable,
|
||||
|
||||
windowsDefenderEnabled: postures.windowsDefenderEnabled,
|
||||
windowsAntivirusEnabled: postures.windowsAntivirusEnabled,
|
||||
|
||||
macosSipEnabled: postures.macosSipEnabled,
|
||||
macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
|
||||
|
||||
@@ -46,6 +46,12 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Handling fingerprint insertion for olm register...", {
|
||||
olmId: olm.olmId,
|
||||
fingerprint,
|
||||
postures
|
||||
});
|
||||
|
||||
await handleFingerprintInsertion(olm, fingerprint, postures);
|
||||
|
||||
if (
|
||||
|
||||
@@ -49,7 +49,7 @@ export default async function migration() {
|
||||
"firewallEnabled" boolean DEFAULT false NOT NULL,
|
||||
"autoUpdatesEnabled" boolean DEFAULT false NOT NULL,
|
||||
"tpmAvailable" boolean DEFAULT false NOT NULL,
|
||||
"windowsDefenderEnabled" boolean DEFAULT false NOT NULL,
|
||||
"windowsAntivirusEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosSipEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosGatekeeperEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosFirewallStealthMode" boolean DEFAULT false NOT NULL,
|
||||
@@ -75,7 +75,7 @@ export default async function migration() {
|
||||
"firewallEnabled" boolean DEFAULT false NOT NULL,
|
||||
"autoUpdatesEnabled" boolean DEFAULT false NOT NULL,
|
||||
"tpmAvailable" boolean DEFAULT false NOT NULL,
|
||||
"windowsDefenderEnabled" boolean DEFAULT false NOT NULL,
|
||||
"windowsAntivirusEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosSipEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosGatekeeperEnabled" boolean DEFAULT false NOT NULL,
|
||||
"macosFirewallStealthMode" boolean DEFAULT false NOT NULL,
|
||||
|
||||
@@ -53,7 +53,7 @@ CREATE TABLE 'currentFingerprint' (
|
||||
'firewallEnabled' integer DEFAULT false NOT NULL,
|
||||
'autoUpdatesEnabled' integer DEFAULT false NOT NULL,
|
||||
'tpmAvailable' integer DEFAULT false NOT NULL,
|
||||
'windowsDefenderEnabled' integer DEFAULT false NOT NULL,
|
||||
'windowsAntivirusEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosSipEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosGatekeeperEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosFirewallStealthMode' integer DEFAULT false NOT NULL,
|
||||
@@ -83,7 +83,7 @@ CREATE TABLE 'fingerprintSnapshots' (
|
||||
'firewallEnabled' integer DEFAULT false NOT NULL,
|
||||
'autoUpdatesEnabled' integer DEFAULT false NOT NULL,
|
||||
'tpmAvailable' integer DEFAULT false NOT NULL,
|
||||
'windowsDefenderEnabled' integer DEFAULT false NOT NULL,
|
||||
'windowsAntivirusEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosSipEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosGatekeeperEnabled' integer DEFAULT false NOT NULL,
|
||||
'macosFirewallStealthMode' integer DEFAULT false NOT NULL,
|
||||
|
||||
@@ -656,17 +656,17 @@ export default function GeneralPage() {
|
||||
</InfoSection>
|
||||
)}
|
||||
|
||||
{client.posture.windowsDefenderEnabled !== null &&
|
||||
client.posture.windowsDefenderEnabled !== undefined && (
|
||||
{client.posture.windowsAntivirusEnabled !== null &&
|
||||
client.posture.windowsAntivirusEnabled !== undefined && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("windowsDefenderEnabled")}
|
||||
{t("windowsAntivirusEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{isPaidUser
|
||||
? formatPostureValue(
|
||||
client.posture
|
||||
.windowsDefenderEnabled
|
||||
.windowsAntivirusEnabled
|
||||
)
|
||||
: "-"}
|
||||
</InfoSectionContent>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from "react";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { Download } from "lucide-react";
|
||||
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
||||
import { SiAndroid } from "react-icons/si";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import DismissableBanner from "./DismissableBanner";
|
||||
@@ -61,6 +62,34 @@ export const ClientDownloadBanner = () => {
|
||||
Linux
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://pangolin.net/downloads/ios"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<FaApple className="w-4 h-4" />
|
||||
iOS
|
||||
</Button>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://pangolin.net/downloads/android"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<SiAndroid className="w-4 h-4" />
|
||||
Android
|
||||
</Button>
|
||||
</Link>
|
||||
</DismissableBanner>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user