fix: 프로젝트 Select Users에 포함된 사용자만 프로젝트 목록 표시 및 검색필터 수정

main
jschoi 8 months ago
parent 9dba9d153a
commit 96b3718493

@ -10,19 +10,17 @@ import IconModifyBtn from "@/components/atoms/button/IconModifyBtn.vue";
const store = commonStore(); const store = commonStore();
type SearchType = "전체" | "제목" | "작성자"; type SearchType = "전체" | "제목";
const searchOptions = [ const searchOptions = [
{ label: "전체", value: "전체" as SearchType }, { label: "전체", value: "전체" as SearchType },
{ label: "제목", value: "제목" as SearchType }, { label: "제목", value: "제목" as SearchType },
{ label: "작성자", value: "작성자" as SearchType },
]; ];
const SEARCH_TYPE_MAP: Record<SearchType | "", "ALL" | "TITLE" | "AUTHOR"> = { const SEARCH_TYPE_MAP: Record<SearchType | "", "ALL" | "TITLE"> = {
"": "ALL", "": "ALL",
전체: "ALL", 전체: "ALL",
제목: "TITLE", 제목: "TITLE",
작성자: "AUTHOR",
}; };
const DEFAULT_PERMISSIONS: Permission[] = [ const DEFAULT_PERMISSIONS: Permission[] = [
@ -157,22 +155,11 @@ async function getData() {
// //
if (needLocalFilter) { if (needLocalFilter) {
const kw = keyword.toLowerCase(); const kw = keyword.toLowerCase();
if (mapped === "TITLE") {
list = list.filter((x: any) => list = list.filter((x: any) =>
String(x?.prjNm ?? "") String(x?.prjNm ?? "")
.toLowerCase() .toLowerCase()
.includes(kw), .includes(kw),
); );
} else if (mapped === "AUTHOR") {
list = list.filter((x: any) => {
let authorStr = "";
if (typeof x.modUserNm === "string" && x.modUserNm.length > 0)
authorStr = x.modUserNm;
else if (typeof x.regUserNm === "string") authorStr = x.regUserNm;
return authorStr.toLowerCase().includes(kw);
});
}
const uiSize = data.value.params.pageSize; const uiSize = data.value.params.pageSize;
const totalElements = list.length; const totalElements = list.length;
const totalPages = Math.max(1, Math.ceil(totalElements / uiSize)); const totalPages = Math.max(1, Math.ceil(totalElements / uiSize));

@ -59,7 +59,7 @@ const pendingArtifactPath = ref<string | null>(null);
const packageOptions = ref<PackageOption[]>([]); const packageOptions = ref<PackageOption[]>([]);
const packagesLoading = ref(false); const packagesLoading = ref(false);
const packagesError = ref(""); const packagesError = ref("");
const shouldOpenDeploymentAfterLogin = ref(false);
/* ========= MLflow State ========= */ /* ========= MLflow State ========= */
const runs = ref<any[]>([]); const runs = ref<any[]>([]);
const loadingRuns = ref(false); const loadingRuns = ref(false);
@ -587,6 +587,12 @@ function restoreAuthFromStorage() {
/* no-op */ /* no-op */
} }
} }
function openLoginManually() {
shouldOpenDeploymentAfterLogin.value = false;
pendingArtifactPath.value = null;
loginDialog.value = true;
}
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem(AUTH_KEY); localStorage.removeItem(AUTH_KEY);
isAuthenticated.value = false; isAuthenticated.value = false;
@ -602,6 +608,7 @@ const handleLogin = async () => {
loginError.value = "ID와 비밀번호를 입력하세요."; loginError.value = "ID와 비밀번호를 입력하세요.";
return; return;
} }
const res = await ExternalAuthControllerService.signIn(id, password); const res = await ExternalAuthControllerService.signIn(id, password);
const raw = res?.data ?? res; const raw = res?.data ?? res;
if (!raw) { if (!raw) {
@ -615,6 +622,7 @@ const handleLogin = async () => {
loginError.value = "로그인에 실패했습니다. 아이디/비밀번호를 확인하세요."; loginError.value = "로그인에 실패했습니다. 아이디/비밀번호를 확인하세요.";
return; return;
} }
const toSave: ExternalAuth = { const toSave: ExternalAuth = {
id: payload.id ?? id, id: payload.id ?? id,
name: payload.name ?? id, name: payload.name ?? id,
@ -626,8 +634,12 @@ const handleLogin = async () => {
loginDialog.value = false; loginDialog.value = false;
loginForm.value = { id: "", password: "" }; loginForm.value = { id: "", password: "" };
if (shouldOpenDeploymentAfterLogin.value) {
shouldOpenDeploymentAfterLogin.value = false;
await nextTick(); await nextTick();
openDeploymentModal(); // pendingArtifactPath openDeploymentModal();
}
} catch (e: any) { } catch (e: any) {
loginError.value = loginError.value =
e?.response?.data?.message || e?.message || "로그인에 실패했습니다."; e?.response?.data?.message || e?.message || "로그인에 실패했습니다.";
@ -644,6 +656,7 @@ const openDeploymentModal = async (fullPath?: string) => {
pendingArtifactPath.value = uri; pendingArtifactPath.value = uri;
} }
if (!isAuthenticated.value) { if (!isAuthenticated.value) {
shouldOpenDeploymentAfterLogin.value = true;
loginDialog.value = true; loginDialog.value = true;
return; return;
} }
@ -1260,7 +1273,7 @@ const artifactsLoading = ref(false);
size="small" size="small"
color="primary" color="primary"
variant="text" variant="text"
@click="loginDialog = true" @click="openLoginManually"
> >
<v-icon start size="16">mdi-login</v-icon> Login <v-icon start size="16">mdi-login</v-icon> Login
</v-btn> </v-btn>
@ -1330,12 +1343,10 @@ const artifactsLoading = ref(false);
<td /> <td />
</tr> </tr>
<!-- 하위(폴더/파일) -->
<tr <tr
v-for="(it, idx) in grp.files" v-for="(it, idx) in grp.files"
:key="grp.dir + '-' + idx" :key="grp.dir + '-' + idx"
> >
<!-- 아이콘 전용 제거, 경로 칸이 아이콘 칸까지 흡수 -->
<td colspan="2"> <td colspan="2">
<div <div
class="path-cell" class="path-cell"
@ -1348,7 +1359,7 @@ const artifactsLoading = ref(false);
size="18" size="18"
class="mr-2" class="mr-2"
/> />
<code class="truncate">{{ it.path }}</code> <span>{{ it.path }}</span>
</div> </div>
</td> </td>
@ -1488,6 +1499,6 @@ const artifactsLoading = ref(false);
background: rgba(255, 255, 255, 0.04); background: rgba(255, 255, 255, 0.04);
} }
.child-path { .child-path {
padding-left: 18px; /* 들여쓰기로 디렉터리 소속임을 표시 */ padding-left: 18px;
} }
</style> </style>

@ -12,17 +12,15 @@ const store = commonStore();
const roleOptions = ["ROLE_USER", "ROLE_MODERATOR", "ROLE_ADMIN"] as const; const roleOptions = ["ROLE_USER", "ROLE_MODERATOR", "ROLE_ADMIN"] as const;
type SearchType = "전체" | "제목" | "작성자"; type SearchType = "전체" | "제목";
const searchOptions = [ const searchOptions = [
{ label: "전체", value: "전체" as SearchType }, { label: "전체", value: "전체" as SearchType },
{ label: "제목", value: "제목" as SearchType }, { label: "이름", value: "제목" as SearchType },
{ label: "작성자", value: "작성자" as SearchType },
]; ];
const SEARCH_TYPE_MAP: Record<SearchType | "", "ALL" | "TITLE" | "AUTHOR"> = { const SEARCH_TYPE_MAP: Record<SearchType | "", "ALL" | "TITLE"> = {
"": "ALL", "": "ALL",
전체: "ALL", 전체: "ALL",
제목: "TITLE", 제목: "TITLE",
작성자: "AUTHOR",
}; };
const fmtDate = (v?: string) => (v ? v.replace("T", " ").slice(0, 19) : "-"); const fmtDate = (v?: string) => (v ? v.replace("T", " ").slice(0, 19) : "-");
@ -147,8 +145,6 @@ async function getData() {
: String(u?.roles ?? "").toLowerCase(); : String(u?.roles ?? "").toLowerCase();
if (mapped === "TITLE") return username.includes(keyword); if (mapped === "TITLE") return username.includes(keyword);
if (mapped === "AUTHOR")
return email.includes(keyword) || rolesStr.includes(keyword);
return ( return (
username.includes(keyword) || username.includes(keyword) ||
email.includes(keyword) || email.includes(keyword) ||

@ -82,23 +82,30 @@ const splitCSV = (v?: string) =>
.map((s) => s.trim()) .map((s) => s.trim())
.filter(Boolean); .filter(Boolean);
const pickSelected = (p: ProjectSearchResponseItem) => {
const modIds = splitCSV(p.modUserId);
const modNms = splitCSV(p.modUserNm);
const useMod = modIds.length > 0 || modNms.length > 0; // mod mod
return {
ids: useMod ? modIds : splitCSV(p.regUserId),
nms: useMod ? modNms : splitCSV(p.regUserNm),
};
};
// (/ OK) // (/ OK)
const canViewProjectRaw = (p: ProjectSearchResponseItem) => { const canViewProjectRaw = (p: ProjectSearchResponseItem) => {
if (isAdmin.value) return true;
const idStr = const idStr =
currentUser.value.id != null ? String(currentUser.value.id) : ""; currentUser.value.id != null ? String(currentUser.value.id).trim() : "";
const uname = currentUser.value.username ?? ""; const uname = (currentUser.value.username ?? "").trim();
const allowIds = new Set([ const norm = (s: string) => s.trim().toLowerCase();
...splitCSV(p.regUserId), const { ids, nms } = pickSelected(p);
...splitCSV(p.modUserId),
]); const allowIds = new Set(ids.map(String).map(norm));
const allowNms = new Set([ const allowNms = new Set(nms.map(norm));
...splitCSV(p.regUserNm),
...splitCSV(p.modUserNm), return (
]); (idStr && allowIds.has(norm(idStr))) || (uname && allowNms.has(norm(uname)))
);
return (idStr && allowIds.has(idStr)) || (uname && allowNms.has(uname));
}; };
/** ===== 페이지네이션 상태 ===== */ /** ===== 페이지네이션 상태 ===== */
const pager = ref({ pageNum: 1, pageSize: 8, total: 0, pageLength: 1 }); const pager = ref({ pageNum: 1, pageSize: 8, total: 0, pageLength: 1 });
@ -261,9 +268,7 @@ const loadProjects = async () => {
projectRegById.value[p.id] = { regId: p.regUserId, regNm: p.regUserNm }; projectRegById.value[p.id] = { regId: p.regUserId, regNm: p.regUserNm };
// : reg/mod // : reg/mod
const usersDisplay = Array.from( const usersDisplay = pickSelected(p).nms.join(",");
new Set([...splitCSV(p.regUserNm), ...splitCSV(p.modUserNm)]),
).join(",");
return { return {
id: p.id, id: p.id,

Loading…
Cancel
Save