forked from baron/baron-sso
75f192fb24 기준 병합 code-check 수정
This commit is contained in:
15
Makefile
15
Makefile
@@ -280,7 +280,11 @@ code-check-front-lint:
|
|||||||
cd adminfront && npx biome format .
|
cd adminfront && npx biome format .
|
||||||
@echo "==> devfront biome lint/format check"
|
@echo "==> devfront biome lint/format check"
|
||||||
rm -rf devfront/playwright-report devfront/test-results
|
rm -rf devfront/playwright-report devfront/test-results
|
||||||
cd devfront && npm ci --ignore-scripts
|
@if [ -d devfront/node_modules ]; then \
|
||||||
|
echo "devfront/node_modules already present; skipping npm install."; \
|
||||||
|
else \
|
||||||
|
cd devfront && npm ci --ignore-scripts; \
|
||||||
|
fi
|
||||||
cd devfront && npx biome lint .
|
cd devfront && npx biome lint .
|
||||||
cd devfront && npx biome format .
|
cd devfront && npx biome format .
|
||||||
@echo "==> orgfront biome lint/format check"
|
@echo "==> orgfront biome lint/format check"
|
||||||
@@ -324,7 +328,14 @@ code-check-devfront-tests:
|
|||||||
@mkdir -p reports/devfront
|
@mkdir -p reports/devfront
|
||||||
@rm -rf reports/devfront/playwright-report reports/devfront/test-results
|
@rm -rf reports/devfront/playwright-report reports/devfront/test-results
|
||||||
@status=0; \
|
@status=0; \
|
||||||
|
preview_pattern='[v]ite preview --host 127.0.0.1 --strictPort --port 4174'; \
|
||||||
|
pkill -f "$$preview_pattern" >/dev/null 2>&1 || true; \
|
||||||
|
trap 'pkill -f "$$preview_pattern" >/dev/null 2>&1 || true' EXIT INT TERM; \
|
||||||
|
if [ -d devfront/node_modules ]; then \
|
||||||
|
echo "devfront/node_modules already present; skipping npm install."; \
|
||||||
|
else \
|
||||||
(cd devfront && npm ci --ignore-scripts) || status=$$?; \
|
(cd devfront && npm ci --ignore-scripts) || status=$$?; \
|
||||||
|
fi; \
|
||||||
if [ $$status -eq 0 ]; then \
|
if [ $$status -eq 0 ]; then \
|
||||||
(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
|
(cd devfront && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
@@ -388,7 +399,7 @@ code-check-userfront-e2e-tests:
|
|||||||
(cd "$$tmp_dir/userfront" && flutter build web --wasm --release) || status=$$?; \
|
(cd "$$tmp_dir/userfront" && flutter build web --wasm --release) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ $$status -eq 0 ]; then \
|
if [ $$status -eq 0 ]; then \
|
||||||
(cd "$$tmp_dir/userfront-e2e" && $(PLAYWRIGHT_INSTALL_CHROMIUM)) || status=$$?; \
|
(cd "$$tmp_dir/userfront-e2e" && $(PLAYWRIGHT_INSTALL_ALL)) || status=$$?; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ $$status -eq 0 ]; then \
|
if [ $$status -eq 0 ]; then \
|
||||||
port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \
|
port="$$(node -e "const net=require('node:net'); const s=net.createServer(); s.listen(0,'127.0.0.1',()=>{console.log(s.address().port); s.close();});")"; \
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const configuredWorkers = process.env.PLAYWRIGHT_WORKERS
|
|||||||
const skipWebServer =
|
const skipWebServer =
|
||||||
process.env.PLAYWRIGHT_SKIP_WEBSERVER === "1" ||
|
process.env.PLAYWRIGHT_SKIP_WEBSERVER === "1" ||
|
||||||
process.env.PLAYWRIGHT_SKIP_WEBSERVER === "true";
|
process.env.PLAYWRIGHT_SKIP_WEBSERVER === "true";
|
||||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL || "http://127.0.0.1:5176";
|
const baseURL = process.env.PLAYWRIGHT_BASE_URL || "http://127.0.0.1:4174";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
@@ -74,7 +74,7 @@ export default defineConfig({
|
|||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
command:
|
command:
|
||||||
"VITE_OIDC_AUTHORITY=http://localhost:5000/oidc ./node_modules/.bin/vite build && ./node_modules/.bin/vite preview --host 127.0.0.1 --strictPort --port 5176",
|
"VITE_OIDC_AUTHORITY=http://localhost:5000/oidc ./node_modules/.bin/vite build && ./node_modules/.bin/vite preview --host 127.0.0.1 --strictPort --port 4174",
|
||||||
url: baseURL,
|
url: baseURL,
|
||||||
reuseExistingServer: false,
|
reuseExistingServer: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -158,7 +158,9 @@ describe("AuditLogsPage", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const container = await renderPage();
|
const container = await renderPage();
|
||||||
expect(container.textContent).toContain("감사 로그는 개발자 권한이 있어야 볼 수 있습니다.");
|
expect(container.textContent).toContain(
|
||||||
|
"감사 로그는 개발자 권한이 있어야 볼 수 있습니다.",
|
||||||
|
);
|
||||||
|
|
||||||
const button = Array.from(container.querySelectorAll("button")).find(
|
const button = Array.from(container.querySelectorAll("button")).find(
|
||||||
(item) => item.textContent?.includes("개발자 권한 신청"),
|
(item) => item.textContent?.includes("개발자 권한 신청"),
|
||||||
@@ -177,7 +179,9 @@ describe("AuditLogsPage", () => {
|
|||||||
.spyOn(URL, "createObjectURL")
|
.spyOn(URL, "createObjectURL")
|
||||||
.mockReturnValue("blob:csv");
|
.mockReturnValue("blob:csv");
|
||||||
const revokeObjectURL = vi.spyOn(URL, "revokeObjectURL").mockReturnValue();
|
const revokeObjectURL = vi.spyOn(URL, "revokeObjectURL").mockReturnValue();
|
||||||
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => {});
|
const clickSpy = vi
|
||||||
|
.spyOn(HTMLAnchorElement.prototype, "click")
|
||||||
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
const container = await renderPage();
|
const container = await renderPage();
|
||||||
expect(container.textContent).toContain("table:1");
|
expect(container.textContent).toContain("table:1");
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ vi.mock("react-oidc-context", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("react-router-dom", async () => {
|
vi.mock("react-router-dom", async () => {
|
||||||
const actual = await vi.importActual<typeof import("react-router-dom")>(
|
const actual =
|
||||||
|
await vi.importActual<typeof import("react-router-dom")>(
|
||||||
"react-router-dom",
|
"react-router-dom",
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -175,7 +176,9 @@ describe("ClientsPage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const container = await renderPage();
|
const container = await renderPage();
|
||||||
expect(container.textContent).toContain("총 6개의 애플리케이션이 등록되어 있습니다.");
|
expect(container.textContent).toContain(
|
||||||
|
"총 6개의 애플리케이션이 등록되어 있습니다.",
|
||||||
|
);
|
||||||
expect(container.textContent).toContain("App 6");
|
expect(container.textContent).toContain("App 6");
|
||||||
expect(container.textContent).toContain("App 2");
|
expect(container.textContent).toContain("App 2");
|
||||||
expect(container.textContent).not.toContain("App 1");
|
expect(container.textContent).not.toContain("App 1");
|
||||||
@@ -192,26 +195,27 @@ describe("ClientsPage", () => {
|
|||||||
expect(container.textContent).toContain("App 6");
|
expect(container.textContent).toContain("App 6");
|
||||||
expect(container.textContent).toContain("접기");
|
expect(container.textContent).toContain("접기");
|
||||||
|
|
||||||
const advancedButton = Array.from(container.querySelectorAll("button")).find(
|
const advancedButton = Array.from(
|
||||||
(button) => button.textContent === "Advanced Filters",
|
container.querySelectorAll("button"),
|
||||||
);
|
).find((button) => button.textContent === "Advanced Filters");
|
||||||
expect(advancedButton).toBeTruthy();
|
expect(advancedButton).toBeTruthy();
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
advancedButton?.dispatchEvent(
|
advancedButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
new MouseEvent("click", { bubbles: true }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchInput = Array.from(
|
const searchInput = Array.from(container.querySelectorAll("input")).find(
|
||||||
container.querySelectorAll("input"),
|
(input) =>
|
||||||
).find((input) =>
|
input
|
||||||
input.getAttribute("placeholder")?.includes("클라이언트 이름/ID로 검색"),
|
.getAttribute("placeholder")
|
||||||
|
?.includes("클라이언트 이름/ID로 검색"),
|
||||||
) as HTMLInputElement | undefined;
|
) as HTMLInputElement | undefined;
|
||||||
expect(searchInput).toBeTruthy();
|
if (!searchInput) {
|
||||||
|
throw new Error("Expected search input to be rendered");
|
||||||
|
}
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await setInputValue(searchInput!, "missing-client");
|
await setInputValue(searchInput, "missing-client");
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container.textContent).toContain("조건에 맞는 연동 앱이 없습니다.");
|
expect(container.textContent).toContain("조건에 맞는 연동 앱이 없습니다.");
|
||||||
@@ -226,7 +230,7 @@ describe("ClientsPage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await setInputValue(searchInput!, "");
|
await setInputValue(searchInput, "");
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container.textContent).toContain("App 1");
|
expect(container.textContent).toContain("App 1");
|
||||||
|
|||||||
@@ -532,10 +532,12 @@ function ClientsPage() {
|
|||||||
t("ui.dev.clients.untitled", "Untitled")}
|
t("ui.dev.clients.untitled", "Untitled")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
|
<span aria-hidden="true">
|
||||||
{t(
|
{t(
|
||||||
"ui.dev.clients.tenant_scoped",
|
"ui.dev.clients.tenant_scoped",
|
||||||
"Tenant-scoped",
|
"Tenant-scoped",
|
||||||
)}
|
)}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -615,14 +617,9 @@ function ClientsPage() {
|
|||||||
"ui.dev.clients.list.collapse_aria",
|
"ui.dev.clients.list.collapse_aria",
|
||||||
"연동 앱 목록 접기",
|
"연동 앱 목록 접기",
|
||||||
)
|
)
|
||||||
: t(
|
: t("ui.dev.clients.list.more_aria", "연동 앱 목록 더보기")
|
||||||
"ui.dev.clients.list.more_aria",
|
|
||||||
"연동 앱 목록 더보기",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={() =>
|
|
||||||
setIsClientListExpanded((current) => !current)
|
|
||||||
}
|
}
|
||||||
|
onClick={() => setIsClientListExpanded((current) => !current)}
|
||||||
>
|
>
|
||||||
{isClientListExpanded
|
{isClientListExpanded
|
||||||
? t("ui.common.collapse", "접기")
|
? t("ui.common.collapse", "접기")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ vi.mock("../../../components/ui/avatar", () => ({
|
|||||||
Avatar: ({ children }: { children: ReactNode }) => (
|
Avatar: ({ children }: { children: ReactNode }) => (
|
||||||
<div data-testid="avatar">{children}</div>
|
<div data-testid="avatar">{children}</div>
|
||||||
),
|
),
|
||||||
AvatarImage: (props: ComponentProps<"img">) => <img {...props} />,
|
AvatarImage: (props: ComponentProps<"img">) => <img alt="" {...props} />,
|
||||||
AvatarFallback: ({ children }: { children: ReactNode }) => (
|
AvatarFallback: ({ children }: { children: ReactNode }) => (
|
||||||
<div data-testid="fallback">{children}</div>
|
<div data-testid="fallback">{children}</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const listIdpConfigsMock = vi.fn();
|
|||||||
const createIdpConfigMock = vi.fn();
|
const createIdpConfigMock = vi.fn();
|
||||||
|
|
||||||
vi.mock("react-router-dom", async () => {
|
vi.mock("react-router-dom", async () => {
|
||||||
const actual = await vi.importActual<typeof import("react-router-dom")>(
|
const actual =
|
||||||
|
await vi.importActual<typeof import("react-router-dom")>(
|
||||||
"react-router-dom",
|
"react-router-dom",
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -19,10 +20,8 @@ vi.mock("react-router-dom", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("../../../lib/devApi", () => ({
|
vi.mock("../../../lib/devApi", () => ({
|
||||||
listIdpConfigsForClient: (clientId: string) =>
|
listIdpConfigsForClient: (clientId: string) => listIdpConfigsMock(clientId),
|
||||||
listIdpConfigsMock(clientId),
|
createIdpConfigForClient: (payload: unknown) => createIdpConfigMock(payload),
|
||||||
createIdpConfigForClient: (payload: unknown) =>
|
|
||||||
createIdpConfigMock(payload),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../../../lib/i18n", () => ({
|
vi.mock("../../../lib/i18n", () => ({
|
||||||
@@ -146,16 +145,15 @@ describe("ClientFederationPage", () => {
|
|||||||
'input[name="oidc_client_secret"]',
|
'input[name="oidc_client_secret"]',
|
||||||
) as HTMLInputElement | null;
|
) as HTMLInputElement | null;
|
||||||
|
|
||||||
expect(displayName).toBeTruthy();
|
if (!displayName || !issuerUrl || !clientId || !clientSecret) {
|
||||||
expect(issuerUrl).toBeTruthy();
|
throw new Error("Expected federation form inputs to be rendered");
|
||||||
expect(clientId).toBeTruthy();
|
}
|
||||||
expect(clientSecret).toBeTruthy();
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await setInputValue(displayName!, "New Provider");
|
await setInputValue(displayName, "New Provider");
|
||||||
await setInputValue(issuerUrl!, "https://login.example");
|
await setInputValue(issuerUrl, "https://login.example");
|
||||||
await setInputValue(clientId!, "client-oidc");
|
await setInputValue(clientId, "client-oidc");
|
||||||
await setInputValue(clientSecret!, "secret-value");
|
await setInputValue(clientSecret, "secret-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
||||||
|
|||||||
@@ -159,10 +159,12 @@ describe("DeveloperRequestPage", () => {
|
|||||||
const reasonField = container.querySelector(
|
const reasonField = container.querySelector(
|
||||||
"textarea",
|
"textarea",
|
||||||
) as HTMLTextAreaElement | null;
|
) as HTMLTextAreaElement | null;
|
||||||
expect(reasonField).toBeTruthy();
|
if (!reasonField) {
|
||||||
|
throw new Error("Expected reason textarea to be rendered");
|
||||||
|
}
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await setTextAreaValue(reasonField!, "Need RP access");
|
await setTextAreaValue(reasonField, "Need RP access");
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
||||||
|
|||||||
@@ -119,9 +119,7 @@ function resolveAppLocale(): AppLocale {
|
|||||||
return pathLocale;
|
return pathLocale;
|
||||||
}
|
}
|
||||||
|
|
||||||
return window.navigator.language.toLowerCase().startsWith("ko")
|
return window.navigator.language.toLowerCase().startsWith("ko") ? "ko" : "en";
|
||||||
? "ko"
|
|
||||||
: "en";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRecentChangeTimestamp(value: string) {
|
function formatRecentChangeTimestamp(value: string) {
|
||||||
@@ -390,7 +388,10 @@ function summarizeRecentChanges(
|
|||||||
items: RecentClientChange[],
|
items: RecentClientChange[],
|
||||||
period: RPUsagePeriod,
|
period: RPUsagePeriod,
|
||||||
): RecentChangePoint[] {
|
): RecentChangePoint[] {
|
||||||
const byDate = new Map<string, { changeCount: number; actors: Set<string> }>();
|
const byDate = new Map<
|
||||||
|
string,
|
||||||
|
{ changeCount: number; actors: Set<string> }
|
||||||
|
>();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const bucket = toPeriodBucket(item.timestamp.slice(0, 10), period);
|
const bucket = toPeriodBucket(item.timestamp.slice(0, 10), period);
|
||||||
const current = byDate.get(bucket) ?? {
|
const current = byDate.get(bucket) ?? {
|
||||||
@@ -447,7 +448,9 @@ function buildRecentChangeSeries(
|
|||||||
items: RecentClientChange[],
|
items: RecentClientChange[],
|
||||||
period: RPUsagePeriod,
|
period: RPUsagePeriod,
|
||||||
): RecentChangeSeries[] {
|
): RecentChangeSeries[] {
|
||||||
const dates = summarizeRecentChanges(items, period).map((point) => point.date);
|
const dates = summarizeRecentChanges(items, period).map(
|
||||||
|
(point) => point.date,
|
||||||
|
);
|
||||||
const byClient = new Map<
|
const byClient = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -937,7 +940,7 @@ function GlobalOverviewPage() {
|
|||||||
const [visibleRecentClientChangesCount, setVisibleRecentClientChangesCount] =
|
const [visibleRecentClientChangesCount, setVisibleRecentClientChangesCount] =
|
||||||
useState(6);
|
useState(6);
|
||||||
const [isRecentChangesDetailOpen, setIsRecentChangesDetailOpen] =
|
const [isRecentChangesDetailOpen, setIsRecentChangesDetailOpen] =
|
||||||
useState(false);
|
useState(true);
|
||||||
const usageDays = period === "day" ? 14 : period === "week" ? 84 : 90;
|
const usageDays = period === "day" ? 14 : period === "week" ? 84 : 90;
|
||||||
const statsQuery = useQuery({
|
const statsQuery = useQuery({
|
||||||
queryKey: ["dev-dashboard-stats"],
|
queryKey: ["dev-dashboard-stats"],
|
||||||
@@ -1112,8 +1115,7 @@ function GlobalOverviewPage() {
|
|||||||
),
|
),
|
||||||
[currentClientIdSet, recentClientChangesWithActors],
|
[currentClientIdSet, recentClientChangesWithActors],
|
||||||
);
|
);
|
||||||
const recentChangeFilterOptions = useMemo<ClientFilterOption[]>(
|
const recentChangeFilterOptions = useMemo<ClientFilterOption[]>(() => {
|
||||||
() => {
|
|
||||||
const activeOptions = Array.from(
|
const activeOptions = Array.from(
|
||||||
new Map(
|
new Map(
|
||||||
recentClientChangesWithActors
|
recentClientChangesWithActors
|
||||||
@@ -1133,19 +1135,14 @@ function GlobalOverviewPage() {
|
|||||||
...activeOptions,
|
...activeOptions,
|
||||||
{
|
{
|
||||||
id: deletedRecentChangeFilterId,
|
id: deletedRecentChangeFilterId,
|
||||||
label: t(
|
label: t("ui.dev.dashboard.recent_changes.deleted_group", "삭제된 앱"),
|
||||||
"ui.dev.dashboard.recent_changes.deleted_group",
|
|
||||||
"삭제된 앱",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
}, [
|
||||||
[
|
|
||||||
currentClientIdSet,
|
currentClientIdSet,
|
||||||
deletedRecentChangeClientIds.length,
|
deletedRecentChangeClientIds.length,
|
||||||
recentClientChangesWithActors,
|
recentClientChangesWithActors,
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
const filteredRecentClientChanges = useMemo(() => {
|
const filteredRecentClientChanges = useMemo(() => {
|
||||||
if (selectedRecentChangeClientIds.length === 0) {
|
if (selectedRecentChangeClientIds.length === 0) {
|
||||||
return recentClientChangesWithActors;
|
return recentClientChangesWithActors;
|
||||||
@@ -1155,7 +1152,8 @@ function GlobalOverviewPage() {
|
|||||||
return recentClientChangesWithActors.filter(
|
return recentClientChangesWithActors.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
selectedSet.has(item.clientId) ||
|
selectedSet.has(item.clientId) ||
|
||||||
(includeDeletedGroup && deletedRecentChangeClientIds.includes(item.clientId)),
|
(includeDeletedGroup &&
|
||||||
|
deletedRecentChangeClientIds.includes(item.clientId)),
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
deletedRecentChangeClientIds,
|
deletedRecentChangeClientIds,
|
||||||
@@ -1163,7 +1161,8 @@ function GlobalOverviewPage() {
|
|||||||
selectedRecentChangeClientIds,
|
selectedRecentChangeClientIds,
|
||||||
]);
|
]);
|
||||||
const selectedRecentChangeSeries = useMemo(
|
const selectedRecentChangeSeries = useMemo(
|
||||||
() => buildRecentChangeSeries(filteredRecentClientChanges, recentChangesPeriod),
|
() =>
|
||||||
|
buildRecentChangeSeries(filteredRecentClientChanges, recentChangesPeriod),
|
||||||
[filteredRecentClientChanges, recentChangesPeriod],
|
[filteredRecentClientChanges, recentChangesPeriod],
|
||||||
);
|
);
|
||||||
const recentChangedClientCount = useMemo(
|
const recentChangedClientCount = useMemo(
|
||||||
@@ -1180,7 +1179,9 @@ function GlobalOverviewPage() {
|
|||||||
new Set(
|
new Set(
|
||||||
filteredRecentClientChanges
|
filteredRecentClientChanges
|
||||||
.map((item) => item.clientId)
|
.map((item) => item.clientId)
|
||||||
.filter((clientId) => deletedRecentChangeClientIds.includes(clientId)),
|
.filter((clientId) =>
|
||||||
|
deletedRecentChangeClientIds.includes(clientId),
|
||||||
|
),
|
||||||
).size,
|
).size,
|
||||||
[deletedRecentChangeClientIds, filteredRecentClientChanges],
|
[deletedRecentChangeClientIds, filteredRecentClientChanges],
|
||||||
);
|
);
|
||||||
@@ -1254,7 +1255,7 @@ function GlobalOverviewPage() {
|
|||||||
setVisibleRecentClientChangesCount((current) =>
|
setVisibleRecentClientChangesCount((current) =>
|
||||||
Math.min(Math.max(6, current), filteredRecentClientChanges.length),
|
Math.min(Math.max(6, current), filteredRecentClientChanges.length),
|
||||||
);
|
);
|
||||||
}, [filteredRecentClientChanges.length, selectedRecentChangeClientIds]);
|
}, [filteredRecentClientChanges.length]);
|
||||||
|
|
||||||
if (isLoadingDeveloperAccessGate) {
|
if (isLoadingDeveloperAccessGate) {
|
||||||
return (
|
return (
|
||||||
@@ -1533,8 +1534,9 @@ function GlobalOverviewPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
visibleRecentClientChanges.map((item) => {
|
visibleRecentClientChanges.map((item) => {
|
||||||
const { date, time } =
|
const { date, time } = formatRecentChangeTimestamp(
|
||||||
formatRecentChangeTimestamp(item.timestamp);
|
item.timestamp,
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.eventId}
|
key={item.eventId}
|
||||||
@@ -1600,7 +1602,6 @@ function GlobalOverviewPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ describe("recent client changes", () => {
|
|||||||
mockLocale("en");
|
mockLocale("en");
|
||||||
|
|
||||||
expect(getRecentClientActionLabel("CREATE_CLIENT")).toBe("App creation");
|
expect(getRecentClientActionLabel("CREATE_CLIENT")).toBe("App creation");
|
||||||
expect(getRecentClientActionLabel("UPDATE_CLIENT")).toBe("Settings changes");
|
expect(getRecentClientActionLabel("UPDATE_CLIENT")).toBe(
|
||||||
|
"Settings changes",
|
||||||
|
);
|
||||||
expect(getRecentClientActionLabel("UPDATE_CLIENT_STATUS")).toBe(
|
expect(getRecentClientActionLabel("UPDATE_CLIENT_STATUS")).toBe(
|
||||||
"Status changes",
|
"Status changes",
|
||||||
);
|
);
|
||||||
@@ -64,7 +66,9 @@ describe("recent client changes", () => {
|
|||||||
"Client secret rotation",
|
"Client secret rotation",
|
||||||
);
|
);
|
||||||
expect(getRecentClientActionLabel("ADD_RELATION")).toBe("Add Relationship");
|
expect(getRecentClientActionLabel("ADD_RELATION")).toBe("Add Relationship");
|
||||||
expect(getRecentClientActionLabel("REMOVE_RELATION")).toBe("Remove");
|
expect(getRecentClientActionLabel("REMOVE_RELATION")).toBe(
|
||||||
|
"Remove Relationship",
|
||||||
|
);
|
||||||
expect(getRecentClientActionLabel("DELETE_CLIENT")).toBe("App deletion");
|
expect(getRecentClientActionLabel("DELETE_CLIENT")).toBe("App deletion");
|
||||||
expect(getRecentClientActionLabel("OTHER_ACTION")).toBe("OTHER_ACTION");
|
expect(getRecentClientActionLabel("OTHER_ACTION")).toBe("OTHER_ACTION");
|
||||||
|
|
||||||
@@ -90,12 +94,26 @@ describe("recent client changes", () => {
|
|||||||
it("builds recent client changes with sorting, filtering, and detail slicing", () => {
|
it("builds recent client changes with sorting, filtering, and detail slicing", () => {
|
||||||
mockLocale("ko");
|
mockLocale("ko");
|
||||||
|
|
||||||
const clients = [makeClient("client-a", "Alpha"), makeClient("client-b", "")];
|
const clients = [
|
||||||
|
makeClient("client-a", "Alpha"),
|
||||||
|
makeClient("client-b", ""),
|
||||||
|
];
|
||||||
const auditLogs = [
|
const auditLogs = [
|
||||||
makeAuditLog("evt-1", "2026-05-27T07:00:00.000Z", "CREATE_CLIENT", "client-a", {
|
makeAuditLog(
|
||||||
|
"evt-1",
|
||||||
|
"2026-05-27T07:00:00.000Z",
|
||||||
|
"CREATE_CLIENT",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
after: { name: "Alpha", type: "private", status: "active" },
|
after: { name: "Alpha", type: "private", status: "active" },
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-2", "2026-05-27T08:00:00.000Z", "UPDATE_CLIENT", "client-a", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-2",
|
||||||
|
"2026-05-27T08:00:00.000Z",
|
||||||
|
"UPDATE_CLIENT",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
before: {
|
before: {
|
||||||
name: "Alpha old",
|
name: "Alpha old",
|
||||||
status: "inactive",
|
status: "inactive",
|
||||||
@@ -108,41 +126,75 @@ describe("recent client changes", () => {
|
|||||||
sameField: "same",
|
sameField: "same",
|
||||||
newField: "new-value",
|
newField: "new-value",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-3", "2026-05-27T09:00:00.000Z", "UPDATE_CLIENT_STATUS", "client-a", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-3",
|
||||||
|
"2026-05-27T09:00:00.000Z",
|
||||||
|
"UPDATE_CLIENT_STATUS",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
before: { status: "inactive" },
|
before: { status: "inactive" },
|
||||||
after: { status: "active" },
|
after: { status: "active" },
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-4", "2026-05-27T10:00:00.000Z", "ADD_RELATION", "client-b", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-4",
|
||||||
|
"2026-05-27T10:00:00.000Z",
|
||||||
|
"ADD_RELATION",
|
||||||
|
"client-b",
|
||||||
|
{
|
||||||
after: {
|
after: {
|
||||||
relation: "audit_viewer",
|
relation: "audit_viewer",
|
||||||
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-5", "2026-05-27T11:00:00.000Z", "REMOVE_RELATION", "client-b", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-5",
|
||||||
|
"2026-05-27T11:00:00.000Z",
|
||||||
|
"REMOVE_RELATION",
|
||||||
|
"client-b",
|
||||||
|
{
|
||||||
before: {
|
before: {
|
||||||
relation: "admins",
|
relation: "admins",
|
||||||
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
subject: "User:89692983-f512-4d96-845d-ac6123d08b95",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-6", "2026-05-27T12:00:00.000Z", "ROTATE_SECRET", "client-a", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-6",
|
||||||
|
"2026-05-27T12:00:00.000Z",
|
||||||
|
"ROTATE_SECRET",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
after: {},
|
after: {},
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-7", "2026-05-27T13:00:00.000Z", "DELETE_CLIENT", "client-a", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-7",
|
||||||
|
"2026-05-27T13:00:00.000Z",
|
||||||
|
"DELETE_CLIENT",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
before: {
|
before: {
|
||||||
name: "Alpha",
|
name: "Alpha",
|
||||||
status: "inactive",
|
status: "inactive",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
makeAuditLog("evt-8", "2026-05-27T14:00:00.000Z", "UNSUPPORTED_ACTION", "client-a", {
|
),
|
||||||
|
makeAuditLog(
|
||||||
|
"evt-8",
|
||||||
|
"2026-05-27T14:00:00.000Z",
|
||||||
|
"UNSUPPORTED_ACTION",
|
||||||
|
"client-a",
|
||||||
|
{
|
||||||
after: { name: "Ignored" },
|
after: { name: "Ignored" },
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const changes = buildRecentClientChanges(
|
const changes = buildRecentClientChanges(auditLogs, clients);
|
||||||
auditLogs,
|
|
||||||
clients,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(changes).toHaveLength(7);
|
expect(changes).toHaveLength(7);
|
||||||
expect(changes[0]).toMatchObject({
|
expect(changes[0]).toMatchObject({
|
||||||
@@ -164,7 +216,7 @@ describe("recent client changes", () => {
|
|||||||
expect(changes[2]).toMatchObject({
|
expect(changes[2]).toMatchObject({
|
||||||
eventId: "evt-5",
|
eventId: "evt-5",
|
||||||
clientName: "client-b",
|
clientName: "client-b",
|
||||||
actionLabel: "제외",
|
actionLabel: "관계 삭제",
|
||||||
detailLabels: [
|
detailLabels: [
|
||||||
{ label: "관계", value: "admins" },
|
{ label: "관계", value: "admins" },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function getRecentClientActionLabel(action: string) {
|
|||||||
case "ADD_RELATION":
|
case "ADD_RELATION":
|
||||||
return t("ui.dev.clients.relationships.add_title", "관계 추가");
|
return t("ui.dev.clients.relationships.add_title", "관계 추가");
|
||||||
case "REMOVE_RELATION":
|
case "REMOVE_RELATION":
|
||||||
return t("ui.common.remove", "Remove");
|
return t("ui.dev.clients.relationships.remove_title", "관계 삭제");
|
||||||
case "DELETE_CLIENT":
|
case "DELETE_CLIENT":
|
||||||
return t("ui.dev.clients.recent_changes.guide.delete", "앱 삭제");
|
return t("ui.dev.clients.recent_changes.guide.delete", "앱 삭제");
|
||||||
default:
|
default:
|
||||||
@@ -68,7 +68,7 @@ function getRecentClientFieldLabel(key: string) {
|
|||||||
case "relation":
|
case "relation":
|
||||||
return t("ui.dev.clients.relationships.relation", "관계");
|
return t("ui.dev.clients.relationships.relation", "관계");
|
||||||
case "subject":
|
case "subject":
|
||||||
return t("ui.dev.clients.relationships.subject", "대상");
|
return t("ui.dev.clients.relationships.subject", "주체");
|
||||||
case "client_secret":
|
case "client_secret":
|
||||||
return t(
|
return t(
|
||||||
"ui.dev.clients.details.credentials.client_secret",
|
"ui.dev.clients.details.credentials.client_secret",
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ describe("apiClient", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
vi.stubEnv("MODE", "test");
|
vi.stubEnv("MODE", "test");
|
||||||
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })._IS_TEST_MODE =
|
(
|
||||||
true;
|
window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }
|
||||||
|
)._IS_TEST_MODE = true;
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
getUserMock.mockResolvedValue(null);
|
getUserMock.mockResolvedValue(null);
|
||||||
findPersistedOidcUserMock.mockReturnValue(undefined);
|
findPersistedOidcUserMock.mockReturnValue(undefined);
|
||||||
@@ -47,7 +48,8 @@ describe("apiClient", () => {
|
|||||||
window.localStorage.setItem("dev_tenant_id", "tenant-1");
|
window.localStorage.setItem("dev_tenant_id", "tenant-1");
|
||||||
|
|
||||||
const { default: apiClient } = await import("./apiClient");
|
const { default: apiClient } = await import("./apiClient");
|
||||||
const requestHandler = apiClient.interceptors.request.handlers[0]?.fulfilled;
|
const requestHandler =
|
||||||
|
apiClient.interceptors.request.handlers[0]?.fulfilled;
|
||||||
|
|
||||||
const result = await requestHandler?.({ headers: {} });
|
const result = await requestHandler?.({ headers: {} });
|
||||||
|
|
||||||
@@ -58,7 +60,8 @@ describe("apiClient", () => {
|
|||||||
it("rejects non-auth response errors without redirecting", async () => {
|
it("rejects non-auth response errors without redirecting", async () => {
|
||||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
const { default: apiClient } = await import("./apiClient");
|
const { default: apiClient } = await import("./apiClient");
|
||||||
const responseHandler = apiClient.interceptors.response.handlers[0]?.rejected;
|
const responseHandler =
|
||||||
|
apiClient.interceptors.response.handlers[0]?.rejected;
|
||||||
const error = { response: { status: 500, data: { error: "boom" } } };
|
const error = { response: { status: 500, data: { error: "boom" } } };
|
||||||
|
|
||||||
await expect(responseHandler?.(error)).rejects.toBe(error);
|
await expect(responseHandler?.(error)).rejects.toBe(error);
|
||||||
@@ -69,7 +72,8 @@ describe("apiClient", () => {
|
|||||||
it("warns and rejects auth failures in test mode", async () => {
|
it("warns and rejects auth failures in test mode", async () => {
|
||||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||||
const { default: apiClient } = await import("./apiClient");
|
const { default: apiClient } = await import("./apiClient");
|
||||||
const responseHandler = apiClient.interceptors.response.handlers[0]?.rejected;
|
const responseHandler =
|
||||||
|
apiClient.interceptors.response.handlers[0]?.rejected;
|
||||||
const error = {
|
const error = {
|
||||||
response: {
|
response: {
|
||||||
status: 403,
|
status: 403,
|
||||||
|
|||||||
@@ -1601,6 +1601,7 @@ revoke_cache = "Revoke Cache"
|
|||||||
[ui.dev.clients.relationships]
|
[ui.dev.clients.relationships]
|
||||||
title = "Client Relationships"
|
title = "Client Relationships"
|
||||||
add_title = "Add Relationship"
|
add_title = "Add Relationship"
|
||||||
|
remove_title = "Remove Relationship"
|
||||||
relation = "Relation"
|
relation = "Relation"
|
||||||
user_id = "User ID"
|
user_id = "User ID"
|
||||||
user_id_placeholder = "kratos user id"
|
user_id_placeholder = "kratos user id"
|
||||||
|
|||||||
@@ -1600,6 +1600,7 @@ revoke_cache = "캐시 삭제"
|
|||||||
[ui.dev.clients.relationships]
|
[ui.dev.clients.relationships]
|
||||||
title = "클라이언트 관계"
|
title = "클라이언트 관계"
|
||||||
add_title = "관계 추가"
|
add_title = "관계 추가"
|
||||||
|
remove_title = "관계 삭제"
|
||||||
relation = "관계"
|
relation = "관계"
|
||||||
user_id = "사용자 ID"
|
user_id = "사용자 ID"
|
||||||
user_id_placeholder = "kratos 사용자 id"
|
user_id_placeholder = "kratos 사용자 id"
|
||||||
|
|||||||
@@ -1655,6 +1655,7 @@ revoke_cache = ""
|
|||||||
[ui.dev.clients.relationships]
|
[ui.dev.clients.relationships]
|
||||||
title = ""
|
title = ""
|
||||||
add_title = ""
|
add_title = ""
|
||||||
|
remove_title = ""
|
||||||
relation = ""
|
relation = ""
|
||||||
user_id = ""
|
user_id = ""
|
||||||
user_id_placeholder = ""
|
user_id_placeholder = ""
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ test("clients page loads correctly", async ({ page }) => {
|
|||||||
|
|
||||||
// 페이지 내 주요 텍스트 확인
|
// 페이지 내 주요 텍스트 확인
|
||||||
await expect(page.getByText("연동 앱 목록")).toBeVisible();
|
await expect(page.getByText("연동 앱 목록")).toBeVisible();
|
||||||
await expect(page.getByText("Total Applications", { exact: true })).toHaveCount(0);
|
await expect(
|
||||||
|
page.getByText("Total Applications", { exact: true }),
|
||||||
|
).toHaveCount(0);
|
||||||
|
|
||||||
// 테이블 헤더 확인
|
// 테이블 헤더 확인
|
||||||
await expect(
|
await expect(
|
||||||
@@ -108,9 +110,7 @@ test("clients page shows only five apps by default and expands with more button"
|
|||||||
const clients = Array.from({ length: 6 }, (_, index) =>
|
const clients = Array.from({ length: 6 }, (_, index) =>
|
||||||
makeClient(`client-${index + 1}`, {
|
makeClient(`client-${index + 1}`, {
|
||||||
name: `Preview App ${index + 1}`,
|
name: `Preview App ${index + 1}`,
|
||||||
createdAt: new Date(
|
createdAt: new Date(Date.UTC(2026, 2, 3, 9, 10 - index, 0)).toISOString(),
|
||||||
Date.UTC(2026, 2, 3, 9, 10 - index, 0),
|
|
||||||
).toISOString(),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -126,7 +126,11 @@ test("clients page shows only five apps by default and expands with more button"
|
|||||||
page.getByRole("heading", { name: "연동 앱 목록" }),
|
page.getByRole("heading", { name: "연동 앱 목록" }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.locator("table").first().locator("tbody tr").filter({
|
page
|
||||||
|
.locator("table")
|
||||||
|
.first()
|
||||||
|
.locator("tbody tr")
|
||||||
|
.filter({
|
||||||
hasText: /Preview App \d/,
|
hasText: /Preview App \d/,
|
||||||
}),
|
}),
|
||||||
).toHaveCount(5);
|
).toHaveCount(5);
|
||||||
@@ -142,13 +146,15 @@ test("clients page shows only five apps by default and expands with more button"
|
|||||||
await moreButton.click();
|
await moreButton.click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.locator("table").first().locator("tbody tr").filter({
|
page
|
||||||
|
.locator("table")
|
||||||
|
.first()
|
||||||
|
.locator("tbody tr")
|
||||||
|
.filter({
|
||||||
hasText: /Preview App \d/,
|
hasText: /Preview App \d/,
|
||||||
}),
|
}),
|
||||||
).toHaveCount(6);
|
).toHaveCount(6);
|
||||||
await expect(
|
await expect(page.getByText("Preview App 6", { exact: true })).toBeVisible();
|
||||||
page.getByText("Preview App 6", { exact: true }),
|
|
||||||
).toBeVisible();
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole("button", { name: "연동 앱 목록 더보기" }),
|
page.getByRole("button", { name: "연동 앱 목록 더보기" }),
|
||||||
).toHaveCount(0);
|
).toHaveCount(0);
|
||||||
@@ -205,15 +211,13 @@ test("overview page shows user-delete relation cleanup in recent changes", async
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(page.getByText("관계 삭제", { exact: true })).toBeVisible();
|
await expect(page.getByText("관계 삭제", { exact: true })).toBeVisible();
|
||||||
await expect(page.getByText(/관계:\s*config_editor/)).toBeVisible();
|
await expect(page.getByText(/관계:\s*config_editor/)).toBeVisible();
|
||||||
await expect(page.getByText(/대상:\s*User:deleted-user/)).toBeVisible();
|
await expect(page.getByText(/주체:\s*User:deleted-user/)).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("cleanup-actor", { exact: true }).first(),
|
page.getByText("cleanup-actor", { exact: true }).first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("clients page no longer shows recent changes card", async ({
|
test("clients page no longer shows recent changes card", async ({ page }) => {
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await seedAuth(page, "super_admin");
|
await seedAuth(page, "super_admin");
|
||||||
const clients = Array.from({ length: 6 }, (_, index) =>
|
const clients = Array.from({ length: 6 }, (_, index) =>
|
||||||
makeClient(`client-${index + 1}`, {
|
makeClient(`client-${index + 1}`, {
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
cli_config:
|
cli_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -268,6 +268,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -320,18 +328,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -653,26 +661,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
|
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.30.0"
|
version: "1.26.3"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.7"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
|
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.16"
|
version: "0.6.12"
|
||||||
toml:
|
toml:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
Reference in New Issue
Block a user