개발자포탈 미게시 GW 인터페이스 표시 추가:
- hiddenApiCount 필드로 미게시 인터페이스 집계 노출 - API Pills 및 관련 CSS/JS 수정 - 영향 인터페이스 노출 로직 개선 및 VisibleApis 클래스 적용
This commit is contained in:
@@ -54,6 +54,8 @@ public class PortalNoticeDTO {
|
||||
private String state;
|
||||
private String previousState;
|
||||
private List<IncidentAffectedApiDTO> affectedApis = Collections.emptyList();
|
||||
/** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */
|
||||
private int hiddenApiCount;
|
||||
/** 장애 처리 타임라인. 최신순(내림차순), 공개(visibleYn='Y') 항목만 담는다. */
|
||||
private List<TimelineEntryDTO> timeline = Collections.emptyList();
|
||||
|
||||
|
||||
+20
-4
@@ -5,7 +5,9 @@ import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeDTO;
|
||||
import com.eactive.apim.portal.apps.community.notice.dto.PortalNoticeSearch;
|
||||
import com.eactive.apim.portal.apps.community.notice.mapper.PortalNoticeMapper;
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncident;
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusIncidentApi;
|
||||
import com.eactive.apim.portal.djb.apistatus.service.ApiStatusAssembler;
|
||||
import com.eactive.apim.portal.djb.apistatus.service.ApiStatusCatalogService;
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentRepository;
|
||||
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
|
||||
@@ -17,8 +19,10 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -31,6 +35,7 @@ public class PortalNoticeFacadeImpl implements PortalNoticeFacade {
|
||||
private final DjbApistatusIncidentRepository incidentRepository;
|
||||
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
||||
private final ApiStatusAssembler apiStatusAssembler;
|
||||
private final ApiStatusCatalogService apiStatusCatalogService;
|
||||
|
||||
@Override
|
||||
public List<PortalNoticeDTO> getLatestNotices() {
|
||||
@@ -85,11 +90,22 @@ public class PortalNoticeFacadeImpl implements PortalNoticeFacade {
|
||||
dto.setState(incident.getState() == null ? null : incident.getState().name());
|
||||
dto.setPreviousState(incident.getPreviousState() == null ? null : incident.getPreviousState().name());
|
||||
|
||||
List<IncidentAffectedApiDTO> apis = incidentApiRepository
|
||||
.findByIncidentIdOrderByApiId(incident.getIncidentId()).stream()
|
||||
.map(api -> new IncidentAffectedApiDTO(api.getApiId(), api.getApiName()))
|
||||
.collect(Collectors.toList());
|
||||
// 영향 인터페이스 중 개발자포탈에 게시된 API 만 개별 노출한다.
|
||||
// 나머지 GW 인터페이스는 이름·ID 를 감추고 건수로만 알린다.
|
||||
Map<String, String> visibleNames = apiStatusCatalogService.getVisibleApiNames();
|
||||
List<IncidentAffectedApiDTO> apis = new ArrayList<>();
|
||||
int hiddenCount = 0;
|
||||
for (DjbApistatusIncidentApi api :
|
||||
incidentApiRepository.findByIncidentIdOrderByApiId(incident.getIncidentId())) {
|
||||
String publishedName = visibleNames.get(api.getApiId());
|
||||
if (publishedName == null) {
|
||||
hiddenCount++;
|
||||
continue;
|
||||
}
|
||||
apis.add(new IncidentAffectedApiDTO(api.getApiId(), publishedName));
|
||||
}
|
||||
dto.setAffectedApis(apis);
|
||||
dto.setHiddenApiCount(hiddenCount);
|
||||
|
||||
// 장애·지연만 타임라인을 붙인다 (점검은 타임라인을 쌓지 않음 — ADR-F15)
|
||||
boolean degrading = incident.getKind() != null && incident.getKind().isDegrading();
|
||||
|
||||
@@ -28,5 +28,7 @@ public class ActiveIncidentDTO {
|
||||
private LocalDateTime startedAt;
|
||||
private long elapsedMinutes;
|
||||
private List<AffectedApiDTO> apis = new ArrayList<>();
|
||||
/** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */
|
||||
private int hiddenApiCount;
|
||||
private List<TimelineEntryDTO> recentTimeline = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public class MaintenanceCardDTO {
|
||||
private LocalDateTime endAt;
|
||||
private Long durationMinutes;
|
||||
private List<AffectedApiDTO> impactedApis = new ArrayList<>();
|
||||
/** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */
|
||||
private int hiddenApiCount;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime registeredAt;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
@@ -33,5 +33,7 @@ public class PastIssueCardDTO {
|
||||
private LocalDate dateGroup;
|
||||
private Long durationMinutes;
|
||||
private List<AffectedApiDTO> impactedApis = new ArrayList<>();
|
||||
/** 개발자포탈에 게시되지 않아 개별 노출하지 않는 GW 인터페이스 건수 */
|
||||
private int hiddenApiCount;
|
||||
private List<TimelineEntryDTO> timeline = new ArrayList<>();
|
||||
}
|
||||
|
||||
+51
-3
@@ -13,6 +13,7 @@ import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusInc
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentTimelineRepository;
|
||||
import com.eactive.apim.portal.apps.community.notice.repository.PortalNoticeRepository;
|
||||
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -40,6 +41,41 @@ public class ApiStatusAssembler {
|
||||
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
||||
private final DjbApistatusIncidentTimelineRepository timelineRepository;
|
||||
private final PortalNoticeRepository portalNoticeRepository;
|
||||
private final ApiStatusCatalogService catalogService;
|
||||
|
||||
/**
|
||||
* 영향 인터페이스를 "개발자포탈에 게시된 API" 와 그 외 GW 인터페이스로 가른 결과.
|
||||
* 게시된 것만 개별 노출하고 나머지는 건수로만 알린다.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public static class VisibleApis {
|
||||
private final List<AffectedApiDTO> visible;
|
||||
private final int hiddenCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈 영향 인터페이스에서 현재 사용자에게 공개된 API 만 남긴다.
|
||||
*
|
||||
* <p>이름은 이슈에 캐시된 EAI 서비스명이 아니라 포털 노출명(PTL_API_SPEC_INFO)을 쓴다 -
|
||||
* 같은 API 가 화면마다 다른 이름으로 보이지 않게 한다.</p>
|
||||
*/
|
||||
private VisibleApis splitByVisibility(List<AffectedApiDTO> apis, Map<String, String> visibleNames) {
|
||||
if (apis == null || apis.isEmpty()) {
|
||||
return new VisibleApis(Collections.emptyList(), 0);
|
||||
}
|
||||
List<AffectedApiDTO> visible = new ArrayList<>();
|
||||
int hidden = 0;
|
||||
for (AffectedApiDTO api : apis) {
|
||||
String publishedName = visibleNames.get(api.getApiId());
|
||||
if (publishedName == null) {
|
||||
hidden++;
|
||||
continue;
|
||||
}
|
||||
visible.add(new AffectedApiDTO(api.getApiId(), publishedName, api.getRecoveredAt()));
|
||||
}
|
||||
return new VisibleApis(visible, hidden);
|
||||
}
|
||||
|
||||
public Map<Long, List<AffectedApiDTO>> loadApis(Collection<Long> incidentIds) {
|
||||
if (incidentIds == null || incidentIds.isEmpty()) {
|
||||
@@ -103,6 +139,7 @@ public class ApiStatusAssembler {
|
||||
List<Long> ids = incidentIds(incidents);
|
||||
Map<Long, List<AffectedApiDTO>> apis = loadApis(ids);
|
||||
Map<Long, List<TimelineEntryDTO>> timelines = loadTimelines(ids);
|
||||
Map<String, String> visibleNames = catalogService.getVisibleApiNames();
|
||||
Map<String, PortalNotice> notices = loadNotices(incidents.stream()
|
||||
.map(DjbApistatusIncident::getNoticeId)
|
||||
.collect(Collectors.toList()));
|
||||
@@ -119,7 +156,10 @@ public class ApiStatusAssembler {
|
||||
dto.setSummary(incident.getSummary());
|
||||
dto.setStartedAt(incident.getStartedAt());
|
||||
dto.setElapsedMinutes(ApiStatusSupport.minutesBetween(incident.getStartedAt(), now));
|
||||
dto.setApis(apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()));
|
||||
VisibleApis affected = splitByVisibility(
|
||||
apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames);
|
||||
dto.setApis(affected.getVisible());
|
||||
dto.setHiddenApiCount(affected.getHiddenCount());
|
||||
|
||||
PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId());
|
||||
if (notice != null) {
|
||||
@@ -140,6 +180,7 @@ public class ApiStatusAssembler {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<Long, List<AffectedApiDTO>> apis = loadApis(incidentIds(incidents));
|
||||
Map<String, String> visibleNames = catalogService.getVisibleApiNames();
|
||||
Map<String, PortalNotice> notices = loadNotices(incidents.stream()
|
||||
.map(DjbApistatusIncident::getNoticeId)
|
||||
.collect(Collectors.toList()));
|
||||
@@ -162,7 +203,10 @@ public class ApiStatusAssembler {
|
||||
dto.setEndAt(incident.getEndAt());
|
||||
dto.setDurationMinutes(incident.getEndAt() == null ? null
|
||||
: ApiStatusSupport.minutesBetween(incident.getStartedAt(), incident.getEndAt()));
|
||||
dto.setImpactedApis(apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()));
|
||||
VisibleApis affected = splitByVisibility(
|
||||
apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames);
|
||||
dto.setImpactedApis(affected.getVisible());
|
||||
dto.setHiddenApiCount(affected.getHiddenCount());
|
||||
dto.setRegisteredAt(incident.getCreatedDate());
|
||||
dto.setLastModifiedAt(incident.getLastModifiedDate());
|
||||
result.add(dto);
|
||||
@@ -179,6 +223,7 @@ public class ApiStatusAssembler {
|
||||
}
|
||||
List<Long> ids = incidentIds(incidents);
|
||||
Map<Long, List<AffectedApiDTO>> apis = loadApis(ids);
|
||||
Map<String, String> visibleNames = catalogService.getVisibleApiNames();
|
||||
|
||||
List<Long> incidentKindIds = incidents.stream()
|
||||
.filter(incident -> incident.getKind() != null && incident.getKind().isDegrading())
|
||||
@@ -211,7 +256,10 @@ public class ApiStatusAssembler {
|
||||
dto.setDateGroup(incident.getStartedAt() == null ? null : incident.getStartedAt().toLocalDate());
|
||||
dto.setDurationMinutes(incident.getEndAt() == null ? null
|
||||
: ApiStatusSupport.minutesBetween(incident.getStartedAt(), incident.getEndAt()));
|
||||
dto.setImpactedApis(apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()));
|
||||
VisibleApis affected = splitByVisibility(
|
||||
apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()), visibleNames);
|
||||
dto.setImpactedApis(affected.getVisible());
|
||||
dto.setHiddenApiCount(affected.getHiddenCount());
|
||||
dto.setTimeline(timelines.getOrDefault(incident.getIncidentId(), Collections.emptyList()));
|
||||
result.add(dto);
|
||||
}
|
||||
|
||||
+19
@@ -18,7 +18,9 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -80,6 +82,23 @@ public class ApiStatusCatalogService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 사용자에게 공개된 API 의 {API ID → 노출명} 맵.
|
||||
*
|
||||
* <p>GW 인터페이스는 전부 이슈 데이터(DJB_APISTATUS_INCIDENT_API)에 들어오지만
|
||||
* 개발자포탈에 게시되는 것은 그 일부(= API)뿐이다. 화면은 이 맵에 있는 것만
|
||||
* 개별 이름으로 노출하고 나머지는 건수로 묶는다.</p>
|
||||
*
|
||||
* <p>{@link #getSelectableApis()} 와 같은 경로라 역할·소속에 따른 공개 범위가 그대로 반영된다.</p>
|
||||
*/
|
||||
public Map<String, String> getVisibleApiNames() {
|
||||
Map<String, String> names = new LinkedHashMap<>();
|
||||
for (ApiOptionDTO api : getSelectableApis()) {
|
||||
names.put(api.getApiId(), api.getApiName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 상태 모니터링 Job(eapim-admin Quartz)의 마지막 실행 시각.
|
||||
*
|
||||
|
||||
@@ -19008,6 +19008,12 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.affected-api-badge.is-gw {
|
||||
background-color: #ffffff;
|
||||
border-style: dashed;
|
||||
border-color: #cbd5e1;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.btn-affected-toggle {
|
||||
font-size: 13px;
|
||||
@@ -26639,6 +26645,12 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
color: var(--as-muted);
|
||||
cursor: default;
|
||||
}
|
||||
.api-status .as-api-pills .as-api-pill.is-gw {
|
||||
background: #fff;
|
||||
border: 1px dashed var(--as-border-strong);
|
||||
color: var(--as-muted);
|
||||
cursor: default;
|
||||
}
|
||||
.api-status .as-card-split {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -175,21 +175,31 @@
|
||||
|
||||
// ---------------- 이슈 카드 ----------------
|
||||
/**
|
||||
* 영향 API 태그. 오픈 API 목록에 있는 API 만 상세 화면으로 링크하고,
|
||||
* 목록에 없는 API(비공개·미편성)는 링크 없이 흐리게 표시한다.
|
||||
* 영향 API 태그.
|
||||
*
|
||||
* 서버가 이미 현재 사용자에게 게시된 API 만 내려주므로 여기 오는 항목은 전부 링크 대상이다.
|
||||
* 게시되지 않은 GW 인터페이스는 개별 노출하지 않고 hiddenApiCount 로만 받아 건수로 묶는다.
|
||||
*/
|
||||
function apiPillsHtml(apis) {
|
||||
if (!apis || !apis.length) return '';
|
||||
const pills = apis.map(function (api) {
|
||||
function apiPillsHtml(apis, hiddenCount) {
|
||||
const hidden = hiddenCount || 0;
|
||||
if ((!apis || !apis.length) && hidden === 0) return '';
|
||||
|
||||
const pills = (apis || []).map(function (api) {
|
||||
const text = escapeHtml(api.apiName || api.apiId);
|
||||
if (!linkableApiIds.has(api.apiId)) {
|
||||
return '<span class="as-api-pill is-unlinked" title="공개된 API 목록에 없는 API 입니다">'
|
||||
+ text + '</span>';
|
||||
return '<span class="as-api-pill is-unlinked">' + text + '</span>';
|
||||
}
|
||||
return '<a class="as-api-pill" href="' + apiDetailBase + '?id=' + encodeURIComponent(api.apiId) + '">'
|
||||
+ text + '</a>';
|
||||
}).join('');
|
||||
return '<div class="as-api-pills"><span>영향 API:</span>' + pills + '</div>';
|
||||
|
||||
// 개발자포탈에 게시되지 않은 GW 인터페이스는 개별 노출 없이 건수로만 알린다
|
||||
const hiddenPill = hidden > 0
|
||||
? '<span class="as-api-pill is-gw" title="개발자포탈에 게시되지 않은 게이트웨이 인터페이스입니다">'
|
||||
+ 'GW 인터페이스 ' + hidden + '건</span>'
|
||||
: '';
|
||||
|
||||
return '<div class="as-api-pills"><span>영향 API:</span>' + pills + hiddenPill + '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +264,7 @@
|
||||
+ '</div>'
|
||||
+ '<h3 class="as-issue-title">' + escapeHtml(issue.title) + '</h3>'
|
||||
+ splitHtml(inner, noticeHtml(issue))
|
||||
+ apiPillsHtml(issue.impactedApis)
|
||||
+ apiPillsHtml(issue.impactedApis, issue.hiddenApiCount)
|
||||
+ '</article>';
|
||||
}
|
||||
|
||||
|
||||
@@ -88,21 +88,30 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* 영향 API 태그. 오픈 API 목록에 있는 API 만 상세 화면으로 링크하고,
|
||||
* 목록에 없는 API(비공개·미편성)는 링크 없이 흐리게 표시한다.
|
||||
* 영향 API 태그.
|
||||
*
|
||||
* 서버가 이미 현재 사용자에게 게시된 API 만 내려주므로 여기 오는 항목은 전부 링크 대상이다.
|
||||
* 게시되지 않은 GW 인터페이스는 개별 노출하지 않고 hiddenApiCount 로만 받아 건수로 묶는다.
|
||||
*/
|
||||
function apiPillsHtml(apis, label) {
|
||||
if (!apis || !apis.length) return '';
|
||||
const pills = apis.map(function (api) {
|
||||
function apiPillsHtml(apis, label, hiddenCount) {
|
||||
const hidden = hiddenCount || 0;
|
||||
if ((!apis || !apis.length) && hidden === 0) return '';
|
||||
|
||||
const pills = (apis || []).map(function (api) {
|
||||
const text = escapeHtml(api.apiName || api.apiId);
|
||||
if (!linkableApiIds.has(api.apiId)) {
|
||||
return '<span class="as-api-pill is-unlinked" title="공개된 API 목록에 없는 API 입니다">'
|
||||
+ text + '</span>';
|
||||
return '<span class="as-api-pill is-unlinked">' + text + '</span>';
|
||||
}
|
||||
return '<a class="as-api-pill" href="' + apiDetailBase + '?id=' + encodeURIComponent(api.apiId) + '">'
|
||||
+ text + '</a>';
|
||||
}).join('');
|
||||
return '<div class="as-api-pills"><span>' + escapeHtml(label) + '</span>' + pills + '</div>';
|
||||
|
||||
const hiddenPill = hidden > 0
|
||||
? '<span class="as-api-pill is-gw" title="개발자포탈에 게시되지 않은 게이트웨이 인터페이스입니다">'
|
||||
+ 'GW 인터페이스 ' + hidden + '건</span>'
|
||||
: '';
|
||||
|
||||
return '<div class="as-api-pills"><span>' + escapeHtml(label) + '</span>' + pills + hiddenPill + '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +179,7 @@
|
||||
+ '</div>'
|
||||
+ '<h2 class="as-alert-title">' + escapeHtml(incident.title) + '</h2>'
|
||||
+ (incident.summary ? '<p class="as-meta">' + escapeHtml(incident.summary) + '</p>' : '')
|
||||
+ apiPillsHtml(incident.apis, '영향 API:')
|
||||
+ apiPillsHtml(incident.apis, '영향 API:', incident.hiddenApiCount)
|
||||
+ splitHtml(timeline ? '<div class="as-alert-timeline">' + timeline + '</div>' : '',
|
||||
noticeHtml(incident))
|
||||
+ '</section>';
|
||||
@@ -286,7 +295,7 @@
|
||||
+ '</div>'
|
||||
+ splitHtml(card.summary ? '<div class="as-maint-body">' + escapeHtml(card.summary) + '</div>' : '',
|
||||
noticeHtml(card))
|
||||
+ apiPillsHtml(card.impactedApis, '영향 API:')
|
||||
+ apiPillsHtml(card.impactedApis, '영향 API:', card.hiddenApiCount)
|
||||
+ '<div class="as-maint-meta">등록 ' + escapeHtml(formatDateTime(card.registeredAt))
|
||||
+ (card.lastModifiedAt ? ' (최종 수정 ' + escapeHtml(formatDateTime(card.lastModifiedAt)) + ')' : '')
|
||||
+ '</div>'
|
||||
@@ -334,7 +343,7 @@
|
||||
+ ' </div>'
|
||||
+ ' <h3 class="as-issue-title">' + escapeHtml(issue.title) + '</h3>'
|
||||
+ splitHtml(inner, noticeHtml(issue))
|
||||
+ apiPillsHtml(issue.impactedApis, '영향 API:')
|
||||
+ apiPillsHtml(issue.impactedApis, '영향 API:', issue.hiddenApiCount)
|
||||
+ '</article>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
@@ -253,6 +253,14 @@
|
||||
color: var(--as-muted);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
// 개발자포탈에 게시되지 않은 GW 인터페이스 묶음 ("GW 인터페이스 3건")
|
||||
.as-api-pill.is-gw {
|
||||
background: #fff;
|
||||
border: 1px dashed var(--as-border-strong);
|
||||
color: var(--as-muted);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
// 타임라인 | 공지 2열. 데스크탑(961px~)에서만 갈라지고 그 아래는 세로로 쌓인다.
|
||||
|
||||
@@ -358,6 +358,14 @@
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// 개발자포탈에 게시되지 않은 GW 인터페이스 묶음 ("GW 인터페이스 3건")
|
||||
&.is-gw {
|
||||
background-color: #ffffff;
|
||||
border-style: dashed;
|
||||
border-color: #cbd5e1;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-affected-toggle {
|
||||
|
||||
@@ -78,29 +78,27 @@
|
||||
<th>상태</th>
|
||||
<td colspan="3" th:text="${portalNotice.state != null ? portalNotice.state : '-'}">-</td>
|
||||
</tr>
|
||||
<tr th:if="${portalNotice.affectedApis != null and !portalNotice.affectedApis.isEmpty()}">
|
||||
<tr
|
||||
th:if="${(portalNotice.affectedApis != null and !portalNotice.affectedApis.isEmpty()) or portalNotice.hiddenApiCount > 0}">
|
||||
<th>영향 API</th>
|
||||
<td colspan="3">
|
||||
<div class="affected-apis-wrapper">
|
||||
<!-- First 3 APIs (Always visible) -->
|
||||
<!-- 개발자포탈에 게시된 API 만 개별 노출한다 (인터페이스 ID 는 표시하지 않음) -->
|
||||
<div class="affected-apis-list">
|
||||
<span class="affected-api-badge" th:each="api, iterStat : ${portalNotice.affectedApis}"
|
||||
th:if="${iterStat.index < 3}">
|
||||
<strong th:text="${api.apiId}">API_ID</strong><span
|
||||
th:if="${api.apiName != null and !api.apiName.isEmpty()}"
|
||||
th:text="| - ${api.apiName}|"></span>
|
||||
</span>
|
||||
th:if="${iterStat.index < 3}" th:text="${api.apiName}">API 명</span>
|
||||
|
||||
<!-- Rest of APIs (Hidden by default) -->
|
||||
<th:block th:if="${portalNotice.affectedApis.size() > 3}">
|
||||
<span class="affected-api-badge extra-api"
|
||||
th:each="api, iterStat : ${portalNotice.affectedApis}" th:if="${iterStat.index >= 3}"
|
||||
style="display: none;">
|
||||
<strong th:text="${api.apiId}">API_ID</strong><span
|
||||
th:if="${api.apiName != null and !api.apiName.isEmpty()}"
|
||||
th:text="| - ${api.apiName}|"></span>
|
||||
</span>
|
||||
style="display: none;" th:text="${api.apiName}">API 명</span>
|
||||
</th:block>
|
||||
|
||||
<!-- 게시되지 않은 GW 인터페이스는 건수로만 묶어서 표기 -->
|
||||
<span class="affected-api-badge is-gw" th:if="${portalNotice.hiddenApiCount > 0}"
|
||||
th:text="|GW 인터페이스 ${portalNotice.hiddenApiCount}건|"
|
||||
title="개발자포탈에 게시되지 않은 게이트웨이 인터페이스입니다">GW 인터페이스 0건</span>
|
||||
</div>
|
||||
|
||||
<!-- Toggle Button -->
|
||||
|
||||
Reference in New Issue
Block a user