mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(updates): improve check updates confirmation
This commit is contained in:
@@ -731,6 +731,12 @@
|
|||||||
"countMessage": "Modelle werden dauerhaft gelöscht.",
|
"countMessage": "Modelle werden dauerhaft gelöscht.",
|
||||||
"action": "Alle löschen"
|
"action": "Alle löschen"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "Alle {typePlural} auf Updates prüfen?",
|
||||||
|
"message": "Damit werden alle {typePlural} in deiner Bibliothek auf Updates geprüft. Bei großen Sammlungen kann das etwas länger dauern.",
|
||||||
|
"tip": "Du möchtest in Etappen prüfen? Wechsle in den Sammelmodus, wähle die benötigten Modelle aus und nutze anschließend \"Auswahl auf Updates prüfen\".",
|
||||||
|
"action": "Alles prüfen"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Tags zu mehreren Modellen hinzufügen",
|
"title": "Tags zu mehreren Modellen hinzufügen",
|
||||||
"description": "Tags hinzufügen zu",
|
"description": "Tags hinzufügen zu",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "models will be permanently deleted.",
|
"countMessage": "models will be permanently deleted.",
|
||||||
"action": "Delete All"
|
"action": "Delete All"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "Check updates for all {typePlural}?",
|
||||||
|
"message": "This checks every {typePlural} in your library for updates. Large collections may take a little longer.",
|
||||||
|
"tip": "To work in smaller batches, switch to bulk mode, choose the ones you need, then use \"Check Updates for Selected\".",
|
||||||
|
"action": "Check All"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Add Tags to Multiple Models",
|
"title": "Add Tags to Multiple Models",
|
||||||
"description": "Add tags to",
|
"description": "Add tags to",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "modelos serán eliminados permanentemente.",
|
"countMessage": "modelos serán eliminados permanentemente.",
|
||||||
"action": "Eliminar todo"
|
"action": "Eliminar todo"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "¿Comprobar actualizaciones para todos los {typePlural}?",
|
||||||
|
"message": "Esto comprobará las actualizaciones de todos los {typePlural} de tu biblioteca. En colecciones grandes puede tardar un poco más.",
|
||||||
|
"tip": "¿Quieres hacerlo por partes? Activa el modo por lotes, selecciona los modelos que necesites y usa \"Comprobar actualizaciones para la selección\".",
|
||||||
|
"action": "Comprobar todo"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Añadir etiquetas a múltiples modelos",
|
"title": "Añadir etiquetas a múltiples modelos",
|
||||||
"description": "Añadir etiquetas a",
|
"description": "Añadir etiquetas a",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "modèles seront définitivement supprimés.",
|
"countMessage": "modèles seront définitivement supprimés.",
|
||||||
"action": "Tout supprimer"
|
"action": "Tout supprimer"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "Vérifier les mises à jour pour tous les {typePlural} ?",
|
||||||
|
"message": "Cette action vérifie les mises à jour pour tous les {typePlural} de votre bibliothèque. Les grandes collections peuvent prendre un peu plus de temps.",
|
||||||
|
"tip": "Besoin de procéder par étapes ? Passez en mode lot, sélectionnez les modèles souhaités puis utilisez \"Vérifier les mises à jour pour la sélection\".",
|
||||||
|
"action": "Tout vérifier"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Ajouter des tags à plusieurs modèles",
|
"title": "Ajouter des tags à plusieurs modèles",
|
||||||
"description": "Ajouter des tags à",
|
"description": "Ajouter des tags à",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "מודלים יימחקו לצמיתות.",
|
"countMessage": "מודלים יימחקו לצמיתות.",
|
||||||
"action": "מחק הכל"
|
"action": "מחק הכל"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "לבדוק עדכונים לכל ה-{typePlural}?",
|
||||||
|
"message": "הפעולה תבדוק עדכונים עבור כל ה-{typePlural} בספרייה שלך. באוספים גדולים זה עלול לקחת מעט יותר זמן.",
|
||||||
|
"tip": "רוצים לחלק למנות קטנות? עברו למצב קבוצתי, בחרו את המודלים הדרושים ואז השתמשו ב\"בדוק עדכונים לנבחרים\".",
|
||||||
|
"action": "בדוק הכל"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "הוסף תגיות למספר מודלים",
|
"title": "הוסף תגיות למספר מודלים",
|
||||||
"description": "הוסף תגיות ל-",
|
"description": "הוסף תגיות ל-",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "モデルが完全に削除されます。",
|
"countMessage": "モデルが完全に削除されます。",
|
||||||
"action": "すべて削除"
|
"action": "すべて削除"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "すべての{type}の更新を確認しますか?",
|
||||||
|
"message": "ライブラリ内のすべての{type}で更新を確認します。コレクションが大きい場合は時間がかかることがあります。",
|
||||||
|
"tip": "少しずつ確認したい場合はバルクモードに切り替え、必要なモデルを選んで「選択項目の更新を確認」を使ってください。",
|
||||||
|
"action": "すべて確認"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "複数モデルにタグを追加",
|
"title": "複数モデルにタグを追加",
|
||||||
"description": "タグを追加するモデル:",
|
"description": "タグを追加するモデル:",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "개의 모델이 영구적으로 삭제됩니다.",
|
"countMessage": "개의 모델이 영구적으로 삭제됩니다.",
|
||||||
"action": "모두 삭제"
|
"action": "모두 삭제"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "{type} 전체 업데이트를 확인할까요?",
|
||||||
|
"message": "라이브러리에 있는 모든 {type}의 업데이트를 확인합니다. 컬렉션이 클수록 시간이 조금 더 걸릴 수 있습니다.",
|
||||||
|
"tip": "나눠서 진행하고 싶다면 벌크 모드로 전환해 필요한 모델만 선택한 뒤 \"선택 항목 업데이트 확인\"을 사용하세요.",
|
||||||
|
"action": "전체 확인"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "여러 모델에 태그 추가",
|
"title": "여러 모델에 태그 추가",
|
||||||
"description": "다음에 태그를 추가합니다:",
|
"description": "다음에 태그를 추가합니다:",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "моделей будут удалены навсегда.",
|
"countMessage": "моделей будут удалены навсегда.",
|
||||||
"action": "Удалить все"
|
"action": "Удалить все"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "Проверить обновления для всех {typePlural}?",
|
||||||
|
"message": "Будут проверены обновления для всех {typePlural} в вашей библиотеке. Для больших коллекций это может занять немного больше времени.",
|
||||||
|
"tip": "Хотите проверять по частям? Переключитесь в массовый режим, выберите нужные модели и используйте \"Проверить обновления для выбранных\".",
|
||||||
|
"action": "Проверить всё"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "Добавить теги к нескольким моделям",
|
"title": "Добавить теги к нескольким моделям",
|
||||||
"description": "Добавить теги к",
|
"description": "Добавить теги к",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "模型将被永久删除。",
|
"countMessage": "模型将被永久删除。",
|
||||||
"action": "全部删除"
|
"action": "全部删除"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "检查所有 {type} 的更新?",
|
||||||
|
"message": "这会为库中的每个 {type} 检查更新,大型集合可能需要一些时间。",
|
||||||
|
"tip": "想分批进行?切换到批量模式,选中需要的模型,然后使用“检查所选更新”。",
|
||||||
|
"action": "检查全部"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "批量添加标签",
|
"title": "批量添加标签",
|
||||||
"description": "为多个模型添加标签",
|
"description": "为多个模型添加标签",
|
||||||
|
|||||||
@@ -730,6 +730,12 @@
|
|||||||
"countMessage": "模型將被永久刪除。",
|
"countMessage": "模型將被永久刪除。",
|
||||||
"action": "全部刪除"
|
"action": "全部刪除"
|
||||||
},
|
},
|
||||||
|
"checkUpdates": {
|
||||||
|
"title": "要檢查所有 {type} 的更新嗎?",
|
||||||
|
"message": "這會為資料庫中的每個 {type} 檢查更新,大型收藏可能會花上一些時間。",
|
||||||
|
"tip": "想分批處理?切換到批次模式,選擇需要的模型,然後使用「檢查所選更新」。",
|
||||||
|
"action": "全部檢查"
|
||||||
|
},
|
||||||
"bulkAddTags": {
|
"bulkAddTags": {
|
||||||
"title": "新增標籤到多個模型",
|
"title": "新增標籤到多個模型",
|
||||||
"description": "新增標籤到",
|
"description": "新增標籤到",
|
||||||
|
|||||||
@@ -235,13 +235,22 @@ export class PageControls {
|
|||||||
this._updateCheckInProgress = true;
|
this._updateCheckInProgress = true;
|
||||||
setLoadingState(true);
|
setLoadingState(true);
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
this._updateCheckInProgress = false;
|
||||||
|
setLoadingState(false);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await performModelUpdateCheck();
|
await performModelUpdateCheck({
|
||||||
|
onComplete: handleComplete,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to check model updates:', error);
|
console.error('Failed to check model updates:', error);
|
||||||
} finally {
|
} finally {
|
||||||
this._updateCheckInProgress = false;
|
if (this._updateCheckInProgress) {
|
||||||
setLoadingState(false);
|
this._updateCheckInProgress = false;
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
dropdownGroup?.classList.remove('active');
|
dropdownGroup?.classList.remove('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,18 @@ export class ModalManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add checkUpdatesConfirmModal registration
|
||||||
|
const checkUpdatesConfirmModal = document.getElementById('checkUpdatesConfirmModal');
|
||||||
|
if (checkUpdatesConfirmModal) {
|
||||||
|
this.registerModal('checkUpdatesConfirmModal', {
|
||||||
|
element: checkUpdatesConfirmModal,
|
||||||
|
onClose: () => {
|
||||||
|
this.getModal('checkUpdatesConfirmModal').element.classList.remove('show');
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Add helpModal registration
|
// Add helpModal registration
|
||||||
const helpModal = document.getElementById('helpModal');
|
const helpModal = document.getElementById('helpModal');
|
||||||
if (helpModal) {
|
if (helpModal) {
|
||||||
@@ -339,7 +351,8 @@ export class ModalManager {
|
|||||||
id === "duplicateDeleteModal" ||
|
id === "duplicateDeleteModal" ||
|
||||||
id === "modelDuplicateDeleteModal" ||
|
id === "modelDuplicateDeleteModal" ||
|
||||||
id === "clearCacheModal" ||
|
id === "clearCacheModal" ||
|
||||||
id === "bulkDeleteModal"
|
id === "bulkDeleteModal" ||
|
||||||
|
id === "checkUpdatesConfirmModal"
|
||||||
) {
|
) {
|
||||||
modal.element.classList.add("show");
|
modal.element.classList.add("show");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { translate } from './i18nHelpers.js';
|
|||||||
import { showToast } from './uiHelpers.js';
|
import { showToast } from './uiHelpers.js';
|
||||||
import { getCompleteApiConfig, getCurrentModelType } from '../api/apiConfig.js';
|
import { getCompleteApiConfig, getCurrentModelType } from '../api/apiConfig.js';
|
||||||
import { resetAndReload } from '../api/modelApiFactory.js';
|
import { resetAndReload } from '../api/modelApiFactory.js';
|
||||||
|
import { getStorageItem, setStorageItem } from './storageHelpers.js';
|
||||||
|
import { modalManager } from '../managers/ModalManager.js';
|
||||||
|
|
||||||
|
const CHECK_UPDATES_CONFIRMATION_KEY = 'ack_check_updates_for_all_models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a model update check using the shared backend endpoint.
|
* Perform a model update check using the shared backend endpoint.
|
||||||
@@ -22,6 +26,12 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
return { status: 'unsupported', displayName, records: [], error: null };
|
return { status: 'unsupported', displayName, records: [], error: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proceed = await ensureCheckUpdatesConfirmation(displayName);
|
||||||
|
if (!proceed) {
|
||||||
|
onComplete?.({ status: 'cancelled', displayName, records: [], error: null });
|
||||||
|
return { status: 'cancelled', displayName, records: [], error: null };
|
||||||
|
}
|
||||||
|
|
||||||
const loadingMessage = translate(
|
const loadingMessage = translate(
|
||||||
'globalContextMenu.checkModelUpdates.loading',
|
'globalContextMenu.checkModelUpdates.loading',
|
||||||
{ type: displayName },
|
{ type: displayName },
|
||||||
@@ -83,3 +93,101 @@ export async function performModelUpdateCheck({ onStart, onComplete } = {}) {
|
|||||||
|
|
||||||
return { status, displayName, records, error };
|
return { status, displayName, records, error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTypePlural(displayName) {
|
||||||
|
if (!displayName) {
|
||||||
|
return 'models';
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = displayName.toLowerCase();
|
||||||
|
if (lower.endsWith('s')) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${displayName}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureCheckUpdatesConfirmation(displayName) {
|
||||||
|
const hasConfirmed = getStorageItem(CHECK_UPDATES_CONFIRMATION_KEY, false);
|
||||||
|
if (hasConfirmed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalElement = document.getElementById('checkUpdatesConfirmModal');
|
||||||
|
if (!modalElement) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typePlural = getTypePlural(displayName);
|
||||||
|
|
||||||
|
const titleElement = modalElement.querySelector('[data-role="title"]');
|
||||||
|
if (titleElement) {
|
||||||
|
titleElement.textContent = translate(
|
||||||
|
'modals.checkUpdates.title',
|
||||||
|
{ type: displayName, typePlural },
|
||||||
|
`Check updates for all ${typePlural}?`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageElement = modalElement.querySelector('[data-role="message"]');
|
||||||
|
if (messageElement) {
|
||||||
|
messageElement.textContent = translate(
|
||||||
|
'modals.checkUpdates.message',
|
||||||
|
{ type: displayName, typePlural },
|
||||||
|
`This checks every ${typePlural} in your library for updates. Large collections may take a little longer.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tipElement = modalElement.querySelector('[data-role="tip"]');
|
||||||
|
if (tipElement) {
|
||||||
|
tipElement.textContent = translate(
|
||||||
|
'modals.checkUpdates.tip',
|
||||||
|
{ type: displayName, typePlural },
|
||||||
|
'To work in smaller batches, switch to bulk mode, pick the ones you need, then use "Check Updates for Selected".'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmButton = modalElement.querySelector('[data-action="confirm-check-updates"]');
|
||||||
|
const cancelButton = modalElement.querySelector('[data-action="cancel-check-updates"]');
|
||||||
|
|
||||||
|
if (!confirmButton || !cancelButton) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
confirmButton.removeEventListener('click', handleConfirm);
|
||||||
|
cancelButton.removeEventListener('click', handleCancel);
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalize = (proceed) => {
|
||||||
|
if (resolved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved = true;
|
||||||
|
cleanup();
|
||||||
|
resolve(proceed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setStorageItem(CHECK_UPDATES_CONFIRMATION_KEY, true);
|
||||||
|
finalize(true);
|
||||||
|
modalManager.closeModal('checkUpdatesConfirmModal');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
finalize(false);
|
||||||
|
modalManager.closeModal('checkUpdatesConfirmModal');
|
||||||
|
};
|
||||||
|
|
||||||
|
confirmButton.addEventListener('click', handleConfirm);
|
||||||
|
cancelButton.addEventListener('click', handleCancel);
|
||||||
|
|
||||||
|
modalManager.showModal('checkUpdatesConfirmModal', null, () => finalize(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,3 +68,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Check Updates Confirmation Modal -->
|
||||||
|
<div id="checkUpdatesConfirmModal" class="modal delete-modal">
|
||||||
|
<div class="modal-content delete-modal-content">
|
||||||
|
<h2 data-role="title"></h2>
|
||||||
|
<p class="confirmation-message" data-role="message"></p>
|
||||||
|
<p class="confirmation-tip" data-role="tip"></p>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="cancel-btn" data-action="cancel-check-updates">{{ t('common.actions.cancel') }}</button>
|
||||||
|
<button class="primary-btn" data-action="confirm-check-updates">{{ t('modals.checkUpdates.action') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -75,6 +75,26 @@ vi.mock('../../../static/js/utils/storageHelpers.js', () => ({
|
|||||||
setSessionItem: vi.fn(),
|
setSessionItem: vi.fn(),
|
||||||
removeSessionItem: vi.fn(),
|
removeSessionItem: vi.fn(),
|
||||||
getSessionItem: vi.fn(),
|
getSessionItem: vi.fn(),
|
||||||
|
getStorageItem: vi.fn((key, defaultValue = null) => {
|
||||||
|
const value = localStorage.getItem(`lora_manager_${key}`);
|
||||||
|
if (value === null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setStorageItem: vi.fn((key, value) => {
|
||||||
|
const prefixedKey = `lora_manager_${key}`;
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
localStorage.setItem(prefixedKey, JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(prefixedKey, value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
vi.mock('../../../static/js/api/modelApiFactory.js', () => ({
|
||||||
@@ -350,11 +370,15 @@ describe('Interaction-level regression coverage', () => {
|
|||||||
expect(cleanupItem.classList.contains('disabled')).toBe(false);
|
expect(cleanupItem.classList.contains('disabled')).toBe(false);
|
||||||
expect(menu._cleanupInProgress).toBe(false);
|
expect(menu._cleanupInProgress).toBe(false);
|
||||||
|
|
||||||
|
localStorage.setItem('lora_manager_ack_check_updates_for_all_models', 'true');
|
||||||
|
|
||||||
menu.showMenu(360, 420);
|
menu.showMenu(360, 420);
|
||||||
const checkUpdatesItem = document.querySelector('[data-action="check-model-updates"]');
|
const checkUpdatesItem = document.querySelector('[data-action="check-model-updates"]');
|
||||||
checkUpdatesItem.dispatchEvent(new Event('click', { bubbles: true }));
|
checkUpdatesItem.dispatchEvent(new Event('click', { bubbles: true }));
|
||||||
expect(checkUpdatesItem.classList.contains('disabled')).toBe(true);
|
expect(checkUpdatesItem.classList.contains('disabled')).toBe(true);
|
||||||
|
|
||||||
|
await flushAsyncTasks();
|
||||||
|
|
||||||
expect(global.fetch).toHaveBeenLastCalledWith('/api/lm/loras/updates/refresh', {
|
expect(global.fetch).toHaveBeenLastCalledWith('/api/lm/loras/updates/refresh', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
Reference in New Issue
Block a user