mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
refactor(frontend): update the styling for the microagent management page. (#10494)
This commit is contained in:
parent
f296d7bde5
commit
476954f3a4
@ -444,28 +444,38 @@ describe("MicroagentManagement", () => {
|
||||
expect(filePath2).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display add microagent button in repository accordion", async () => {
|
||||
it("should render add microagent button", async () => {
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check that add microagent buttons are present
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
expect(addButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should open add microagent modal when add button is clicked", async () => {
|
||||
it("should open modal when add button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1292,11 +1302,18 @@ describe("MicroagentManagement", () => {
|
||||
it("should render add microagent button", async () => {
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check that add microagent buttons are present
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
expect(addButtons.length).toBeGreaterThan(0);
|
||||
@ -1306,11 +1323,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1361,11 +1385,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1385,11 +1416,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1408,11 +1446,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1441,11 +1486,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1468,11 +1520,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
@ -1494,11 +1553,18 @@ describe("MicroagentManagement", () => {
|
||||
const user = userEvent.setup();
|
||||
renderMicroagentManagement();
|
||||
|
||||
// Wait for repositories to be loaded
|
||||
// Wait for repositories to be loaded and processed
|
||||
await waitFor(() => {
|
||||
expect(mockUseUserRepositories).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Wait for repositories to be displayed in the accordion
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId("repository-name-tooltip"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Find and click the first add microagent button
|
||||
const addButtons = screen.getAllByTestId("add-microagent-button");
|
||||
await user.click(addButtons[0]);
|
||||
|
||||
@ -17,7 +17,7 @@ export function MicroagentManagementAccordionTitle({
|
||||
<TooltipButton
|
||||
tooltip={repository.full_name}
|
||||
ariaLabel={repository.full_name}
|
||||
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[232px]"
|
||||
className="text-white text-base font-normal bg-transparent p-0 min-w-0 h-auto cursor-pointer truncate max-w-[200px] translate-y-[-1px]"
|
||||
testId="repository-name-tooltip"
|
||||
placement="bottom"
|
||||
>
|
||||
|
||||
@ -7,8 +7,6 @@ import {
|
||||
} from "#/state/microagent-management-slice";
|
||||
import { RootState } from "#/store";
|
||||
import { GitRepository } from "#/types/git";
|
||||
import PlusIcon from "#/icons/plus.svg?react";
|
||||
import { TooltipButton } from "#/components/shared/buttons/tooltip-button";
|
||||
|
||||
interface MicroagentManagementAddMicroagentButtonProps {
|
||||
repository: GitRepository;
|
||||
@ -25,23 +23,22 @@ export function MicroagentManagementAddMicroagentButton({
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
dispatch(setAddMicroagentModalVisible(!addMicroagentModalVisible));
|
||||
dispatch(setSelectedRepository(repository));
|
||||
};
|
||||
|
||||
return (
|
||||
<div onClick={handleClick}>
|
||||
<TooltipButton
|
||||
tooltip={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
ariaLabel={t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
className="p-0 min-w-0 h-6 w-6 flex items-center justify-center bg-transparent cursor-pointer"
|
||||
testId="add-microagent-button"
|
||||
placement="bottom"
|
||||
>
|
||||
<PlusIcon width={22} height={22} />
|
||||
</TooltipButton>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className="translate-y-[-1px]"
|
||||
data-testid="add-microagent-button"
|
||||
>
|
||||
<span className="text-sm font-normal leading-5 text-[#8480FF] cursor-pointer hover:text-[#6C63FF] transition-colors duration-200">
|
||||
{t(I18nKey.COMMON$ADD_MICROAGENT)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { MicroagentManagementSidebar } from "./microagent-management-sidebar";
|
||||
import { MicroagentManagementMain } from "./microagent-management-main";
|
||||
@ -25,6 +26,12 @@ import { GitRepository } from "#/types/git";
|
||||
import { queryClient } from "#/query-client-config";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { MicroagentManagementLearnThisRepoModal } from "./microagent-management-learn-this-repo-modal";
|
||||
import {
|
||||
displaySuccessToast,
|
||||
displayErrorToast,
|
||||
} from "#/utils/custom-toast-handlers";
|
||||
import { getFirstPRUrl } from "#/utils/parse-pr-url";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
// Handle error events
|
||||
const isErrorEvent = (evt: unknown): evt is { error: true; message: string } =>
|
||||
@ -112,6 +119,8 @@ export function MicroagentManagementContent() {
|
||||
learnThisRepoModalVisible,
|
||||
} = useSelector((state: RootState) => state.microagentManagement);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { createConversationAndSubscribe, isPending } =
|
||||
@ -159,6 +168,37 @@ export function MicroagentManagementContent() {
|
||||
? (selectedRepository as GitRepository).full_name
|
||||
: "";
|
||||
|
||||
// Check if agent is running and ready to work
|
||||
if (
|
||||
isOpenHandsEvent(socketEvent) &&
|
||||
isAgentStateChangeObservation(socketEvent) &&
|
||||
socketEvent.extras.agent_state === AgentState.RUNNING
|
||||
) {
|
||||
displaySuccessToast(
|
||||
t(I18nKey.MICROAGENT_MANAGEMENT$OPENING_PR_TO_CREATE_MICROAGENT),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if agent has finished and we have a PR
|
||||
if (isOpenHandsEvent(socketEvent) && isFinishAction(socketEvent)) {
|
||||
const prUrl = getFirstPRUrl(socketEvent.args.final_thought || "");
|
||||
if (prUrl) {
|
||||
displaySuccessToast(
|
||||
t(I18nKey.MICROAGENT_MANAGEMENT$PR_READY_FOR_REVIEW),
|
||||
);
|
||||
} else {
|
||||
// Agent finished but no PR found
|
||||
displaySuccessToast(t(I18nKey.MICROAGENT_MANAGEMENT$PR_NOT_CREATED));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle error events
|
||||
if (isErrorEvent(socketEvent) || isAgentStatusError(socketEvent)) {
|
||||
displayErrorToast(
|
||||
t(I18nKey.MICROAGENT_MANAGEMENT$ERROR_CREATING_MICROAGENT),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldInvalidateConversationsList(socketEvent)) {
|
||||
invalidateConversationsList(repositoryName);
|
||||
}
|
||||
|
||||
@ -65,6 +65,18 @@ export function MicroagentManagementRepoMicroagents({
|
||||
}
|
||||
}, [conversations]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
dispatch(
|
||||
setSelectedMicroagentItem({
|
||||
microagent: null,
|
||||
conversation: null,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Show loading only when both queries are loading
|
||||
const isLoading = isLoadingMicroagents || isLoadingConversations;
|
||||
|
||||
@ -82,7 +94,7 @@ export function MicroagentManagementRepoMicroagents({
|
||||
// If there's an error with microagents, show the learn this repo component
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="pb-4">
|
||||
<div>
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
</div>
|
||||
);
|
||||
@ -93,7 +105,7 @@ export function MicroagentManagementRepoMicroagents({
|
||||
const totalItems = numberOfMicroagents + numberOfConversations;
|
||||
|
||||
return (
|
||||
<div className="pb-4">
|
||||
<div>
|
||||
{totalItems === 0 && (
|
||||
<MicroagentManagementLearnThisRepo repository={repository} />
|
||||
)}
|
||||
|
||||
@ -97,8 +97,10 @@ export function MicroagentManagementRepositories({
|
||||
variant="splitted"
|
||||
className="w-full px-0 gap-3"
|
||||
itemClasses={{
|
||||
base: "shadow-none bg-transparent border border-[#ffffff40] rounded-[6px] cursor-pointer",
|
||||
trigger: "cursor-pointer gap-1",
|
||||
base: "shadow-none bg-transparent cursor-pointer px-0",
|
||||
trigger: "cursor-pointer gap-2 py-3",
|
||||
indicator:
|
||||
"flex items-center justify-center p-0.5 pr-[3px] text-white hover:bg-[#454545] rounded transition-colors duration-200 rotate-180",
|
||||
}}
|
||||
selectionMode="multiple"
|
||||
>
|
||||
|
||||
@ -23,7 +23,7 @@ export function ModalBackdrop({ children, onClose }: ModalBackdropProps) {
|
||||
<div className="fixed inset-0 flex items-center justify-center z-20">
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="fixed inset-0 bg-black bg-opacity-80"
|
||||
className="fixed inset-0 bg-black opacity-60"
|
||||
/>
|
||||
<div className="relative">{children}</div>
|
||||
</div>
|
||||
|
||||
@ -810,4 +810,8 @@ export enum I18nKey {
|
||||
PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION = "PROJECT_MANAGEMENT$CONFIGURE_MODAL_DESCRIPTION",
|
||||
PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION = "PROJECT_MANAGEMENT$IMPORTANT_WORKSPACE_INTEGRATION",
|
||||
SETTINGS = "SETTINGS",
|
||||
MICROAGENT_MANAGEMENT$OPENING_PR_TO_CREATE_MICROAGENT = "MICROAGENT_MANAGEMENT$OPENING_PR_TO_CREATE_MICROAGENT",
|
||||
MICROAGENT_MANAGEMENT$PR_READY_FOR_REVIEW = "MICROAGENT_MANAGEMENT$PR_READY_FOR_REVIEW",
|
||||
MICROAGENT_MANAGEMENT$PR_NOT_CREATED = "MICROAGENT_MANAGEMENT$PR_NOT_CREATED",
|
||||
MICROAGENT_MANAGEMENT$ERROR_CREATING_MICROAGENT = "MICROAGENT_MANAGEMENT$ERROR_CREATING_MICROAGENT",
|
||||
}
|
||||
|
||||
@ -12958,5 +12958,69 @@
|
||||
"tr": "A server with this URL already exists for the selected type",
|
||||
"de": "A server with this URL already exists for the selected type",
|
||||
"uk": "A server with this URL already exists for the selected type"
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$OPENING_PR_TO_CREATE_MICROAGENT": {
|
||||
"en": "Opening a PR to create the microagent for you...",
|
||||
"ja": "マイクロエージェントを作成するためのプルリクエストを作成しています...",
|
||||
"zh-CN": "正在为您创建微代理的拉取请求...",
|
||||
"zh-TW": "正在為您建立微代理的拉取請求...",
|
||||
"ko-KR": "마이크로에이전트를 생성하기 위한 PR을 열고 있습니다...",
|
||||
"no": "Åpner en PR for å opprette mikroagenten for deg...",
|
||||
"it": "Apertura di una PR per creare il microagente per te...",
|
||||
"pt": "Abrindo um PR para criar o microagente para você...",
|
||||
"es": "Abriendo un PR para crear el microagente para ti...",
|
||||
"ar": "يتم فتح طلب سحب لإنشاء الوكيل الدقيق من أجلك...",
|
||||
"fr": "Ouverture d'une PR pour créer le microagent pour vous...",
|
||||
"tr": "Sizin için mikro ajanı oluşturmak üzere bir PR açılıyor...",
|
||||
"de": "Es wird ein PR geöffnet, um den Microagent für Sie zu erstellen...",
|
||||
"uk": "Відкривається PR для створення мікроагента для вас..."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$PR_READY_FOR_REVIEW": {
|
||||
"en": "PR is ready for review! The microagent has been created successfully.",
|
||||
"ja": "PRのレビューが可能です!マイクロエージェントが正常に作成されました。",
|
||||
"zh-CN": "PR已准备好审核!微代理已成功创建。",
|
||||
"zh-TW": "PR 已準備好審查!微代理已成功建立。",
|
||||
"ko-KR": "PR이 검토를 위해 준비되었습니다! 마이크로에이전트가 성공적으로 생성되었습니다.",
|
||||
"no": "PR er klar for gjennomgang! Mikroagenten har blitt opprettet.",
|
||||
"it": "La PR è pronta per la revisione! Il microagente è stato creato con successo.",
|
||||
"pt": "PR pronto para revisão! O microagente foi criado com sucesso.",
|
||||
"es": "¡La PR está lista para revisión! El microagente se ha creado correctamente.",
|
||||
"ar": "طلب السحب جاهز للمراجعة! تم إنشاء الوكيل الدقيق بنجاح.",
|
||||
"fr": "La PR est prête pour révision ! Le microagent a été créé avec succès.",
|
||||
"tr": "PR incelemeye hazır! Mikro ajan başarıyla oluşturuldu.",
|
||||
"de": "PR ist bereit zur Überprüfung! Der Microagent wurde erfolgreich erstellt.",
|
||||
"uk": "PR готовий до перегляду! Мікроагента успішно створено."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$PR_NOT_CREATED": {
|
||||
"en": "The agent has finished its task but was unable to create a PR.",
|
||||
"ja": "エージェントはタスクを完了しましたが、PRを作成できませんでした。",
|
||||
"zh-CN": "代理已完成任务,但无法创建 PR。",
|
||||
"zh-TW": "代理已完成任務,但無法建立 PR。",
|
||||
"ko-KR": "에이전트가 작업을 완료했지만 PR을 생성할 수 없었습니다.",
|
||||
"no": "Agenten har fullført oppgaven, men klarte ikke å opprette en PR.",
|
||||
"it": "L'agente ha terminato il suo compito ma non è riuscito a creare una PR.",
|
||||
"pt": "O agente concluiu sua tarefa, mas não conseguiu criar um PR.",
|
||||
"es": "El agente ha terminado su tarea pero no pudo crear un PR.",
|
||||
"ar": "أكمل الوكيل مهمته لكنه لم يتمكن من إنشاء طلب سحب (PR).",
|
||||
"fr": "L'agent a terminé sa tâche mais n'a pas pu créer de PR.",
|
||||
"tr": "Ajan görevini tamamladı ancak bir PR oluşturamadı.",
|
||||
"de": "Der Agent hat seine Aufgabe abgeschlossen, konnte aber keinen PR erstellen.",
|
||||
"uk": "Агент завершив завдання, але не зміг створити PR."
|
||||
},
|
||||
"MICROAGENT_MANAGEMENT$ERROR_CREATING_MICROAGENT": {
|
||||
"en": "Something went wrong. Try initiating the microagent again.",
|
||||
"ja": "問題が発生しました。もう一度マイクロエージェントを開始してください。",
|
||||
"zh-CN": "出现了问题。请重试启动微代理。",
|
||||
"zh-TW": "發生錯誤。請再次嘗試啟動微代理。",
|
||||
"ko-KR": "문제가 발생했습니다. 마이크로에이전트를 다시 시작해 보세요.",
|
||||
"no": "Noe gikk galt. Prøv å starte mikroagenten på nytt.",
|
||||
"it": "Qualcosa è andato storto. Prova a iniziare di nuovo il microagente.",
|
||||
"pt": "Algo deu errado. Tente iniciar o microagente novamente.",
|
||||
"es": "Algo salió mal. Intenta iniciar el microagente de nuevo.",
|
||||
"ar": "حدث خطأ ما. حاول بدء تشغيل الوكيل الدقيق مرة أخرى.",
|
||||
"fr": "Une erreur s'est produite. Essayez de relancer le microagent.",
|
||||
"tr": "Bir şeyler ters gitti. Mikro ajanı tekrar başlatmayı deneyin.",
|
||||
"de": "Etwas ist schiefgelaufen. Versuchen Sie, den Microagenten erneut zu starten.",
|
||||
"uk": "Щось пішло не так. Спробуйте ініціювати мікроагента ще раз."
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user