공지 연계 기능 추가 및 UI 개선:
- 진행 중, 과거 장애/점검 카드에 공지 제목 및 본문 추가. - 공지 본문 2열 레이아웃 및 세로 스크롤 스타일 적용. - CSS 반응형 수정(768px → 1024px).
This commit is contained in:
+7
@@ -2,11 +2,18 @@ package com.eactive.apim.portal.apps.community.notice.repository;
|
||||
|
||||
import com.eactive.apim.portal.portalNotice.entity.PortalNotice;
|
||||
import com.eactive.eai.rms.data.EMSDataSource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
@EMSDataSource
|
||||
public interface PortalNoticeRepository extends JpaRepository<PortalNotice, String>, JpaSpecificationExecutor<PortalNotice> {
|
||||
|
||||
/**
|
||||
* 게시 중인 공지만 골라 한 번에 읽는다. API Status 카드가 연결 공지 본문을 붙일 때 사용한다.
|
||||
* 미게시(USE_YN='N')·삭제된 공지는 결과에서 자연히 빠진다.
|
||||
*/
|
||||
List<PortalNotice> findByIdInAndUseYn(Collection<String> ids, String useYn);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ public class ActiveIncidentDTO {
|
||||
|
||||
private Long incidentId;
|
||||
private String noticeId;
|
||||
/** 연결된 공지 제목. 게시 중인 공지가 없으면 null */
|
||||
private String noticeSubject;
|
||||
/** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */
|
||||
private String noticeDetail;
|
||||
private String kind;
|
||||
private String state;
|
||||
private String stateLabel;
|
||||
|
||||
@@ -15,6 +15,10 @@ public class MaintenanceCardDTO {
|
||||
|
||||
private Long incidentId;
|
||||
private String noticeId;
|
||||
/** 연결된 공지 제목. 게시 중인 공지가 없으면 null */
|
||||
private String noticeSubject;
|
||||
/** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */
|
||||
private String noticeDetail;
|
||||
private String title;
|
||||
private String summary;
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
|
||||
@@ -16,6 +16,10 @@ public class PastIssueCardDTO {
|
||||
|
||||
private Long incidentId;
|
||||
private String noticeId;
|
||||
/** 연결된 공지 제목. 게시 중인 공지가 없으면 null */
|
||||
private String noticeSubject;
|
||||
/** 연결된 공지 본문(HTML). 게시 중인 공지가 없으면 null */
|
||||
private String noticeDetail;
|
||||
private String kind;
|
||||
private String state;
|
||||
private String stateLabel;
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.eactive.apim.portal.djb.apistatus.incident.entity.DjbApistatusInciden
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.entity.IncidentKind;
|
||||
import com.eactive.apim.portal.djb.apistatus.incident.repository.DjbApistatusIncidentApiRepository;
|
||||
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.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -37,6 +39,7 @@ public class ApiStatusAssembler {
|
||||
|
||||
private final DjbApistatusIncidentApiRepository incidentApiRepository;
|
||||
private final DjbApistatusIncidentTimelineRepository timelineRepository;
|
||||
private final PortalNoticeRepository portalNoticeRepository;
|
||||
|
||||
public Map<Long, List<AffectedApiDTO>> loadApis(Collection<Long> incidentIds) {
|
||||
if (incidentIds == null || incidentIds.isEmpty()) {
|
||||
@@ -69,6 +72,30 @@ public class ApiStatusAssembler {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈에 연결된 공지. 게시 중(USE_YN='Y')인 것만 담는다.
|
||||
*
|
||||
* <p>조회 자체의 공개 조건({@code ApiStatusIncidentQueryRepository.VISIBLE})과 같은 기준이라
|
||||
* 여기서 빠지는 건은 공지가 삭제된 고아 행뿐이다.</p>
|
||||
*/
|
||||
public Map<String, PortalNotice> loadNotices(Collection<String> noticeIds) {
|
||||
if (noticeIds == null || noticeIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<String> ids = noticeIds.stream()
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (ids.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<String, PortalNotice> result = new HashMap<>();
|
||||
for (PortalNotice notice : portalNoticeRepository.findByIdInAndUseYn(ids, "Y")) {
|
||||
result.put(notice.getId(), notice);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ActiveIncidentDTO> toActiveIncidents(List<DjbApistatusIncident> incidents, LocalDateTime now) {
|
||||
if (incidents.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
@@ -76,6 +103,9 @@ public class ApiStatusAssembler {
|
||||
List<Long> ids = incidentIds(incidents);
|
||||
Map<Long, List<AffectedApiDTO>> apis = loadApis(ids);
|
||||
Map<Long, List<TimelineEntryDTO>> timelines = loadTimelines(ids);
|
||||
Map<String, PortalNotice> notices = loadNotices(incidents.stream()
|
||||
.map(DjbApistatusIncident::getNoticeId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
List<ActiveIncidentDTO> result = new ArrayList<>();
|
||||
for (DjbApistatusIncident incident : incidents) {
|
||||
@@ -91,6 +121,12 @@ public class ApiStatusAssembler {
|
||||
dto.setElapsedMinutes(ApiStatusSupport.minutesBetween(incident.getStartedAt(), now));
|
||||
dto.setApis(apis.getOrDefault(incident.getIncidentId(), Collections.emptyList()));
|
||||
|
||||
PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId());
|
||||
if (notice != null) {
|
||||
dto.setNoticeSubject(notice.getNoticeSubject());
|
||||
dto.setNoticeDetail(notice.getNoticeDetail());
|
||||
}
|
||||
|
||||
List<TimelineEntryDTO> all = timelines.getOrDefault(incident.getIncidentId(), Collections.emptyList());
|
||||
dto.setRecentTimeline(all.size() > RECENT_TIMELINE_SIZE
|
||||
? new ArrayList<>(all.subList(0, RECENT_TIMELINE_SIZE)) : all);
|
||||
@@ -104,12 +140,22 @@ public class ApiStatusAssembler {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<Long, List<AffectedApiDTO>> apis = loadApis(incidentIds(incidents));
|
||||
Map<String, PortalNotice> notices = loadNotices(incidents.stream()
|
||||
.map(DjbApistatusIncident::getNoticeId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
List<MaintenanceCardDTO> result = new ArrayList<>();
|
||||
for (DjbApistatusIncident incident : incidents) {
|
||||
MaintenanceCardDTO dto = new MaintenanceCardDTO();
|
||||
dto.setIncidentId(incident.getIncidentId());
|
||||
dto.setNoticeId(incident.getNoticeId());
|
||||
|
||||
PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId());
|
||||
if (notice != null) {
|
||||
dto.setNoticeSubject(notice.getNoticeSubject());
|
||||
dto.setNoticeDetail(notice.getNoticeDetail());
|
||||
}
|
||||
|
||||
dto.setTitle(incident.getTitle());
|
||||
dto.setSummary(incident.getSummary());
|
||||
dto.setStartedAt(incident.getStartedAt());
|
||||
@@ -139,12 +185,22 @@ public class ApiStatusAssembler {
|
||||
.map(DjbApistatusIncident::getIncidentId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Long, List<TimelineEntryDTO>> timelines = loadTimelines(incidentKindIds);
|
||||
Map<String, PortalNotice> notices = loadNotices(incidents.stream()
|
||||
.map(DjbApistatusIncident::getNoticeId)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
List<PastIssueCardDTO> result = new ArrayList<>();
|
||||
for (DjbApistatusIncident incident : incidents) {
|
||||
PastIssueCardDTO dto = new PastIssueCardDTO();
|
||||
dto.setIncidentId(incident.getIncidentId());
|
||||
dto.setNoticeId(incident.getNoticeId());
|
||||
|
||||
PortalNotice notice = incident.getNoticeId() == null ? null : notices.get(incident.getNoticeId());
|
||||
if (notice != null) {
|
||||
dto.setNoticeSubject(notice.getNoticeSubject());
|
||||
dto.setNoticeDetail(notice.getNoticeDetail());
|
||||
}
|
||||
|
||||
dto.setKind(incident.getKind() == null ? null : incident.getKind().name());
|
||||
dto.setState(incident.getState() == null ? null : incident.getState().name());
|
||||
dto.setStateLabel(ApiStatusSupport.stateLabel(incident.getState()));
|
||||
|
||||
@@ -762,7 +762,7 @@ hr {
|
||||
.global-header .container .header-content {
|
||||
height: 100%;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.global-header {
|
||||
height: clamp(44px, 11.73vw, 60px);
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
@@ -795,18 +795,26 @@ hr {
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.header-left .logo {
|
||||
height: 32px;
|
||||
align-items: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
.header-left .logo img {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.header-left .logo .mobile-logo-link {
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.header-left .logo .mobile-logo-text {
|
||||
align-items: flex-end;
|
||||
line-height: 1;
|
||||
padding-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
@@ -908,7 +916,7 @@ hr {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.desktop-header {
|
||||
display: none;
|
||||
}
|
||||
@@ -920,7 +928,7 @@ hr {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
}
|
||||
@@ -1180,7 +1188,7 @@ hr {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.mobile-drawer .drawer-welcome .btn-drawer-login:hover {
|
||||
background: rgb(0, 65.7, 162);
|
||||
background: rgb(0%, 25.7647058824%, 63.5294117647%);
|
||||
}
|
||||
.mobile-drawer .drawer-welcome.authenticated {
|
||||
flex-direction: row;
|
||||
@@ -1500,6 +1508,7 @@ hr {
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
transition: var(--transition-smooth);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nav-link:hover {
|
||||
color: var(--primary-blue);
|
||||
@@ -1526,7 +1535,7 @@ hr {
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.global-header {
|
||||
height: clamp(44px, 11.73vw, 60px);
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
@@ -1648,7 +1657,7 @@ hr {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.header-user-info {
|
||||
display: none;
|
||||
}
|
||||
@@ -2490,7 +2499,7 @@ hr {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.btn-success:hover {
|
||||
background: rgb(83.2897959184, 199.3102040816, 106.493877551);
|
||||
background: rgb(32.662665066%, 78.1608643457%, 41.762304922%);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
.btn-danger {
|
||||
@@ -2498,7 +2507,7 @@ hr {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: rgb(255, 70.8, 70.8);
|
||||
background: rgb(100%, 27.7647058824%, 27.7647058824%);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
.btn-ghost {
|
||||
@@ -2764,7 +2773,7 @@ hr {
|
||||
.action-btn-delete:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
background: rgb(255, 88.9, 88.9);
|
||||
background: rgb(100%, 34.862745098%, 34.862745098%);
|
||||
}
|
||||
.action-btn-delete:active {
|
||||
transform: translateY(0);
|
||||
@@ -2882,7 +2891,7 @@ hr {
|
||||
background: #a4d6ea;
|
||||
}
|
||||
.btn-input-action.btn-change:hover {
|
||||
background: rgb(131.6625, 199.4303571429, 226.5375);
|
||||
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
}
|
||||
@@ -2957,7 +2966,7 @@ hr {
|
||||
border: none;
|
||||
}
|
||||
.btn-action-primary:hover {
|
||||
background: rgb(31.8731707317, 92.7219512195, 205.7268292683);
|
||||
background: rgb(12.4992826399%, 36.3615494978%, 80.6771879484%);
|
||||
transform: translateY(-2px);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -3007,7 +3016,7 @@ hr {
|
||||
}
|
||||
.status-badge.status-processing {
|
||||
background: rgba(255, 217, 61, 0.1);
|
||||
color: rgb(221.2, 177.8721649485, 0);
|
||||
color: rgb(86.7450980392%, 69.7537901759%, 0%);
|
||||
}
|
||||
.status-badge.status-failed {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
@@ -3047,7 +3056,7 @@ hr {
|
||||
}
|
||||
.status-badge-header.status-processing {
|
||||
background: rgba(255, 217, 61, 0.1);
|
||||
color: rgb(221.2, 177.8721649485, 0);
|
||||
color: rgb(86.7450980392%, 69.7537901759%, 0%);
|
||||
}
|
||||
|
||||
.badge-sm {
|
||||
@@ -4235,7 +4244,7 @@ select.form-control {
|
||||
.file-upload-wrapper .file-remove-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(75, 155, 255, 0.1);
|
||||
background: rgb(255, 88.9, 88.9);
|
||||
background: rgb(100%, 34.862745098%, 34.862745098%);
|
||||
}
|
||||
.file-upload-wrapper .file-remove-btn:active {
|
||||
transform: translateY(0);
|
||||
@@ -4556,7 +4565,7 @@ select.form-control {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.form-actions--with-withdrawal .withdrawal-link:hover {
|
||||
background: rgb(210.2090909091, 232.6045454545, 242.9409090909);
|
||||
background: rgb(82.4349376114%, 91.2174688057%, 95.2709447415%);
|
||||
}
|
||||
.form-actions--with-withdrawal .withdrawal-link img {
|
||||
width: 22px;
|
||||
@@ -4711,7 +4720,7 @@ select.form-control {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.notice-content-box a:hover {
|
||||
color: rgb(0, 65.7, 162);
|
||||
color: rgb(0%, 25.7647058824%, 63.5294117647%);
|
||||
}
|
||||
|
||||
.form-row--content .form-label-wrapper {
|
||||
@@ -5420,6 +5429,7 @@ select.form-control {
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
transition: var(--transition-smooth);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.login-btn:hover {
|
||||
background-color: var(--accent-light);
|
||||
@@ -5432,6 +5442,7 @@ select.form-control {
|
||||
background-color: var(--primary-color);
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 12px rgba(0, 73, 180, 0.15);
|
||||
transition: var(--transition-smooth);
|
||||
}
|
||||
@@ -5645,7 +5656,7 @@ select.form-control {
|
||||
font-size: 16px;
|
||||
}
|
||||
.drawer-logout-btn:hover {
|
||||
background: rgb(255, 70.8, 70.8);
|
||||
background: rgb(100%, 27.7647058824%, 27.7647058824%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(75, 155, 255, 0.15);
|
||||
}
|
||||
@@ -6358,7 +6369,7 @@ select.form-control {
|
||||
color: #64748b;
|
||||
}
|
||||
.list-table-btn--default:hover {
|
||||
background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429);
|
||||
background-color: rgb(91.512605042%, 91.512605042%, 90.6638655462%);
|
||||
}
|
||||
.list-table-btn--primary {
|
||||
background-color: #ecf0fa;
|
||||
@@ -6366,7 +6377,7 @@ select.form-control {
|
||||
color: #2a69de;
|
||||
}
|
||||
.list-table-btn--primary:hover {
|
||||
background-color: rgb(216.7625, 224.8125, 244.9375);
|
||||
background-color: rgb(85.0049019608%, 88.1617647059%, 96.0539215686%);
|
||||
}
|
||||
.list-table-btn--secondary {
|
||||
background-color: #f5f5f4;
|
||||
@@ -6374,7 +6385,7 @@ select.form-control {
|
||||
color: #64748b;
|
||||
}
|
||||
.list-table-btn--secondary:hover {
|
||||
background-color: rgb(233.3571428571, 233.3571428571, 231.1928571429);
|
||||
background-color: rgb(91.512605042%, 91.512605042%, 90.6638655462%);
|
||||
}
|
||||
.list-table-btn--danger {
|
||||
background-color: #fbe7e9;
|
||||
@@ -6382,7 +6393,7 @@ select.form-control {
|
||||
color: #bb1026;
|
||||
}
|
||||
.list-table-btn--danger:hover {
|
||||
background-color: rgb(247.5571428571, 210.3428571429, 214.0642857143);
|
||||
background-color: rgb(97.081232493%, 82.487394958%, 83.9467787115%);
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
@@ -7032,7 +7043,7 @@ select.form-control {
|
||||
.alert.alert-error {
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
border: 1px solid rgba(255, 107, 107, 0.3);
|
||||
color: rgb(255, 70.8, 70.8);
|
||||
color: rgb(100%, 27.7647058824%, 27.7647058824%);
|
||||
align-items: center;
|
||||
}
|
||||
.alert.alert-error svg {
|
||||
@@ -7046,7 +7057,7 @@ select.form-control {
|
||||
.alert.alert-success {
|
||||
background: rgba(107, 207, 127, 0.1);
|
||||
border: 1px solid rgba(107, 207, 127, 0.3);
|
||||
color: rgb(61.5183673469, 189.6816326531, 87.1510204082);
|
||||
color: rgb(24.12484994%, 74.3849539816%, 34.1768707483%);
|
||||
}
|
||||
.alert.alert-info {
|
||||
background: rgba(0, 73, 180, 0.1);
|
||||
@@ -11409,10 +11420,10 @@ body.index-page-body {
|
||||
line-height: 20px;
|
||||
}
|
||||
.login-button:hover {
|
||||
background: rgb(25.65, 70.3, 173.85);
|
||||
background: rgb(10.0588235294%, 27.568627451%, 68.1764705882%);
|
||||
}
|
||||
.login-button:active {
|
||||
background: rgb(24.3, 66.6, 164.7);
|
||||
background: rgb(9.5294117647%, 26.1176470588%, 64.5882352941%);
|
||||
}
|
||||
.login-button:disabled {
|
||||
opacity: 0.6;
|
||||
@@ -11455,10 +11466,10 @@ body.index-page-body {
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
.login-links-container .link-btn:hover {
|
||||
background: rgb(220.61, 227.85, 245.95);
|
||||
background: rgb(86.5137254902%, 89.3529411765%, 96.4509803922%);
|
||||
}
|
||||
.login-links-container .link-btn:active {
|
||||
background: rgb(205.22, 215.7, 241.9);
|
||||
background: rgb(80.4784313725%, 84.5882352941%, 94.862745098%);
|
||||
}
|
||||
|
||||
.login-alert {
|
||||
@@ -11967,12 +11978,12 @@ body.index-page-body {
|
||||
}
|
||||
.auth-request-button:hover,
|
||||
.auth-verify-button:hover {
|
||||
background: rgb(37.3117757009, 153.9304672897, 235.0082242991);
|
||||
background: rgb(14.6320689023%, 60.3648891332%, 92.1600879604%);
|
||||
transform: none !important;
|
||||
}
|
||||
.auth-request-button:active,
|
||||
.auth-verify-button:active {
|
||||
background: rgb(21.411588785, 146.3125233645, 233.148411215);
|
||||
background: rgb(8.3967014843%, 57.3774601429%, 91.4307494961%);
|
||||
}
|
||||
.auth-request-button:disabled,
|
||||
.auth-verify-button:disabled {
|
||||
@@ -12021,10 +12032,10 @@ body.index-page-body {
|
||||
background: #f0f2f5;
|
||||
}
|
||||
.account-recovery-card .form-actions .cancel-button:hover {
|
||||
background: rgb(225.45, 229.39, 235.3);
|
||||
background: rgb(88.4117647059%, 89.9568627451%, 92.2745098039%);
|
||||
}
|
||||
.account-recovery-card .form-actions .cancel-button:active {
|
||||
background: rgb(210.9, 216.78, 225.6);
|
||||
background: rgb(82.7058823529%, 85.0117647059%, 88.4705882353%);
|
||||
}
|
||||
.account-recovery-card .form-actions .submit-button {
|
||||
color: #FFFFFF;
|
||||
@@ -12034,7 +12045,7 @@ body.index-page-body {
|
||||
background: rgb(6, 54, 125);
|
||||
}
|
||||
.account-recovery-card .form-actions .submit-button:active {
|
||||
background: rgb(0, 65.7, 162);
|
||||
background: rgb(0%, 25.7647058824%, 63.5294117647%);
|
||||
}
|
||||
.account-recovery-card .form-actions .submit-button:disabled {
|
||||
opacity: 0.6;
|
||||
@@ -12270,7 +12281,7 @@ body.index-page-body {
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
.result-info-box .info-text .info-link:hover {
|
||||
color: rgb(0, 65.7, 162);
|
||||
color: rgb(0%, 25.7647058824%, 63.5294117647%);
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.result-info-box .info-text {
|
||||
@@ -17470,7 +17481,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.btn-copy-action:hover {
|
||||
background: rgb(131.6625, 199.4303571429, 226.5375);
|
||||
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.btn-copy-action {
|
||||
@@ -17497,7 +17508,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.btn-view-secret:hover {
|
||||
background: rgb(31.8897196262, 151.4130841121, 234.5102803738);
|
||||
background: rgb(12.5057724024%, 59.377680044%, 91.9648158329%);
|
||||
}
|
||||
.btn-view-secret svg {
|
||||
width: 20px;
|
||||
@@ -17682,7 +17693,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.btn-copy-action:hover {
|
||||
background: rgb(131.6625, 199.4303571429, 226.5375);
|
||||
background: rgb(51.6323529412%, 78.2079831933%, 88.8382352941%);
|
||||
}
|
||||
.btn-view-secret {
|
||||
width: 100% !important;
|
||||
@@ -17698,7 +17709,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
height: 16px;
|
||||
}
|
||||
.btn-view-secret:hover {
|
||||
background: rgb(31.8897196262, 151.4130841121, 234.5102803738);
|
||||
background: rgb(12.5057724024%, 59.377680044%, 91.9648158329%);
|
||||
}
|
||||
#revealedSecretBox {
|
||||
width: 100%;
|
||||
@@ -17971,7 +17982,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.detail-wrap .dt-btn-copy:hover {
|
||||
background: rgb(189.6, 230.0857142857, 255);
|
||||
background: rgb(74.3529411765%, 90.2296918768%, 100%);
|
||||
}
|
||||
.detail-wrap .dt-btn-copy svg {
|
||||
color: #2a69de;
|
||||
@@ -18148,7 +18159,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.detail-wrap .dt-btn-gray:hover {
|
||||
background: rgb(170.6589473684, 183.4378947368, 193.6610526316);
|
||||
background: rgb(66.9250773994%, 71.9364293086%, 75.9455108359%);
|
||||
}
|
||||
.detail-wrap .dt-btn-red {
|
||||
width: 156px;
|
||||
@@ -18166,7 +18177,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
.detail-wrap .dt-btn-red:hover {
|
||||
background: rgb(255, 70.0915337423, 64.24);
|
||||
background: rgb(100%, 27.4868759774%, 25.1921568627%);
|
||||
}
|
||||
.detail-wrap .dt-btn-blue {
|
||||
width: 156px;
|
||||
@@ -19734,7 +19745,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
}
|
||||
}
|
||||
.btn-inquiry-list:hover {
|
||||
background: rgb(215.8869565217, 218.8956521739, 224.9130434783);
|
||||
background: rgb(84.6615515772%, 85.8414322251%, 88.2011935209%);
|
||||
}
|
||||
.btn-inquiry-list:active {
|
||||
transform: scale(0.98);
|
||||
@@ -19767,7 +19778,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
}
|
||||
}
|
||||
.btn-inquiry-edit:hover {
|
||||
background: rgb(0, 69.35, 171);
|
||||
background: rgb(0%, 27.1960784314%, 67.0588235294%);
|
||||
}
|
||||
.btn-inquiry-edit:active {
|
||||
transform: scale(0.98);
|
||||
@@ -19800,7 +19811,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
}
|
||||
}
|
||||
.btn-inquiry-delete:hover {
|
||||
background: rgb(217.9841772152, 41.3658227848, 58.2873417722);
|
||||
background: rgb(85.4839910648%, 16.2218912882%, 22.8577810871%);
|
||||
}
|
||||
.btn-inquiry-delete:active {
|
||||
transform: scale(0.98);
|
||||
@@ -19872,7 +19883,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.file-upload-inline .btn-remove-file-inline:hover {
|
||||
background: rgb(209.4151898734, 36.2848101266, 52.8721518987);
|
||||
background: rgb(82.1236038719%, 14.2293373045%, 20.7341772152%);
|
||||
}
|
||||
.file-upload-inline .btn-remove-file-inline svg {
|
||||
width: 12px;
|
||||
@@ -19904,7 +19915,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
}
|
||||
}
|
||||
.file-upload-inline .btn-file-attach:hover {
|
||||
background: rgb(37.3117757009, 153.9304672897, 235.0082242991);
|
||||
background: rgb(14.6320689023%, 60.3648891332%, 92.1600879604%);
|
||||
}
|
||||
.file-upload-inline .btn-file-attach svg {
|
||||
width: 22px;
|
||||
@@ -19964,7 +19975,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
border: none;
|
||||
}
|
||||
.inquiry-form-container .form-actions .btn-secondary:hover {
|
||||
background: rgb(215.8869565217, 218.8956521739, 224.9130434783);
|
||||
background: rgb(84.6615515772%, 85.8414322251%, 88.2011935209%);
|
||||
}
|
||||
.inquiry-form-container .form-actions .btn-primary {
|
||||
background: #0049b4;
|
||||
@@ -19972,7 +19983,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
border: none;
|
||||
}
|
||||
.inquiry-form-container .form-actions .btn-primary:hover {
|
||||
background: rgb(0, 69.35, 171);
|
||||
background: rgb(0%, 27.1960784314%, 67.0588235294%);
|
||||
}
|
||||
.inquiry-form-container .file-upload-inline .file-input-display {
|
||||
min-height: 50px;
|
||||
@@ -20761,7 +20772,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
cursor: pointer;
|
||||
}
|
||||
.djb-board-write-container .form-actions .btn-submit:hover {
|
||||
background-color: rgb(33.643902439, 97.8731707317, 217.156097561);
|
||||
background-color: rgb(13.193687231%, 38.3816355811%, 85.1592539455%);
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.djb-board-write-container .form-actions .btn-submit {
|
||||
@@ -21297,7 +21308,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.org-file-remove:hover {
|
||||
background: rgb(255, 70.8, 70.8);
|
||||
background: rgb(100%, 27.7647058824%, 27.7647058824%);
|
||||
}
|
||||
|
||||
.org-file-notice {
|
||||
@@ -22309,7 +22320,7 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
}
|
||||
.status-indicator.status-active {
|
||||
background-color: rgba(107, 207, 127, 0.1);
|
||||
color: rgb(83.2897959184, 199.3102040816, 106.493877551);
|
||||
color: rgb(32.662665066%, 78.1608643457%, 41.762304922%);
|
||||
}
|
||||
.status-indicator.status-active .status-dot {
|
||||
background-color: #6BCF7F;
|
||||
@@ -26628,6 +26639,74 @@ input[type=checkbox]:checked + .custom-checkbox {
|
||||
color: var(--as-muted);
|
||||
cursor: default;
|
||||
}
|
||||
.api-status .as-card-split {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (min-width: 961px) {
|
||||
.api-status .as-card-split {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
.api-status .as-card-split > .as-alert-notice {
|
||||
margin-top: 0;
|
||||
}
|
||||
.api-status .as-alert-card .as-card-split > .as-alert-timeline {
|
||||
margin-top: 0;
|
||||
}
|
||||
.api-status .as-maint-card .as-card-split > .as-maint-body {
|
||||
margin-top: 0;
|
||||
}
|
||||
.api-status .as-alert-notice {
|
||||
margin-top: 16px;
|
||||
border: 1px solid var(--as-border);
|
||||
border-radius: var(--as-radius);
|
||||
background: var(--as-gray-bg);
|
||||
padding: 14px 16px;
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-head strong {
|
||||
font-size: 14px;
|
||||
color: var(--as-text);
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--as-pill);
|
||||
background: var(--as-info-bg);
|
||||
color: var(--as-info);
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-body {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: var(--as-text-2);
|
||||
word-break: break-word;
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-body img, .api-status .as-alert-notice .as-alert-notice-body table {
|
||||
max-width: 100%;
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.api-status .as-alert-notice .as-alert-notice-link {
|
||||
display: inline-block;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--as-info);
|
||||
text-decoration: none;
|
||||
}
|
||||
.api-status .as-alert-card {
|
||||
border-left: 4px solid var(--as-err);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@
|
||||
const windowDays = parseInt(page.getAttribute('data-window-days'), 10) || 90;
|
||||
const base = page.getAttribute('data-base') || '/apistatus';
|
||||
const apiDetailBase = page.getAttribute('data-api-detail-base') || '/apis/detail';
|
||||
const noticeDetailBase = page.getAttribute('data-notice-detail-base') || '/portalnotice/detail';
|
||||
|
||||
/** 오픈 API 목록에 노출되는 API (링크 가능 대상) */
|
||||
const linkableApiIds = new Set();
|
||||
@@ -191,6 +192,36 @@
|
||||
return '<div class="as-api-pills"><span>영향 API:</span>' + pills + '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈에 연결된 공지 본문. 서버가 게시 중(USE_YN='Y')인 공지만 내려주므로 여기서는 존재 여부만 본다.
|
||||
* 본문은 관리자가 에디터로 작성한 HTML 이라 공지 상세(th:utext)와 같이 그대로 렌더한다.
|
||||
*/
|
||||
function noticeHtml(issue) {
|
||||
if (!issue.noticeDetail) return '';
|
||||
const link = issue.noticeId
|
||||
? '<a class="as-alert-notice-link" href="' + noticeDetailBase + '?id='
|
||||
+ encodeURIComponent(issue.noticeId) + '">공지 전체 보기 →</a>'
|
||||
: '';
|
||||
return '<div class="as-alert-notice">'
|
||||
+ '<div class="as-alert-notice-head">'
|
||||
+ '<span class="as-alert-notice-label">공지사항</span>'
|
||||
+ (issue.noticeSubject ? '<strong>' + escapeHtml(issue.noticeSubject) + '</strong>' : '')
|
||||
+ '</div>'
|
||||
+ '<div class="as-alert-notice-body editor-content">' + issue.noticeDetail + '</div>'
|
||||
+ link
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 타임라인 | 공지 2열 배치. 데스크탑에서만 두 칸으로 갈라지고 좁은 화면에서는 세로로 쌓인다.
|
||||
* 한쪽만 있으면 감싸지 않는다 (혼자 반쪽 폭을 차지하지 않게).
|
||||
*/
|
||||
function splitHtml(left, right) {
|
||||
if (!left) return right;
|
||||
if (!right) return left;
|
||||
return '<div class="as-card-split">' + left + right + '</div>';
|
||||
}
|
||||
|
||||
function issueCardHtml(issue) {
|
||||
const kindClass = KIND_CLASS[issue.kind] || 'is-incident';
|
||||
|
||||
@@ -222,7 +253,7 @@
|
||||
+ (issue.stateLabel ? '<span>' + escapeHtml(issue.stateLabel) + '</span>' : '')
|
||||
+ '</div>'
|
||||
+ '<h3 class="as-issue-title">' + escapeHtml(issue.title) + '</h3>'
|
||||
+ inner
|
||||
+ splitHtml(inner, noticeHtml(issue))
|
||||
+ apiPillsHtml(issue.impactedApis)
|
||||
+ '</article>';
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
const authenticated = page.classList.contains('is-authenticated');
|
||||
const base = page.getAttribute('data-base') || '/apistatus';
|
||||
const apiDetailBase = page.getAttribute('data-api-detail-base') || '/apis/detail';
|
||||
const noticeDetailBase = page.getAttribute('data-notice-detail-base') || '/portalnotice/detail';
|
||||
|
||||
/** 오픈 API 목록에 노출되는 API (링크 가능 대상) */
|
||||
const linkableApiIds = new Set();
|
||||
@@ -104,6 +105,36 @@
|
||||
return '<div class="as-api-pills"><span>' + escapeHtml(label) + '</span>' + pills + '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 이슈에 연결된 공지 본문. 서버가 게시 중(USE_YN='Y')인 공지만 내려주므로 여기서는 존재 여부만 본다.
|
||||
* 본문은 관리자가 에디터로 작성한 HTML 이라 공지 상세(th:utext)와 같이 그대로 렌더한다.
|
||||
*/
|
||||
function noticeHtml(incident) {
|
||||
if (!incident.noticeDetail) return '';
|
||||
const link = incident.noticeId
|
||||
? '<a class="as-alert-notice-link" href="' + noticeDetailBase + '?id='
|
||||
+ encodeURIComponent(incident.noticeId) + '">공지 전체 보기 →</a>'
|
||||
: '';
|
||||
return '<div class="as-alert-notice">'
|
||||
+ '<div class="as-alert-notice-head">'
|
||||
+ '<span class="as-alert-notice-label">공지사항</span>'
|
||||
+ (incident.noticeSubject ? '<strong>' + escapeHtml(incident.noticeSubject) + '</strong>' : '')
|
||||
+ '</div>'
|
||||
+ '<div class="as-alert-notice-body editor-content">' + incident.noticeDetail + '</div>'
|
||||
+ link
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 타임라인 | 공지 2열 배치. 데스크탑에서만 두 칸으로 갈라지고 좁은 화면에서는 세로로 쌓인다.
|
||||
* 한쪽만 있으면 감싸지 않는다 (혼자 반쪽 폭을 차지하지 않게).
|
||||
*/
|
||||
function splitHtml(left, right) {
|
||||
if (!left) return right;
|
||||
if (!right) return left;
|
||||
return '<div class="as-card-split">' + left + right + '</div>';
|
||||
}
|
||||
|
||||
// ---------------- ❶ 진행 중 장애 ----------------
|
||||
function renderActiveIncidents(incidents) {
|
||||
const container = document.getElementById('activeIncidents');
|
||||
@@ -140,7 +171,8 @@
|
||||
+ '<h2 class="as-alert-title">' + escapeHtml(incident.title) + '</h2>'
|
||||
+ (incident.summary ? '<p class="as-meta">' + escapeHtml(incident.summary) + '</p>' : '')
|
||||
+ apiPillsHtml(incident.apis, '영향 API:')
|
||||
+ (timeline ? '<div class="as-alert-timeline">' + timeline + '</div>' : '')
|
||||
+ splitHtml(timeline ? '<div class="as-alert-timeline">' + timeline + '</div>' : '',
|
||||
noticeHtml(incident))
|
||||
+ '</section>';
|
||||
}).join('');
|
||||
|
||||
@@ -252,7 +284,8 @@
|
||||
+ ' <h3 class="as-maint-title">' + escapeHtml(card.title) + '</h3>'
|
||||
+ ' <span class="as-schedule">' + escapeHtml(phase + ' · ' + range) + '</span>'
|
||||
+ '</div>'
|
||||
+ (card.summary ? '<div class="as-maint-body">' + escapeHtml(card.summary) + '</div>' : '')
|
||||
+ splitHtml(card.summary ? '<div class="as-maint-body">' + escapeHtml(card.summary) + '</div>' : '',
|
||||
noticeHtml(card))
|
||||
+ apiPillsHtml(card.impactedApis, '영향 API:')
|
||||
+ '<div class="as-maint-meta">등록 ' + escapeHtml(formatDateTime(card.registeredAt))
|
||||
+ (card.lastModifiedAt ? ' (최종 수정 ' + escapeHtml(formatDateTime(card.lastModifiedAt)) + ')' : '')
|
||||
@@ -300,7 +333,7 @@
|
||||
+ (issue.stateLabel ? '<span>' + escapeHtml(issue.stateLabel) + '</span>' : '')
|
||||
+ ' </div>'
|
||||
+ ' <h3 class="as-issue-title">' + escapeHtml(issue.title) + '</h3>'
|
||||
+ inner
|
||||
+ splitHtml(inner, noticeHtml(issue))
|
||||
+ apiPillsHtml(issue.impactedApis, '영향 API:')
|
||||
+ '</article>'
|
||||
+ '</div>';
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
transition: var(--transition-smooth);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--accent-light);
|
||||
@@ -63,6 +64,7 @@
|
||||
background-color: var(--primary-color);
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 12px rgba(0, 73, 180, 0.15);
|
||||
transition: var(--transition-smooth);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
height: clamp(44px, 11.73vw, 60px);
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
@@ -128,20 +128,30 @@
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// 헤더가 좁아져도 로고 영역은 줄이지 않는다 (줄임은 가운데 메뉴가 감당)
|
||||
flex-shrink: 0;
|
||||
|
||||
.logo {
|
||||
height: 32px;
|
||||
// 로고 워드마크(DJ Bank) 밑선에 "API Portal" 글자 baseline 맞춤
|
||||
align-items: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
// width 가 고정(114px)이라 flex 축소가 걸리면 가로만 눌려 비율이 깨진다
|
||||
img { flex-shrink: 0; }
|
||||
|
||||
.mobile-logo-link {
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mobile-logo-text {
|
||||
align-items: flex-end;
|
||||
line-height: 1;
|
||||
padding-bottom: 2px;
|
||||
// "API / Portal" 로 접히지 않게
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +273,7 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -274,7 +284,7 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -993,6 +1003,8 @@
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
transition: var(--transition-smooth);
|
||||
// 폭이 좁아져도 "서비스 소 / 개" 처럼 메뉴명이 잘리지 않게 한다
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-blue);
|
||||
@@ -1107,7 +1119,7 @@
|
||||
// Mobile Responsive Design
|
||||
// 375px 기준 반응형 - clamp() 사용하여 화면 크기에 비례
|
||||
// ===========================
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.global-header {
|
||||
// Figma: 44px 높이, 반투명 배경
|
||||
// 375px 기준 44px → 44/375*100 ≈ 11.73vw, 범위: 44px ~ 60px
|
||||
@@ -1258,7 +1270,7 @@
|
||||
}
|
||||
|
||||
// Mobile responsive
|
||||
@media (max-width: 768px) {
|
||||
@media (max-width: 1024px) {
|
||||
.header-user-info {
|
||||
display: none; // Hide on mobile, use drawer instead
|
||||
}
|
||||
|
||||
@@ -255,6 +255,75 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 타임라인 | 공지 2열. 데스크탑(961px~)에서만 갈라지고 그 아래는 세로로 쌓인다.
|
||||
// minmax(0, 1fr) 은 공지 본문의 긴 URL·표가 칸을 밀어내지 못하게 하는 grid 관용구.
|
||||
.as-card-split {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
|
||||
@media (min-width: 961px) {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
// 감싼 두 블록의 자체 세로 margin 은 grid gap 으로 대체한다.
|
||||
// 카드 컨텍스트를 함께 써서 원 규칙(.as-alert-card .as-alert-timeline 등)보다 특이도를 높인다.
|
||||
.as-card-split > .as-alert-notice { margin-top: 0; }
|
||||
.as-alert-card .as-card-split > .as-alert-timeline { margin-top: 0; }
|
||||
.as-maint-card .as-card-split > .as-maint-body { margin-top: 0; }
|
||||
|
||||
// 이슈에 연결된 공지 본문 (진행 중 장애 카드 · 점검 카드 공용).
|
||||
// 본문 길이가 제각각이라 높이를 제한하고 안쪽만 스크롤시킨다.
|
||||
.as-alert-notice {
|
||||
margin-top: 16px;
|
||||
border: 1px solid var(--as-border);
|
||||
border-radius: var(--as-radius);
|
||||
background: var(--as-gray-bg);
|
||||
padding: 14px 16px;
|
||||
|
||||
.as-alert-notice-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
|
||||
strong { font-size: 14px; color: var(--as-text); }
|
||||
}
|
||||
|
||||
.as-alert-notice-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--as-pill);
|
||||
background: var(--as-info-bg);
|
||||
color: var(--as-info);
|
||||
}
|
||||
|
||||
.as-alert-notice-body {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: var(--as-text-2);
|
||||
word-break: break-word;
|
||||
|
||||
img, table { max-width: 100%; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
.as-alert-notice-link {
|
||||
display: inline-block;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--as-info);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- 진행 중 장애 카드 ----------------
|
||||
.as-alert-card {
|
||||
border-left: 4px solid var(--as-err);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<div class="api-status" id="apiStatusPage"
|
||||
th:attr="data-window-days=${windowDays},data-base=@{/apistatus},data-api-detail-base=@{/apis/detail}"
|
||||
th:attr="data-window-days=${windowDays},data-base=@{/apistatus},data-api-detail-base=@{/apis/detail},data-notice-detail-base=@{/portalnotice/detail}"
|
||||
th:classappend="${authenticated} ? 'is-authenticated' : ''">
|
||||
|
||||
<!-- ❶ 진행 중 장애 -->
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
th:attr="data-window-days=${windowDays},
|
||||
data-base=@{/apistatus},
|
||||
data-api-detail-base=@{/apis/detail},
|
||||
data-notice-detail-base=@{/portalnotice/detail},
|
||||
data-today=${today},
|
||||
data-min-date=${minDate},
|
||||
data-selected-date=${selectedDate} ?: '',
|
||||
|
||||
Reference in New Issue
Block a user