feat: Add favicon display functionality in BillingManager for improved visual identification of providers
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m41s

This commit is contained in:
2025-08-08 12:03:20 +07:00
parent 560120f5b8
commit ebfc5a9fab
+52 -40
View File
@@ -325,6 +325,30 @@ function BillingManager() {
return String.fromCodePoint(...codePoints); return String.fromCodePoint(...codePoints);
} }
// Favicon для провайдера по ссылке входа
function normalizeToUrl(value) {
if (!value) return '';
try {
const hasProtocol = /^https?:\/\//i.test(value);
const url = new URL(hasProtocol ? value : `https://${value}`);
return url.toString();
} catch {
return '';
}
}
function getFaviconUrl(loginUrl) {
const normalized = normalizeToUrl(loginUrl);
if (!normalized) return '';
try {
const { hostname } = new URL(normalized);
// DuckDuckGo Icons API: быстрая выдача favicon по домену
return `https://icons.duckduckgo.com/ip3/${hostname}.ico`;
} catch {
return '';
}
}
function getCountryFlag(countryRaw) { function getCountryFlag(countryRaw) {
if (!countryRaw) return ''; if (!countryRaw) return '';
const value = String(countryRaw).trim(); const value = String(countryRaw).trim();
@@ -717,9 +741,19 @@ function BillingManager() {
</div> </div>
</td> </td>
<td> <td>
<span className="badge bg-green-lt text-green"> <div className="d-flex align-items-center gap-2">
{item.provider} {item.loginUrl && (
</span> <img
src={getFaviconUrl(item.loginUrl)}
alt=""
width="16"
height="16"
style={{ borderRadius: 3 }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
)}
<span className="badge bg-green-lt text-green">{item.provider}</span>
</div>
</td> </td>
<td> <td>
<div className="fw-bold"> <div className="fw-bold">
@@ -880,7 +914,19 @@ function BillingManager() {
<tr key={r._key}> <tr key={r._key}>
<td> <td>
<div className="fw-bold">{r.server.hostName}</div> <div className="fw-bold">{r.server.hostName}</div>
<div className="text-muted small">{r.server.provider}</div> <div className="text-muted small d-flex align-items-center gap-2">
{r.server.loginUrl && (
<img
src={getFaviconUrl(r.server.loginUrl)}
alt=""
width="14"
height="14"
style={{ borderRadius: 3 }}
onError={(e) => { e.currentTarget.style.display = 'none'; }}
/>
)}
<span>{r.server.provider}</span>
</div>
</td> </td>
<td>{formatDate(r.date)}</td> <td>{formatDate(r.date)}</td>
<td> <td>
@@ -1318,42 +1364,8 @@ function EditBillingModal({ show, item, onItemChange, onSubmit, onClose }) {
/> />
</div> </div>
{/* Исторические платежи */} {/* Дополнительно */}
<div className="col-12"> <div className="col-md-12">
<h6 className="text-muted mb-3 mt-4">Исторические платежи</h6>
</div>
<div className="col-md-6">
<label className="form-label">Дата последнего платежа</label>
<input
type="date"
className="form-control"
value={item.lastPaymentDate || ''}
onChange={(e) => onItemChange({ ...item, lastPaymentDate: e.target.value })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Сумма последнего платежа</label>
<input
type="number"
step="0.01"
className="form-control"
value={item.lastPaymentAmount || 0}
onChange={(e) => onItemChange({ ...item, lastPaymentAmount: parseFloat(e.target.value) || 0 })}
/>
</div>
<div className="col-md-6">
<label className="form-label">Валюта последнего платежа</label>
<select
className="form-select"
value={item.lastPaymentCurrency || 'USD'}
onChange={(e) => onItemChange({ ...item, lastPaymentCurrency: e.target.value })}
>
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="RUB">RUB</option>
</select>
</div>
<div className="col-md-6">
<label className="form-label">Заметки</label> <label className="form-label">Заметки</label>
<input <input
type="text" type="text"