Revert "perf: replace @tabler packages with lucide-react and static CSS (-70 MB node_modules)"
Docker / build (push) Failing after 17s

This reverts commit a1227a13e8.
This commit is contained in:
Denozordec
2026-03-21 02:15:20 +07:00
parent a1227a13e8
commit d717719e35
13 changed files with 159 additions and 77 deletions
-1
View File
@@ -5,7 +5,6 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vps-tracker</title>
<link rel="stylesheet" href="/css/tabler.min.css" />
</head>
<body>
<div id="root"></div>
+79 -11
View File
@@ -14,6 +14,8 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@tabler/core": "^1.4.0",
"@tabler/icons-react": "^3.40.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
@@ -23,7 +25,6 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"lucide-react": "^0.577.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.1",
@@ -1020,6 +1021,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.3",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
@@ -1377,6 +1389,52 @@
"win32"
]
},
"node_modules/@tabler/core": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tabler/core/-/core-1.4.0.tgz",
"integrity": "sha512-5BigzOlbOH9N0Is4u0rYNRCiwtnUXWO57K9zwuscygcicAa8UV9MGaS4zTgQsZEtZ9tsNANhN/YD8gCBGKYCiw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "5.3.7"
},
"engines": {
"node": ">=20"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.40.0.tgz",
"integrity": "sha512-V/Q4VgNPKubRTiLdmWjV/zscYcj5IIk+euicUtaVVqF6luSC9rDngYWgST5/yh3Mrg/mYUwRv1YVTk71Jp0twQ==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons-react": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.40.0.tgz",
"integrity": "sha512-oO5+6QCnna4a//mYubx4euZfECtzQZFDGsDMIdzZUhbdyBCT+3bRVFBPueGIcemWld4Vb/0UQ39C/cmGfGylAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tabler/icons": "3.40.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
},
"peerDependencies": {
"react": ">= 16"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1628,6 +1686,26 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/bootstrap": {
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -2952,16 +3030,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.577.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
"integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
"dev": true,
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+2 -1
View File
@@ -17,6 +17,8 @@
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@tabler/core": "^1.4.0",
"@tabler/icons-react": "^3.40.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
@@ -26,7 +28,6 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"lucide-react": "^0.577.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.1",
-9
View File
File diff suppressed because one or more lines are too long
+22 -22
View File
@@ -1,28 +1,28 @@
import { NavLink, useLocation } from 'react-router-dom'
import {
Building2,
BarChart,
BarChart2,
Coins,
CreditCard,
LayoutDashboard,
Settings,
Server,
ServerCog,
Wallet,
} from 'lucide-react'
IconBuildingSkyscraper,
IconChartBar,
IconChartHistogram,
IconCoin,
IconCreditCardPay,
IconLayoutDashboard,
IconSettings,
IconServer,
IconServer2,
IconWallet,
} from '@tabler/icons-react'
const menuItems = [
{ to: '/dashboard', label: 'Дашборд', icon: LayoutDashboard },
{ to: '/vps', label: 'VPS', icon: Server },
{ to: '/tariffs', label: 'Активные тарифы', icon: ServerCog },
{ to: '/providers', label: 'Хостеры', icon: Building2 },
{ to: '/accounts', label: 'Аккаунты хостеров', icon: Wallet },
{ to: '/payments', label: 'Платежи', icon: CreditCard },
{ to: '/balance', label: 'Баланс и списания', icon: Coins },
{ to: '/reports', label: 'Отчёты', icon: BarChart2 },
{ to: '/resources', label: 'Ресурсы', icon: BarChart },
{ to: '/settings', label: 'Настройки', icon: Settings },
{ to: '/dashboard', label: 'Дашборд', icon: IconLayoutDashboard },
{ to: '/vps', label: 'VPS', icon: IconServer },
{ to: '/tariffs', label: 'Активные тарифы', icon: IconServer2 },
{ to: '/providers', label: 'Хостеры', icon: IconBuildingSkyscraper },
{ to: '/accounts', label: 'Аккаунты хостеров', icon: IconWallet },
{ to: '/payments', label: 'Платежи', icon: IconCreditCardPay },
{ to: '/balance', label: 'Баланс и списания', icon: IconCoin },
{ to: '/reports', label: 'Отчёты', icon: IconChartHistogram },
{ to: '/resources', label: 'Ресурсы', icon: IconChartBar },
{ to: '/settings', label: 'Настройки', icon: IconSettings },
]
export function AppLayout({ children }) {
@@ -44,7 +44,7 @@ export function AppLayout({ children }) {
}`}
>
<span className="nav-link-icon d-md-none d-lg-inline-block">
<item.icon size={18} strokeWidth={1.75} />
<item.icon size={18} stroke={1.75} />
</span>
<span className="nav-link-title">{item.label}</span>
</NavLink>
+2 -2
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Folder } from 'lucide-react'
import { IconFolder } from '@tabler/icons-react'
import { fetchProjectSuggestions } from '../lib/api'
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
@@ -184,7 +184,7 @@ export function ProjectSuggestInput({
onMouseEnter={() => setActiveIndex(idx)}
>
<span className="text-secondary project-suggest-folder">
<Folder size={18} strokeWidth={1.5} />
<IconFolder size={18} stroke={1.5} />
</span>
<span className="text-truncate">{highlightMatch(name, value)}</span>
</button>
+2
View File
@@ -2,6 +2,8 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import '@tabler/core/dist/css/tabler.min.css'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
+5 -5
View File
@@ -17,7 +17,7 @@ import {
accountBillmanagerUiReady,
accountUsesBillmanagerBalanceApi,
} from '../lib/billmanager-ui'
import { RefreshCw, PlugZap } from 'lucide-react'
import { IconRefresh, IconPlugConnected } from '@tabler/icons-react'
const emptyForm = {
providerId: '',
@@ -314,7 +314,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
{syncLoadingAll ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<RefreshCw size={16} className="me-1" />
<IconRefresh size={16} className="me-1" />
)}
Синхронизировать VPS
</button>
@@ -400,7 +400,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
{balanceLoadingId === account.id ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<RefreshCw size={14} className="me-1" />
<IconRefresh size={14} className="me-1" />
)}
Баланс
</button>
@@ -414,7 +414,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
{syncLoadingId === account.id ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<RefreshCw size={14} className="me-1" />
<IconRefresh size={14} className="me-1" />
)}
Синхронизировать
</button>
@@ -568,7 +568,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
{testConnectionLoading ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<PlugZap size={14} className="me-1" />
<IconPlugConnected size={14} className="me-1" />
)}
Проверить соединение
</button>
+10 -5
View File
@@ -9,7 +9,12 @@ import { EmptyState } from '../components/EmptyState'
import { ExpenseChart } from '../components/ExpenseChart'
import { PageHeader } from '../components/PageHeader'
import { ProviderPieChart } from '../components/ProviderPieChart'
import { Banknote, Clock4, Server, Wallet } from 'lucide-react'
import {
IconCash,
IconClockHour4,
IconServer,
IconWallet,
} from '@tabler/icons-react'
export function DashboardPage({ db = {}, settings, ratesData }) {
const [dashTab, setDashTab] = useState('overview')
@@ -268,7 +273,7 @@ export function DashboardPage({ db = {}, settings, ratesData }) {
<div className="d-flex align-items-center justify-content-between">
<div className="text-secondary">Активные VPS</div>
<span className="metric-icon bg-blue-lt text-blue">
<Server size={18} />
<IconServer size={18} />
</span>
</div>
<div className="stat-value">{activeVpsCount}</div>
@@ -281,7 +286,7 @@ export function DashboardPage({ db = {}, settings, ratesData }) {
<div className="d-flex align-items-center justify-content-between">
<div className="text-secondary">Расходы за месяц</div>
<span className="metric-icon bg-green-lt text-green">
<Banknote size={18} />
<IconCash size={18} />
</span>
</div>
<div className="stat-value">{formatCurrency(monthExpenses, baseCurrency)}</div>
@@ -300,7 +305,7 @@ export function DashboardPage({ db = {}, settings, ratesData }) {
<div className="d-flex align-items-center justify-content-between">
<div className="text-secondary">Аккаунтов хостеров</div>
<span className="metric-icon bg-yellow-lt text-yellow">
<Clock4 size={18} />
<IconClockHour4 size={18} />
</span>
</div>
<div className="stat-value">{providerAccounts.length}</div>
@@ -313,7 +318,7 @@ export function DashboardPage({ db = {}, settings, ratesData }) {
<div className="d-flex align-items-center justify-content-between">
<div className="text-secondary">Суммарный баланс</div>
<span className="metric-icon bg-purple-lt text-purple">
<Wallet size={18} />
<IconWallet size={18} />
</span>
</div>
<div className="stat-value">{formatCurrency(totalBalance, baseCurrency)}</div>
+2 -2
View File
@@ -1,5 +1,5 @@
import { useMemo, useState } from 'react'
import { Pencil } from 'lucide-react'
import { IconEdit } from '@tabler/icons-react'
import { paymentTypeLabel } from '../lib/utils'
import { UiModal } from '../components/UiModal'
import { EmptyState } from '../components/EmptyState'
@@ -155,7 +155,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
className="btn btn-sm btn-outline-primary me-1"
onClick={() => onEdit(payment)}
>
<Pencil size={14} className="me-1" />
<IconEdit size={14} className="me-1" />
Изменить
</button>
<button
+4 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react'
import { Plus, Send, Trash2 } from 'lucide-react'
import { IconPlus, IconSend, IconTrash } from '@tabler/icons-react'
import { PageHeader } from '../components/PageHeader'
import {
downloadBackupDatabaseBlob,
@@ -231,7 +231,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
className="btn btn-primary"
onClick={addCustomField}
>
<Plus size={16} />
<IconPlus size={16} />
</button>
</div>
{customFields.length > 0 ? (
@@ -244,7 +244,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
className="btn btn-sm btn-outline-danger"
onClick={() => removeCustomField(f.key)}
>
<Trash2 size={14} />
<IconTrash size={14} />
</button>
</li>
))}
@@ -422,7 +422,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
</>
) : (
<>
<Send size={16} className="me-1" />
<IconSend size={16} className="me-1" />
Тестовое уведомление
</>
)}
+13 -6
View File
@@ -1,6 +1,13 @@
import { useMemo, useState } from 'react'
import { convertCurrency, formatCurrency, faviconUrlFromWebsite, normalizeWebsiteUrl } from '../lib/utils'
import { ArrowDown, ArrowUp, MapPin, RefreshCw, Search, Server } from 'lucide-react'
import {
IconArrowDown,
IconArrowUp,
IconMapPin,
IconRefresh,
IconSearch,
IconServer,
} from '@tabler/icons-react'
import { syncAccount } from '../lib/api'
import { EmptyState } from '../components/EmptyState'
import { PageHeader } from '../components/PageHeader'
@@ -19,7 +26,7 @@ function SortHeader({ column, children, onSort, sortBy, sortDir }) {
style={SORT_COLUMNS.includes(column) ? { cursor: 'pointer', userSelect: 'none' } : undefined}
>
{children}
{sortBy === column && (sortDir === 'asc' ? <ArrowUp size={14} className="ms-1" /> : <ArrowDown size={14} className="ms-1" />)}
{sortBy === column && (sortDir === 'asc' ? <IconArrowUp size={14} className="ms-1" /> : <IconArrowDown size={14} className="ms-1" />)}
</th>
)
}
@@ -194,7 +201,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
<div className="card">
<div className="card-header">
<h3 className="card-title">
<MapPin size={18} className="me-1" />
<IconMapPin size={18} className="me-1" />
Доступные датацентры и страны
</h3>
</div>
@@ -242,7 +249,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
<div className="col-xl-3 col-lg-4 col-md-6">
<div className="input-icon">
<span className="input-icon-addon">
<Search size={16} />
<IconSearch size={16} />
</span>
<input {...noBrowserSuggestProps}
className="form-control"
@@ -363,7 +370,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
{syncLoading ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<RefreshCw size={16} className="me-1" />
<IconRefresh size={16} className="me-1" />
)}
Синхронизировать
</button>
@@ -416,7 +423,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
<td>
<div className="d-flex align-items-center gap-2">
<span className="avatar avatar-sm bg-blue-lt">
<Server size={16} />
<IconServer size={16} />
</span>
<div>
<div className="fw-medium">{item.name || '—'}</div>
+18 -9
View File
@@ -9,7 +9,16 @@ import {
tariffTypeLabel,
vpsStatusLabel,
} from '../lib/utils'
import { Pencil, Filter, MapPin, Plus, RefreshCw, Search, Server, Trash2 } from 'lucide-react'
import {
IconEdit,
IconFilter,
IconMapPin,
IconPlus,
IconRefresh,
IconSearch,
IconServer,
IconTrash,
} from '@tabler/icons-react'
import { syncAccount, bulkUpdateVps } from '../lib/api'
import { UiModal } from '../components/UiModal'
import { ConvertedAmount } from '../components/ConvertedAmount'
@@ -680,7 +689,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="col-xl-3 col-lg-4 col-md-6">
<div className="input-icon">
<span className="input-icon-addon">
<Search size={16} />
<IconSearch size={16} />
</span>
<input {...noBrowserSuggestProps}
className="form-control"
@@ -813,7 +822,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="col-xl-2 col-lg-4 col-md-6">
<div className="input-icon">
<span className="input-icon-addon">
<Filter size={16} />
<IconFilter size={16} />
</span>
<input {...noBrowserSuggestProps}
className="form-control"
@@ -831,7 +840,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
<div className="col-xl-2 col-lg-4 col-md-6">
<div className="input-icon">
<span className="input-icon-addon">
<MapPin size={16} />
<IconMapPin size={16} />
</span>
<input {...noBrowserSuggestProps}
className="form-control"
@@ -1077,7 +1086,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
{syncLoading ? (
<span className="spinner-border spinner-border-sm me-1" role="status" />
) : (
<RefreshCw size={16} className="me-1" />
<IconRefresh size={16} className="me-1" />
)}
Синхронизировать
</button>
@@ -1095,7 +1104,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
setIsModalOpen(true)
}}
>
<Plus size={16} className="me-1" />
<IconPlus size={16} className="me-1" />
Добавить VPS
</button>
</div>
@@ -1323,7 +1332,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
className="btn btn-sm btn-outline-primary"
onClick={() => onEdit(item)}
>
<Pencil size={14} className="me-1" />
<IconEdit size={14} className="me-1" />
Изменить
</button>
<button
@@ -1331,7 +1340,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
className="btn btn-sm btn-outline-danger"
onClick={() => actions.remove('vps', item.id)}
>
<Trash2 size={14} className="me-1" />
<IconTrash size={14} className="me-1" />
Удалить
</button>
</div>
@@ -1370,7 +1379,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
Отмена
</button>
<button type="submit" form="vps-form" className="btn btn-primary">
<Plus size={16} className="me-1" />
<IconPlus size={16} className="me-1" />
{editingId ? 'Сохранить' : 'Добавить'}
</button>
</div>