Revert "merge: integrate extension auto-update"

This reverts commit 322475860a, reversing
changes made to f52d9d7413.
This commit is contained in:
inman committed 2026-09-03 16:45:14 +08:00
1 parent b2e33e2e5d
commit 81a0cdac8e
47 files changed
+169 -3108

No files matched your search

+19 -236
View File
@@ -29,8 +29,6 @@ let erpAutomationEnabled = true;
let erpHostAccessReady = true;
let erpSessionReady = true;
let extensionCompatible = false;
let extensionUpdateBlocksExecution = true;
let extensionReloadRequestedVersion = '';
let latestAiStatus = null;
let latestBridgeStatus = null;
let activeStatusDetails = '';
@@ -51,9 +49,6 @@ let parserRoutingBusy = false;
let accountList = [];
let accountTaskTypes = [];
let accountSettingsBusy = false;
let extensionReleaseBusy = false;
let extensionReleaseEnabled = false;
let extensionReleaseList = [];
let accountAuthorizationTargetId = '';
let accountAuthorizationDraft = new Set();
let auditEvents = [];
@@ -104,7 +99,7 @@ const persistedExtensionResultVersions = new Map();
let taskCreateInProgress = false;
const AUTO_HANDOFF_RETRY_MS = 30_000;
const REQUIRED_EXTENSION_VERSION = '0.5.167';
const REQUIRED_EXTENSION_VERSION = '0.5.165';
const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件';
const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件';
const RECONCILE_LABEL = '只读回查 ERP 现有结果';
@@ -958,8 +953,6 @@ function validateAccountCreationValues(values = {}) {
const password = String(values.password || '');
const role = String(values.role || '');
const erpAccount = String(values.erpAccount || '').trim();
const ecsRegion = String(values.ecsRegion || '').trim().toLowerCase();
const ecsInstance = String(values.ecsInstance || '').trim();
if (!username || username.length > 160) {
return { ok: false, field: 'accountUsername', message: '账号必须为 1—160 个字符。' };
}
@@ -972,15 +965,6 @@ function validateAccountCreationValues(values = {}) {
if (role !== 'admin' && (!erpAccount || erpAccount.length > 200)) {
return { ok: false, field: 'accountErpAccount', message: '普通用户或组长必须填写 1—200 个字符的 ERP 账号。' };
}
if (role !== 'admin' && Boolean(ecsRegion) !== Boolean(ecsInstance)) {
return { ok: false, field: ecsRegion ? 'accountEcsInstance' : 'accountEcsRegion', message: 'ECS 地域与实例必须同时填写或同时留空。' };
}
if (ecsRegion && !/^[a-z0-9][a-z0-9-]{0,63}$/.test(ecsRegion)) {
return { ok: false, field: 'accountEcsRegion', message: 'ECS 地域 ID 格式无效。' };
}
if (ecsInstance && !/^i-[A-Za-z0-9]{6,64}$/.test(ecsInstance)) {
return { ok: false, field: 'accountEcsInstance', message: 'ECS 实例 ID 格式无效。' };
}
return {
ok: true,
body: {
@@ -988,8 +972,6 @@ function validateAccountCreationValues(values = {}) {
password,
role,
erp_account: role === 'admin' ? '' : erpAccount,
extension_ecs_region_id: role === 'admin' ? '' : ecsRegion,
extension_ecs_instance_id: role === 'admin' ? '' : ecsInstance,
business_route_ids: []
}
};
@@ -1025,7 +1007,7 @@ function showAccountCreationValidationError(validation) {
}
function clearAccountCreationValidationErrors() {
for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole', 'accountErpAccount', 'accountEcsRegion', 'accountEcsInstance']) {
for (const fieldId of ['accountUsername', 'accountPassword', 'accountRole', 'accountErpAccount']) {
document.getElementById(fieldId)?.removeAttribute('aria-invalid');
}
}
@@ -1052,15 +1034,6 @@ function renderAccounts() {
main.append(el('p', account.erp_account ? 'muted' : 'channel-error', account.role === 'admin'
? 'ERP 执行身份:不绑定'
: `ERP 执行身份:${account.erp_account || '未配置(禁止执行)'}`));
main.append(el(
'p',
account.extension_ecs_instance_id ? 'muted' : 'channel-error',
account.role === 'admin'
? '插件云主机:不绑定'
: account.extension_ecs_instance_id
? `插件云主机:${account.extension_ecs_region_id} / ${account.extension_ecs_instance_id}`
: '插件云主机:未配置(不自动更新)'
));
const authorizedCount = Array.isArray(account.authorized_business_route_ids)
? account.authorized_business_route_ids.length
: 0;
@@ -1104,12 +1077,7 @@ function renderAccounts() {
erpAccount.dataset.accountAction = 'erp-account';
erpAccount.dataset.accountId = account.id;
erpAccount.disabled = accountSettingsBusy || account.role === 'admin';
const extensionHost = el('button', 'secondary-button', '云主机');
extensionHost.type = 'button';
extensionHost.dataset.accountAction = 'extension-host';
extensionHost.dataset.accountId = account.id;
extensionHost.disabled = accountSettingsBusy || account.role === 'admin';
actions.append(role, authorizations, erpAccount, extensionHost, toggle, reset, revoke);
actions.append(role, authorizations, erpAccount, toggle, reset, revoke);
row.append(main, actions);
container.append(row);
}
@@ -1208,67 +1176,6 @@ async function syncAccounts() {
accountTaskTypes = Array.isArray(result.task_types) ? result.task_types : [];
renderAccounts();
renderChannelOwnerOptions();
await syncExtensionReleases().catch((error) => {
const message = $('#extensionReleaseMessage');
if (message) message.textContent = error.message || String(error);
});
}
function renderExtensionReleases() {
const summary = $('#extensionReleaseSummary');
const button = $('#extensionReleasePublish');
const file = $('#extensionReleaseFile');
if (!summary || !button || !file) return;
const active = extensionReleaseList.find((release) => release.is_active === true);
summary.textContent = !extensionReleaseEnabled
? '服务端自动更新尚未启用;启用配置后才能发布。'
: active
? `当前活动版本 ${active.version} · ${Math.round(Number(active.byte_size || 0) / 1024)} KB · ${new Date(active.created_at).toLocaleString('zh-CN')}`
: '自动更新已启用,尚未发布任何插件包。';
button.disabled = extensionReleaseBusy || !extensionReleaseEnabled || !file.files?.length;
file.disabled = extensionReleaseBusy || !extensionReleaseEnabled;
}
async function syncExtensionReleases() {
if (!isAdministrator() || !IS_ACCOUNTS_PAGE) return;
const result = await apiRequest('/api/extension-updates/releases');
extensionReleaseEnabled = result.enabled === true;
extensionReleaseList = Array.isArray(result.releases) ? result.releases : [];
renderExtensionReleases();
}
async function publishExtensionRelease() {
if (!isAdministrator() || extensionReleaseBusy || !extensionReleaseEnabled) return;
const input = $('#extensionReleaseFile');
const file = input?.files?.[0];
if (!file) return;
if (!/\.zip$/i.test(String(file.name || '')) || !file.size || file.size > 50_000_000) {
throw new Error('请选择不超过 50 MB 的版本化插件 ZIP。');
}
extensionReleaseBusy = true;
renderExtensionReleases();
const message = $('#extensionReleaseMessage');
if (message) message.textContent = '正在校验并发布到 OSS…';
try {
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => resolve(String(reader.result || '')), { once: true });
reader.addEventListener('error', () => reject(new Error('无法读取插件 ZIP。')), { once: true });
reader.readAsDataURL(file);
});
const packageBase64 = String(dataUrl).slice(String(dataUrl).indexOf(',') + 1);
const result = await apiRequest('/api/extension-updates/releases', {
method: 'POST',
timeoutMs: 120_000,
body: { package_base64: packageBase64 }
});
if (message) message.textContent = `插件 ${result.release?.version || ''} 已发布;各云主机会在完全空闲后更新。`;
if (input) input.value = '';
await syncExtensionReleases();
} finally {
extensionReleaseBusy = false;
renderExtensionReleases();
}
}
async function createAccountFromForm() {
@@ -1277,9 +1184,7 @@ async function createAccountFromForm() {
username: $('#accountUsername').value,
password: $('#accountPassword').value,
role: $('#accountRole').value,
erpAccount: $('#accountErpAccount').value,
ecsRegion: $('#accountEcsRegion').value,
ecsInstance: $('#accountEcsInstance').value
erpAccount: $('#accountErpAccount').value
});
if (!validation.ok) {
showAccountCreationValidationError(validation);
@@ -1299,8 +1204,6 @@ async function createAccountFromForm() {
$('#accountUsername').value = '';
$('#accountPassword').value = '';
$('#accountErpAccount').value = '';
$('#accountEcsRegion').value = '';
$('#accountEcsInstance').value = '';
if (message) {
message.textContent = result.account?.role === 'admin'
? '管理员账号已创建;该角色固定拥有全部任务权限,初始密码不会再次显示。'
@@ -1320,36 +1223,6 @@ async function updateManagedAccountErpIdentity(accountId) {
await updateManagedAccount(accountId, { erp_account: value.trim().slice(0, 200) });
}
async function updateManagedAccountExtensionHost(accountId) {
const account = accountList.find((item) => item.id === accountId);
if (!account || account.role === 'admin') return;
const region = window.prompt(
'请输入 ECS 地域 ID(例如 cn-hangzhou)。留空会同时清除该账号的云主机绑定:',
account.extension_ecs_region_id || ''
);
if (region === null) return;
if (!region.trim()) {
await updateManagedAccount(accountId, {
extension_ecs_region_id: null,
extension_ecs_instance_id: null
});
return;
}
const instance = window.prompt(
'请输入 ECS 实例 ID(例如 i-bp...)。同一台 Windows Server 上的账号填写相同实例:',
account.extension_ecs_instance_id || ''
);
if (instance === null || !instance.trim()) return;
if (
region.trim().toLowerCase() === account.extension_ecs_region_id
&& instance.trim() === account.extension_ecs_instance_id
) return;
await updateManagedAccount(accountId, {
extension_ecs_region_id: region.trim().toLowerCase().slice(0, 64),
extension_ecs_instance_id: instance.trim().slice(0, 80)
});
}
async function updateManagedAccount(accountId, patch) {
if (!isAdministrator() || accountSettingsBusy) return;
accountSettingsBusy = true;
@@ -2729,7 +2602,7 @@ function isAutomaticTask(task) {
}
async function autoDispatchReadyTasks({ force = false } = {}) {
if (!authUser || autoHandoffInProgress || extensionUpdateBlocksExecution) return;
if (!authUser || autoHandoffInProgress) return;
autoHandoffInProgress = true;
try {
const result = await apiRequest('/api/tasks?status=confirmed&limit=200&include_total=false&executable_by=me');
@@ -4481,9 +4354,7 @@ function renderStatusDetails() {
const status = latestBridgeStatus;
const recoveryStatus = String(status?.erpLinkRecovery?.status || status?.erpSession?.erp_keepalive?.recovery_status || '');
const recoveryPending = ['scheduled', 'already_running', 'running'].includes(recoveryStatus);
const updateStatus = String(status?.extensionUpdate?.status || '');
const updateWarning = Boolean(updateStatus && !['current', 'disabled', 'no_release'].includes(updateStatus));
const hasWarning = status?.connected && (!status?.compatible || !status?.automationEnabled || !status?.erpHostAccessReady || status?.erpSessionReady === false || recoveryPending || updateWarning);
const hasWarning = status?.connected && (!status?.compatible || !status?.automationEnabled || !status?.erpHostAccessReady || status?.erpSessionReady === false || recoveryPending);
title.textContent = '插件状态详情';
refresh.textContent = '重新检测插件';
if (!status) {
@@ -4504,8 +4375,6 @@ function renderStatusDetails() {
statusDetailRow('插件版本', status?.version || '未知'),
statusDetailRow('最低版本', REQUIRED_EXTENSION_VERSION),
statusDetailRow('版本兼容', statusBoolean(status?.compatible, '兼容', '需要更新')),
statusDetailRow('自动更新', status?.extensionUpdate?.message || '等待服务端检测'),
statusDetailRow('目标版本', status?.extensionUpdate?.target_version || '尚未发布'),
statusDetailRow('ERP 自动化', statusBoolean(status?.automationEnabled, '已开启', '已关闭')),
statusDetailRow('ERP 页面权限', statusBoolean(status?.erpHostAccessReady, '可访问', '需在扩展设置中允许')),
statusDetailRow('ERP 会话', statusBoolean(status?.erpSessionReady, '正常', '异常或待登录')),
@@ -4566,6 +4435,8 @@ async function pingAi() {
}
function applyBridgePayload(payload = {}) {
const wasBridgeConnected = bridgeConnected;
const previousBridgeInstalledAt = String(latestBridgeStatus?.installedAt || '');
bridgeConnected = Boolean(payload.ok);
if (!bridgeConnected) {
erpAutomationEnabled = false;
@@ -4576,7 +4447,6 @@ function applyBridgePayload(payload = {}) {
connected: false,
compatible: false,
automationEnabled: false,
extensionUpdate: null,
version: payload.version || '',
installedAt: payload.bridge_installed_at || '',
error: payload.message || payload.error || '插件桥接未返回成功状态。',
@@ -4626,7 +4496,6 @@ function applyBridgePayload(payload = {}) {
status: recoveryStatus || 'not_started',
pending: erpLinkRecoveryPending
},
extensionUpdate: latestBridgeStatus?.extensionUpdate || null,
version: payload.version || '',
installedAt: bridgeInstalledAt,
error: !extensionCompatible
@@ -4650,40 +4519,12 @@ function applyBridgePayload(payload = {}) {
setBridgeState('已连接', 'state-ok');
}
renderStatusDetails();
const bridgeReadyForDispatch = !wasBridgeConnected
|| Boolean(bridgeInstalledAt && bridgeInstalledAt !== previousBridgeInstalledAt);
queueMicrotask(() => autoDispatchReadyTasks({ force: bridgeReadyForDispatch }).catch(() => {}));
return true;
}
function extensionUpdateIsBlocking(update = {}) {
return !['current', 'disabled', 'no_release'].includes(String(update?.status || ''));
}
async function applyPreparedExtensionUpdate(update = {}) {
const targetVersion = String(update.target_version || '');
if (!targetVersion || extensionReloadRequestedVersion === targetVersion) return;
extensionReloadRequestedVersion = targetVersion;
try {
const result = await sendToExtension('APPLY_EXTENSION_UPDATE', {
target_version: targetVersion
}, 5_000);
if (result?.accepted !== true && result?.status !== 'already_current') {
throw new Error(result?.message || '插件未接受安全重载请求。');
}
if (result?.status === 'already_current') {
window.location.reload();
return;
}
window.setTimeout(() => window.location.reload(), 900);
} catch (error) {
extensionReloadRequestedVersion = '';
latestBridgeStatus = {
...latestBridgeStatus,
error: error.message || String(error)
};
setBridgeState('更新待重载', 'state-warn');
renderStatusDetails();
}
}
function setTaskState(text) {
const node = $('#taskState');
if (!node) return;
@@ -4729,8 +4570,7 @@ window.addEventListener('message', (event) => {
const message = event.data || {};
if (message.source !== EXTENSION_SOURCE) return;
if (message.type === 'BRIDGE_READY') {
if (authUser) void pingBridge();
else applyBridgePayload(message.payload || { ok: true });
applyBridgePayload(message.payload || { ok: true });
return;
}
if (message.type === 'TASK_RESULT_CHANGED') {
@@ -5055,7 +4895,7 @@ async function confirmAndSubmitToErpPlugin(task) {
throw new Error('该任务分配给其他平台账号,当前账号只能查看,不能确认或执行。');
}
const bridgeReady = await pingBridge();
if (!bridgeReady || !extensionCompatible || extensionUpdateBlocksExecution || !erpAutomationEnabled || !erpHostAccessReady || !erpSessionReady || latestBridgeStatus?.erpLinkRecovery?.pending === true) {
if (!bridgeReady || !extensionCompatible || !erpAutomationEnabled || !erpHostAccessReady || !erpSessionReady || latestBridgeStatus?.erpLinkRecovery?.pending === true) {
throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION}、ERP 页面/会话不可用或 ERP 操作开关未开启;任务尚未确认,也未提交到插件。`);
}
const response = await apiRequest(`/api/tasks/${encodeURIComponent(task.task_id)}/confirm`, { method: 'POST', body: {} });
@@ -5099,36 +4939,9 @@ async function pingBridge() {
extension_version: result.version || '',
erp_account: authUser.erp_account || '',
erp_account_matched: result.erp_session?.account_matched === true,
extension_update_safe: result.extension_update?.safe === true,
metadata: {
bridge_installed_at: result.bridge_installed_at || '',
extension_update_reason: result.extension_update?.reason || ''
}
metadata: { bridge_installed_at: result.bridge_installed_at || '' }
}
});
const extensionUpdate = heartbeat.extension_update || {
status: 'disabled',
current_version: result.version || '',
target_version: '',
message: '插件自动更新未启用。'
};
extensionUpdateBlocksExecution = extensionUpdateIsBlocking(extensionUpdate);
latestBridgeStatus = {
...latestBridgeStatus,
extensionUpdate,
error: extensionUpdateBlocksExecution
? extensionUpdate.message || '插件正在更新,已暂停新 ERP 任务。'
: latestBridgeStatus?.error || ''
};
renderStatusDetails();
if (extensionUpdateBlocksExecution) {
setBridgeState(extensionUpdate.status === 'reload_required' ? '更新待重载' : '插件更新中', 'state-warn');
renderStatusDetails();
}
if (extensionUpdate.action === 'reload') {
void applyPreparedExtensionUpdate(extensionUpdate);
return false;
}
if (heartbeat.execution_ready !== true) {
erpSessionReady = false;
latestBridgeStatus = {
@@ -5143,11 +4956,7 @@ async function pingBridge() {
renderStatusDetails();
return false;
}
if (!extensionUpdateBlocksExecution) {
queueMicrotask(() => autoDispatchReadyTasks({ force: true }).catch(() => {}));
}
} catch (heartbeatError) {
extensionUpdateBlocksExecution = true;
erpSessionReady = false;
latestBridgeStatus = {
...latestBridgeStatus,
@@ -5159,12 +4968,11 @@ async function pingBridge() {
return false;
}
}
return bridgeConnected && erpSessionReady && !extensionUpdateBlocksExecution;
return bridgeConnected && erpSessionReady;
} catch (error) {
bridgeConnected = false;
erpAutomationEnabled = false;
extensionCompatible = false;
extensionUpdateBlocksExecution = true;
latestBridgeStatus = {
connected: false,
compatible: false,
@@ -5623,8 +5431,8 @@ async function pollTaskResult(taskId, { allowReconciliation = false } = {}) {
async function reconcileTaskReceipt(task) {
if (!canReconcileTask(task)) throw new Error('当前任务不在可只读回查的生命周期不确定状态。');
if (!taskAssignedToCurrentAccount(task)) throw new Error('该任务分配给其他平台账号,当前云电脑不能执行回查。');
await pingBridge();
if (!bridgeConnected || !erpHostAccessReady) throw new Error('插件未连接或 ERP 页面权限不可用,未执行回查。');
const bridgeReady = await pingBridge();
if (!bridgeReady || !extensionCompatible || !erpHostAccessReady) throw new Error(`插件未连接、版本低于 ${REQUIRED_EXTENSION_VERSION} 或 ERP 页面权限不可用,未执行回查。`);
taskReconciliationStates.set(task.task_id, 'running');
renderTaskDetail();
try {
@@ -5919,9 +5727,6 @@ document.addEventListener('DOMContentLoaded', async () => {
taskAttachmentDrafts.clear();
accountList = [];
accountTaskTypes = [];
extensionReleaseEnabled = false;
extensionReleaseList = [];
extensionReleaseBusy = false;
accountAuthorizationTargetId = '';
accountAuthorizationDraft = new Set();
historySelectedTaskIds.clear();
@@ -5931,8 +5736,6 @@ document.addEventListener('DOMContentLoaded', async () => {
historyBatchMessageKind = '';
currentTaskId = '';
browserConnectionId = '';
extensionUpdateBlocksExecution = true;
extensionReloadRequestedVersion = '';
sessionStorage.removeItem('liansyn_platform_current_task_id');
$('#rosterAttachment').value = '';
$('#rosterAttachmentStatus').textContent = '';
@@ -6027,11 +5830,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const account = accountList.find((item) => item.id === control.dataset.accountId);
if (!account) return;
const patch = { role: control.value };
if (control.value === 'admin') {
patch.erp_account = null;
patch.extension_ecs_region_id = null;
patch.extension_ecs_instance_id = null;
}
if (control.value === 'admin') patch.erp_account = null;
if (control.value !== 'admin' && !account.erp_account) {
const erpAccount = window.prompt('该员工角色必须绑定 ERP 账号,请输入 ERP 页面显示的登录账号:', '');
if (!erpAccount?.trim()) {
@@ -6058,7 +5857,6 @@ document.addEventListener('DOMContentLoaded', async () => {
return;
}
if (action === 'erp-account') operation = updateManagedAccountErpIdentity(account.id);
else if (action === 'extension-host') operation = updateManagedAccountExtensionHost(account.id);
else if (action === 'toggle') operation = updateManagedAccount(account.id, { is_active: !account.is_active });
else if (action === 'reset-password') operation = resetManagedAccountPassword(account.id);
else if (action === 'revoke-sessions') operation = revokeManagedAccountSessions(account.id);
@@ -6223,25 +6021,10 @@ document.addEventListener('DOMContentLoaded', async () => {
});
$('#accountRole')?.addEventListener('change', (event) => {
const erpInput = $('#accountErpAccount');
const ecsRegionInput = $('#accountEcsRegion');
const ecsInstanceInput = $('#accountEcsInstance');
const admin = event.target.value === 'admin';
erpInput.required = !admin;
erpInput.disabled = admin;
ecsRegionInput.disabled = admin;
ecsInstanceInput.disabled = admin;
if (admin) {
erpInput.value = '';
ecsRegionInput.value = '';
ecsInstanceInput.value = '';
}
});
$('#extensionReleaseFile')?.addEventListener('change', renderExtensionReleases);
$('#extensionReleasePublish')?.addEventListener('click', () => {
void publishExtensionRelease().catch((error) => {
const message = $('#extensionReleaseMessage');
if (message) message.textContent = error.message || String(error);
});
if (admin) erpInput.value = '';
});
$('#aiState').addEventListener('click', () => {
toggleStatusDetails('ai').catch(() => {});
-13
View File
@@ -99,21 +99,8 @@
<label>初始密码<input id="accountPassword" type="password" autocomplete="new-password" required></label>
<label>角色<select id="accountRole"><option value="user">普通用户</option><option value="team_lead">组长</option><option value="admin">管理员</option></select></label>
<label>ERP 账号<input id="accountErpAccount" maxlength="200" autocomplete="off" required placeholder="ERP 页面显示的登录账号"></label>
<label>ECS 地域<input id="accountEcsRegion" maxlength="64" autocomplete="off" placeholder="例如 cn-hangzhou"></label>
<label>ECS 实例<input id="accountEcsInstance" maxlength="80" autocomplete="off" placeholder="例如 i-bp...;同一台机器的账号填相同实例"></label>
<button type="submit">创建账号</button>
</form>
<section class="account-extension-release" aria-labelledby="extensionReleaseTitle">
<div>
<h3 id="extensionReleaseTitle">插件版本发布</h3>
<p id="extensionReleaseSummary" class="muted">正在读取服务端发布状态…</p>
</div>
<div class="actions">
<input id="extensionReleaseFile" type="file" accept=".zip,application/zip">
<button id="extensionReleasePublish" type="button">发布到 OSS</button>
</div>
<p id="extensionReleaseMessage" class="channel-message" role="status"></p>
</section>
<section id="accountAuthorizationPanel" class="account-authorization-panel" hidden aria-labelledby="accountAuthorizationTitle">
<div class="account-authorization-head">
<div>
-32
View File
@@ -638,38 +638,6 @@ textarea {
color: var(--ink) !important;
}
.account-extension-release {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px 16px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--surface-muted);
}
.account-extension-release h3 {
font-size: 14px;
}
.account-extension-release .actions {
display: flex;
align-items: center;
gap: 8px;
}
.account-extension-release input[type='file'] {
max-width: 280px;
font-size: 11px;
}
.account-extension-release .channel-message {
flex-basis: 100%;
margin: 0;
}
.account-authorization-panel {
display: grid;
gap: 12px;