Refactor forms to disable browser suggestions and improve user experience
- Integrated `noBrowserSuggestProps` across multiple pages to prevent browser autocomplete suggestions on input fields. - Updated form elements in `AccountsPage`, `BalancePage`, `PaymentsPage`, `ProvidersPage`, `ReportsPage`, `ResourcesPage`, `SettingsPage`, `TariffsPage`, `VpsPage`, and `ProjectSuggestInput` to enhance usability. - Ensured consistent application of `autoComplete="off"` for select elements to improve form behavior.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { IconFolder } from '@tabler/icons-react'
|
||||
import { fetchProjectSuggestions } from '../lib/api'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
function highlightMatch(text, query) {
|
||||
const q = (query || '').trim().toLowerCase()
|
||||
@@ -141,6 +142,7 @@ export function ProjectSuggestInput({
|
||||
return (
|
||||
<div ref={wrapRef} className="position-relative project-suggest-wrap">
|
||||
<input
|
||||
{...noBrowserSuggestProps}
|
||||
id={id}
|
||||
type="text"
|
||||
className={className}
|
||||
@@ -153,7 +155,6 @@ export function ProjectSuggestInput({
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
aria-label={ariaLabel}
|
||||
aria-expanded={showPanel}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Отключает подсказки автозаполнения браузера и часто используемые хуки менеджеров паролей
|
||||
* на произвольных текстовых полях (адреса, проекты, заметки и т.д.).
|
||||
* Для полей пароля API используйте {@link passwordCredentialInputProps}.
|
||||
*/
|
||||
export const noBrowserSuggestProps = Object.freeze({
|
||||
autoComplete: 'off',
|
||||
'data-lpignore': 'true',
|
||||
'data-1p-ignore': 'true',
|
||||
'data-bwignore': 'true',
|
||||
'data-form-type': 'other',
|
||||
})
|
||||
|
||||
/** Поля пароля/секрета: не отключаем менеджеры паролей, только автоподстановку формы. */
|
||||
export const passwordCredentialInputProps = Object.freeze({
|
||||
autoComplete: 'new-password',
|
||||
})
|
||||
+12
-11
@@ -9,6 +9,7 @@ import { SyncLogTable } from '../components/SyncLogTable'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { ConvertedAmount } from '../components/ConvertedAmount'
|
||||
import { syncAccount, testApiConnection, fetchAccountBalance, fetchSyncStatus } from '../lib/api'
|
||||
import { noBrowserSuggestProps, passwordCredentialInputProps } from '../lib/noBrowserSuggestProps'
|
||||
import { IconRefresh, IconPlugConnected } from '@tabler/icons-react'
|
||||
|
||||
const emptyForm = {
|
||||
@@ -382,10 +383,10 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
}}
|
||||
size="modal-md"
|
||||
>
|
||||
<form onSubmit={onSubmit} className="row g-3">
|
||||
<form autoComplete="off" onSubmit={onSubmit} className="row g-3">
|
||||
<div className="col-12">
|
||||
<label className="form-label">Хостер</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.providerId}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, providerId: e.target.value }))}
|
||||
@@ -401,7 +402,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Имя / Псевдоним аккаунта</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||
@@ -410,7 +411,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Ссылка на панель управления</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="https://..."
|
||||
value={form.panelUrl}
|
||||
@@ -419,7 +420,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Валюта</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.currency}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
|
||||
@@ -431,7 +432,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Режим списания</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.billingMode}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, billingMode: e.target.value }))}
|
||||
@@ -446,7 +447,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Тип API</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.apiType}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, apiType: e.target.value, apiBaseUrl: '', apiLogin: '', apiPassword: '' }))}
|
||||
@@ -459,7 +460,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
<>
|
||||
<div className="col-12">
|
||||
<label className="form-label">URL API BILLmanager</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="https://bill.example.com:1500/billmgr"
|
||||
value={form.apiBaseUrl}
|
||||
@@ -468,7 +469,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Логин</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="admin"
|
||||
value={form.apiLogin}
|
||||
@@ -477,7 +478,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Пароль</label>
|
||||
<input
|
||||
<input {...passwordCredentialInputProps}
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={editingAccount?.apiCredentialsSet ? 'Оставьте пустым, чтобы не менять' : ''}
|
||||
@@ -511,7 +512,7 @@ export function AccountsPage({ db, actions, settings, ratesData }) {
|
||||
) : null}
|
||||
<div className="col-12">
|
||||
<label className="form-label">Комментарий</label>
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.notes}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { UiModal } from '../components/UiModal'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { ConvertedAmount } from '../components/ConvertedAmount'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const emptyForm = {
|
||||
type: 'daily_debit',
|
||||
@@ -191,10 +192,10 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
}}
|
||||
size="modal-md"
|
||||
>
|
||||
<form className="row g-3" onSubmit={onSubmit}>
|
||||
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Тип списания</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.type}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, type: e.target.value }))}
|
||||
@@ -205,7 +206,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Аккаунт</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -222,7 +223,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">VPS (необязательно)</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.vpsId}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, vpsId: e.target.value }))}
|
||||
@@ -237,7 +238,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Дата</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
type="date"
|
||||
value={form.date}
|
||||
@@ -246,7 +247,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Валюта</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.currency}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
|
||||
@@ -258,7 +259,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Сумма</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
@@ -269,7 +270,7 @@ export function BalancePage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Комментарий</label>
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.note}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, note: e.target.value }))}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { UiModal } from '../components/UiModal'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { ConvertedAmount } from '../components/ConvertedAmount'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const emptyForm = {
|
||||
type: 'direct_vps_payment',
|
||||
@@ -190,10 +191,10 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
}}
|
||||
size="modal-md"
|
||||
>
|
||||
<form className="row g-3" onSubmit={onSubmit}>
|
||||
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Тип</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.type}
|
||||
onChange={(e) =>
|
||||
@@ -210,7 +211,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Аккаунт хостера</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -233,7 +234,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
{form.type === 'direct_vps_payment' ? (
|
||||
<div className="col-12">
|
||||
<label className="form-label">VPS</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.vpsId}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, vpsId: e.target.value }))}
|
||||
@@ -250,7 +251,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
) : null}
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Дата</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={form.date}
|
||||
@@ -260,7 +261,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Валюта</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.currency}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, currency: e.target.value }))}
|
||||
@@ -272,7 +273,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Сумма</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
@@ -284,7 +285,7 @@ export function PaymentsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Комментарий</label>
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.note}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, note: e.target.value }))}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { UiModal } from '../components/UiModal'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { faviconUrlFromWebsite } from '../lib/utils'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const emptyForm = {
|
||||
name: '',
|
||||
@@ -146,10 +147,10 @@ export function ProvidersPage({ db, actions }) {
|
||||
}}
|
||||
size="modal-md"
|
||||
>
|
||||
<form onSubmit={onSubmit} className="row g-3">
|
||||
<form autoComplete="off" onSubmit={onSubmit} className="row g-3">
|
||||
<div className="col-12">
|
||||
<label className="form-label">Название</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||
@@ -158,7 +159,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Сайт</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.website}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, website: e.target.value }))}
|
||||
@@ -166,7 +167,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Контакт</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.contact}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, contact: e.target.value }))}
|
||||
@@ -174,7 +175,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-4">
|
||||
<label className="form-label">Валюта приёма платежей</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.baseCurrency}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, baseCurrency: e.target.value }))}
|
||||
@@ -187,7 +188,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-4">
|
||||
<label className="form-label">Курс USD</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
@@ -199,7 +200,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-4">
|
||||
<label className="form-label">Курс EUR</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.0001"
|
||||
@@ -211,7 +212,7 @@ export function ProvidersPage({ db, actions }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Заметки</label>
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.notes}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { ConvertedAmount } from '../components/ConvertedAmount'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
function isDateInRange(dateStr, dateFrom, dateTo) {
|
||||
if (!dateStr) return false
|
||||
@@ -157,7 +158,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
<div className="row g-2 align-items-end">
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Хостер</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerId}
|
||||
onChange={(e) =>
|
||||
@@ -178,7 +179,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Аккаунт</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -195,7 +196,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Проект / пул</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.project}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
|
||||
@@ -211,7 +212,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Страна</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={filters.country}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, country: e.target.value }))}
|
||||
@@ -220,7 +221,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-2">
|
||||
<label className="form-label">Период (YYYY-MM)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={filters.month}
|
||||
onChange={(e) =>
|
||||
@@ -236,7 +237,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-2">
|
||||
<label className="form-label">Дата с</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={filters.dateFrom}
|
||||
@@ -251,7 +252,7 @@ export function ReportsPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-2">
|
||||
<label className="form-label">Дата по</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={filters.dateTo}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '../lib/utils'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
function vpsMonthlyEstimateInBase(item, baseCurrency, ratesData) {
|
||||
if (item.status !== 'active') return 0
|
||||
@@ -188,7 +189,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
<div className="row g-2">
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Группировать по</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={groupBy}
|
||||
onChange={(e) => setGroupBy(e.target.value)}
|
||||
@@ -201,7 +202,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Хостер</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerId}
|
||||
onChange={(e) =>
|
||||
@@ -222,7 +223,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Аккаунт</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -239,7 +240,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Проект (фильтр)</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.project}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
|
||||
@@ -255,7 +256,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Страна</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={filters.country}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, country: e.target.value }))}
|
||||
@@ -264,7 +265,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">Датацентр</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={filters.datacenter}
|
||||
onChange={(e) =>
|
||||
@@ -276,7 +277,7 @@ export function ResourcesPage({ db, settings, ratesData }) {
|
||||
{customFields.map((f) => (
|
||||
<div key={f.key} className="col-12 col-md-6 col-xl-3">
|
||||
<label className="form-label">{f.label}</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={filters[f.key] ?? ''}
|
||||
onChange={(e) =>
|
||||
|
||||
+16
-16
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
|
||||
import { IconPlus, IconSend, IconTrash } from '@tabler/icons-react'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { sendTelegramTestNotification } from '../lib/api'
|
||||
import { noBrowserSuggestProps, passwordCredentialInputProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const defaultSettings = {
|
||||
baseCurrency: 'RUB',
|
||||
@@ -139,10 +140,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
<h3 className="card-title">Настройки валют</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<form className="row g-3" onSubmit={onSubmit}>
|
||||
<form className="row g-3" autoComplete="off" onSubmit={onSubmit}>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label">Валюта отображения</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.baseCurrency}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, baseCurrency: e.target.value }))}
|
||||
@@ -157,7 +158,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label">Автоконвертация</label>
|
||||
<label className="form-check">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.autoConvert}
|
||||
@@ -168,7 +169,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Ссылка на курсы валют</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.ratesUrl}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, ratesUrl: e.target.value }))}
|
||||
@@ -201,7 +202,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
Текстовые поля для расширенного режима просмотра списка VPS. Отображаются как колонки в таблице и в форме редактирования.
|
||||
</p>
|
||||
<div className="d-flex gap-2 mb-3">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Название поля (например: Контакт, ID заказа)"
|
||||
@@ -249,10 +250,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
Периодическая синхронизация данных из BILLmanager для аккаунтов с настроенным API.
|
||||
Два независимых интервала: VPS и платежи обновляются чаще, тарифы — реже.
|
||||
</p>
|
||||
<form className="row g-3" onSubmit={onSyncSettingsSubmit}>
|
||||
<form className="row g-3" autoComplete="off" onSubmit={onSyncSettingsSubmit}>
|
||||
<div className="col-12">
|
||||
<label className="form-check">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.syncEnabled}
|
||||
@@ -263,7 +264,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label">Интервал VPS и платежей (минуты)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="15"
|
||||
className="form-control"
|
||||
@@ -274,7 +275,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label">Интервал тарифов (минуты)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="60"
|
||||
className="form-control"
|
||||
@@ -305,10 +306,10 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
<p className="text-secondary small mb-3">
|
||||
Уведомления отправляются в Telegram при периодической синхронизации. Каждый тип можно включить или отключить отдельно.
|
||||
</p>
|
||||
<form className="row g-3" onSubmit={onTelegramSubmit}>
|
||||
<form className="row g-3" autoComplete="off" onSubmit={onTelegramSubmit}>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Токен бота</label>
|
||||
<input
|
||||
<input {...passwordCredentialInputProps}
|
||||
type="password"
|
||||
className="form-control"
|
||||
value={form.telegramBotToken}
|
||||
@@ -317,12 +318,11 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
setTelegramTokenEdited(true)
|
||||
}}
|
||||
placeholder={current.telegramBotTokenSet ? '••••••••' : 'Токен от @BotFather'}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">Chat ID (SuperGroup)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={form.telegramChatId}
|
||||
@@ -335,7 +335,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-label">ID топика (цепочки сообщений)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={form.telegramMessageThreadId}
|
||||
@@ -348,7 +348,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-check">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.notifyPaymentExpiryEnabled}
|
||||
@@ -359,7 +359,7 @@ export function SettingsPage({ db, actions, ratesData, ratesError }) {
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form-check">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.notifyNewTariffsEnabled}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { syncAccount } from '../lib/api'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const SORT_COLUMNS = ['name', 'vcpu', 'ramGb', 'diskGb', 'diskType', 'virtualization', 'channel', 'country', 'location', 'price']
|
||||
|
||||
@@ -249,7 +250,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
|
||||
<span className="input-icon-addon">
|
||||
<IconSearch size={16} />
|
||||
</span>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="Поиск по названию, ресурсам..."
|
||||
value={filters.search}
|
||||
@@ -260,7 +261,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerId}
|
||||
onChange={(e) =>
|
||||
@@ -280,7 +281,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -299,7 +300,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.country}
|
||||
onChange={(e) =>
|
||||
@@ -315,7 +316,7 @@ export function TariffsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.orderAvailable}
|
||||
onChange={(e) =>
|
||||
|
||||
+53
-52
@@ -24,6 +24,7 @@ import { ConvertedAmount } from '../components/ConvertedAmount'
|
||||
import { EmptyState } from '../components/EmptyState'
|
||||
import { PageHeader } from '../components/PageHeader'
|
||||
import { ProjectSuggestInput } from '../components/ProjectSuggestInput'
|
||||
import { noBrowserSuggestProps } from '../lib/noBrowserSuggestProps'
|
||||
|
||||
const emptyForm = {
|
||||
ip: '',
|
||||
@@ -622,7 +623,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<span className="input-icon-addon">
|
||||
<IconSearch size={16} />
|
||||
</span>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="Поиск IP / DNS"
|
||||
value={filters.search}
|
||||
@@ -636,7 +637,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.project}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, project: e.target.value }))}
|
||||
@@ -654,7 +655,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
{showAdvancedFilters ? (
|
||||
<>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerId}
|
||||
onChange={(e) =>
|
||||
@@ -674,7 +675,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.providerAccountId}
|
||||
onChange={(e) =>
|
||||
@@ -693,7 +694,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.status}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, status: e.target.value }))}
|
||||
@@ -705,7 +706,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-3 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.environment}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, environment: e.target.value }))}
|
||||
@@ -718,7 +719,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.tariffType}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, tariffType: e.target.value }))}
|
||||
@@ -729,7 +730,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.monitoring}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, monitoring: e.target.value }))}
|
||||
@@ -740,7 +741,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={filters.backup}
|
||||
onChange={(e) => setFilters((prev) => ({ ...prev, backup: e.target.value }))}
|
||||
@@ -755,7 +756,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<span className="input-icon-addon">
|
||||
<IconFilter size={16} />
|
||||
</span>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="Страна"
|
||||
value={filters.country}
|
||||
@@ -773,7 +774,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<span className="input-icon-addon">
|
||||
<IconMapPin size={16} />
|
||||
</span>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="Город"
|
||||
value={filters.city}
|
||||
@@ -787,7 +788,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder="ДЦ"
|
||||
value={filters.datacenter}
|
||||
@@ -795,7 +796,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
className="form-control"
|
||||
@@ -805,7 +806,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
className="form-control"
|
||||
@@ -815,7 +816,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="col-xl-2 col-lg-4 col-md-6">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
className="form-control"
|
||||
@@ -826,7 +827,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
{customFields.map((f) => (
|
||||
<div key={f.key} className="col-xl-2 col-lg-4 col-md-6">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
placeholder={f.label}
|
||||
value={filters[f.key] ?? ''}
|
||||
@@ -840,7 +841,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<div className="row g-2 mt-2 align-items-center flex-wrap">
|
||||
<div className="col-12 col-sm-auto">
|
||||
<label className="form-check mb-0">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={filters.groupByProject}
|
||||
@@ -853,7 +854,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-auto">
|
||||
<label className="form-check mb-0">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={filters.tableCompact}
|
||||
@@ -865,7 +866,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</label>
|
||||
</div>
|
||||
<div className="col-12 col-sm-auto">
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select form-select-sm"
|
||||
style={{ minWidth: '11rem' }}
|
||||
defaultValue=""
|
||||
@@ -1048,7 +1049,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width: 40 }}>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={filteredVps.length > 0 && selectedIds.size === filteredVps.length}
|
||||
@@ -1119,7 +1120,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
return (
|
||||
<tr key={item.id}>
|
||||
<td>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={selectedIds.has(item.id)}
|
||||
@@ -1316,13 +1317,13 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<form id="vps-form" onSubmit={onSubmit} className="vps-modal-form">
|
||||
<form id="vps-form" autoComplete="off" onSubmit={onSubmit} className="vps-modal-form">
|
||||
<section className="vps-form-section">
|
||||
<h6 className="vps-form-section-title">Сеть</h6>
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">IP</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.ip}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, ip: e.target.value }))}
|
||||
@@ -1332,7 +1333,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">DNS</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.dns}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, dns: e.target.value }))}
|
||||
@@ -1341,7 +1342,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">IPv6</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.ipv6}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, ipv6: e.target.value }))}
|
||||
@@ -1350,7 +1351,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Дополнительные IP</label>
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
rows={2}
|
||||
value={form.additionalIpsText}
|
||||
@@ -1366,7 +1367,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Хостер</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.providerId}
|
||||
onChange={(e) =>
|
||||
@@ -1388,7 +1389,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Аккаунт хостера</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.providerAccountId}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, providerAccountId: e.target.value }))}
|
||||
@@ -1404,7 +1405,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Страна</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.country}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, country: e.target.value }))}
|
||||
@@ -1413,7 +1414,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Город</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.city}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, city: e.target.value }))}
|
||||
@@ -1422,7 +1423,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">ДЦ / зона</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.datacenter}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, datacenter: e.target.value }))}
|
||||
@@ -1431,7 +1432,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">ОС</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.os}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, os: e.target.value }))}
|
||||
@@ -1446,7 +1447,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">vCPU</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
@@ -1458,7 +1459,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">RAM (GB)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
@@ -1470,7 +1471,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Диск (GB)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
@@ -1482,7 +1483,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Тип диска</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.diskType}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, diskType: e.target.value }))}
|
||||
@@ -1494,7 +1495,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Виртуализация</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.virtualization}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, virtualization: e.target.value }))}
|
||||
@@ -1508,7 +1509,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Трафик (TB)</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.1"
|
||||
@@ -1526,7 +1527,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Пользователь SSH</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.rootUser}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, rootUser: e.target.value }))}
|
||||
@@ -1534,7 +1535,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">SSH порт</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
@@ -1545,7 +1546,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Окружение</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.environment}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, environment: e.target.value }))}
|
||||
@@ -1572,7 +1573,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Назначение</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
value={form.purpose}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, purpose: e.target.value }))}
|
||||
@@ -1581,7 +1582,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 d-flex align-items-end gap-3 pt-2">
|
||||
<label className="form-check mb-0">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.monitoringEnabled}
|
||||
@@ -1590,7 +1591,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<span className="form-check-label">Мониторинг</span>
|
||||
</label>
|
||||
<label className="form-check mb-0">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
checked={form.backupEnabled}
|
||||
@@ -1607,7 +1608,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Статус</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.status}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, status: e.target.value }))}
|
||||
@@ -1619,7 +1620,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6 col-md-4">
|
||||
<label className="form-label">Тип тарифа</label>
|
||||
<select
|
||||
<select autoComplete="off"
|
||||
className="form-select"
|
||||
value={form.tariffType}
|
||||
onChange={(e) =>
|
||||
@@ -1639,7 +1640,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<label className="form-label">
|
||||
{form.tariffType === 'daily' ? 'Суточный тариф' : 'Месячный тариф'}
|
||||
</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
@@ -1657,7 +1658,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Дата создания</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={form.createdAt}
|
||||
@@ -1666,7 +1667,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<label className="form-label">Оплачено до</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="date"
|
||||
className="form-control"
|
||||
value={form.paidUntil}
|
||||
@@ -1681,7 +1682,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
<h6 className="vps-form-section-title">Заметки</h6>
|
||||
<div className="row g-3">
|
||||
<div className="col-12">
|
||||
<textarea
|
||||
<textarea {...noBrowserSuggestProps}
|
||||
className="form-control"
|
||||
rows={3}
|
||||
value={form.notes}
|
||||
@@ -1692,7 +1693,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
{editingId && (form.notes?.includes('bm-') || (db.vps.find((v) => v.id === editingId)?.userOverrides?.length ?? 0) > 0) ? (
|
||||
<div className="col-12">
|
||||
<label className="form-check">
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={form.resetToApi}
|
||||
@@ -1712,7 +1713,7 @@ export function VpsPage({ db, actions, settings, ratesData }) {
|
||||
{customFields.map((f) => (
|
||||
<div key={f.key} className="col-12 col-sm-6">
|
||||
<label className="form-label">{f.label}</label>
|
||||
<input
|
||||
<input {...noBrowserSuggestProps}
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={form[f.key] ?? ''}
|
||||
|
||||
Reference in New Issue
Block a user