fix: 마이페이지 이용 통계 , 내정보 관리 , 비밀전호 변경 & 로그인 반응형 퍼블리싱
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+1
-28404
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,22 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Common Board Header Component Styling
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
.board-header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.board-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #000000;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.board-desc {
|
||||
font-size: 14px;
|
||||
color: #7f8a95;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@
|
||||
@use 'components/test-env-notice' as *;
|
||||
@use 'components/djb-inquiry-comments' as *;
|
||||
@use 'components/password-policy' as *;
|
||||
@use 'components/board-common' as *;
|
||||
|
||||
// 5. Page-specific styles
|
||||
@use 'pages/index' as *;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
.api-statistics-container {
|
||||
margin: 0 auto;
|
||||
padding: 48px 0px;
|
||||
}
|
||||
|
||||
// 집계 안내 문구
|
||||
@@ -34,10 +33,11 @@
|
||||
// Search Section
|
||||
.statistics-search-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 30px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
// 일별/월별 모드 토글
|
||||
@@ -46,6 +46,7 @@
|
||||
background: #eef1f5;
|
||||
border-radius: 22px;
|
||||
padding: 4px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.mode-tab {
|
||||
@@ -347,9 +348,10 @@
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
svg {
|
||||
stroke: #fff;
|
||||
stroke: #1e293b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,39 +377,89 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.statistics-table {
|
||||
.board-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-top: 1px solid #c4c7c8;
|
||||
background: #fff;
|
||||
|
||||
thead th {
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
tbody {
|
||||
td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
.board-table-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #f9f9f9;
|
||||
height: 50px;
|
||||
padding: 0 45px;
|
||||
border-bottom: 1px solid #dedede;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
font-size: 14px;
|
||||
font-family: 'Spoqa Han Sans Neo', sans-serif;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
tfoot .total-row {
|
||||
background: #e3f2fd;
|
||||
.board-table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 16px 20px;
|
||||
.board-table-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
padding: 0 45px;
|
||||
border-bottom: 1px solid #dedede;
|
||||
transition: background-color 0.2s;
|
||||
background: #ffffff;
|
||||
|
||||
&:hover {
|
||||
background-color: #f8fbfe;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
height: auto;
|
||||
padding: 15px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.row-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-family: 'Spoqa Han Sans Neo', sans-serif;
|
||||
font-weight: 400;
|
||||
color: #000;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: flex-start;
|
||||
width: 100% !important;
|
||||
|
||||
&::before {
|
||||
content: attr(data-label);
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
margin-right: 10px;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.total-row {
|
||||
background: #f8fbfe;
|
||||
font-weight: 600;
|
||||
|
||||
.row-cell {
|
||||
color: #0049B4;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,39 +492,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Progress Bar
|
||||
.progress-cell {
|
||||
min-width: 200px;
|
||||
.api-name-text {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
// Progress Bar
|
||||
.col-name {
|
||||
flex: 2;
|
||||
justify-content: flex-start !important;
|
||||
padding-left: 10px;
|
||||
min-width: 0; // Allows flex child truncation
|
||||
}
|
||||
|
||||
.board-table-header .col-name {
|
||||
justify-content: center !important;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.col-num {
|
||||
width: 100px;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.col-chart {
|
||||
width: 180px;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.stats-progress {
|
||||
width: 150px;
|
||||
height: 8px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
height: 12px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-success {
|
||||
background: #4A90D9;
|
||||
transition: width 0.3s ease;
|
||||
.stats-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
color: #64748b;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.progress-timeout {
|
||||
background: #F5C36B;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-failure {
|
||||
background: #F5A3B5;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
// Empty State
|
||||
.empty-row td {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #999;
|
||||
.progress-cell {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@@ -480,6 +557,7 @@
|
||||
.summary-content {
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-left,
|
||||
@@ -487,6 +565,22 @@
|
||||
.summary-right {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.summary-info {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.app-select-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.summary-detail-list {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-center {
|
||||
@@ -495,42 +589,101 @@
|
||||
|
||||
.summary-right {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rate-display {
|
||||
border-bottom: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.api-statistics-container {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
// .api-statistics-container {
|
||||
// padding: 20px 16px;
|
||||
// }
|
||||
|
||||
.statistics-summary-card {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.app-toggle-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.statistics-table {
|
||||
thead th,
|
||||
tbody td,
|
||||
tfoot td {
|
||||
padding: 12px 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-cell {
|
||||
min-width: 100px;
|
||||
.statistics-search-section {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-filter-row {
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-range-picker {
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
width: 45%;
|
||||
font-size: 13px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.board-table-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.board-table-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: auto;
|
||||
padding: 10px 14px;
|
||||
gap: 6px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
|
||||
.row-cell {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center;
|
||||
width: 100% !important;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
padding: 0 !important;
|
||||
|
||||
&::before {
|
||||
content: attr(data-label);
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
text-align: left;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
&.col-name {
|
||||
font-weight: 600;
|
||||
justify-content: flex-start !important;
|
||||
text-align: left;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0 !important;
|
||||
margin-bottom: 4px;
|
||||
display: flex !important;
|
||||
gap: 10px;
|
||||
|
||||
&::before {
|
||||
// content: "API명" !important;
|
||||
// display: inline-block !important;
|
||||
// font-weight: 500;
|
||||
// color: #64748b;
|
||||
// min-width: 80px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-progress {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
@@ -1240,6 +1240,36 @@
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.info-notice-box {
|
||||
background: #ecf0fa;
|
||||
border-radius: 7px;
|
||||
padding: 13px 14px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #4685ef;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: #fdf3f3;
|
||||
p {
|
||||
color: #f4253c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer Buttons layout
|
||||
.dt-actions {
|
||||
display: flex;
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #EDF9FE; // Light blue background from Figma
|
||||
padding: 40px 20px;
|
||||
padding: 100px 20px;
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
margin-top: 60px;
|
||||
margin-bottom: 60px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
@@ -37,60 +36,104 @@
|
||||
}
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.login-message {
|
||||
font-family: $font-family-primary;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
.login-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
|
||||
.header-main-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-lock-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
stroke: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-family: $font-family-primary;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #000000;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.login-message-sub {
|
||||
font-family: $font-family-primary;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
color: #80878d;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
&.input-with-icon {
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
|
||||
svg {
|
||||
stroke: #767e89;
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding-left: 56px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
padding: 0 32px;
|
||||
height: 58px;
|
||||
padding: 0 20px;
|
||||
font-family: $font-family-primary;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #5F666C;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #DDDDDD;
|
||||
border-radius: 12px;
|
||||
font-weight: 300;
|
||||
color: #000000;
|
||||
background: #fcfcfc;
|
||||
border: 1px solid #dfdfdf;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::placeholder {
|
||||
color: #5F666C;
|
||||
color: #767e89;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #0049B4;
|
||||
border-color: #1b4ab7;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
&.error {
|
||||
@@ -101,17 +144,16 @@
|
||||
.form-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 7px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 22px;
|
||||
margin-right: 10px;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1.5px solid #000000;
|
||||
border-radius: 4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid #dfdfdf;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background: #FFFFFF;
|
||||
@@ -119,8 +161,8 @@
|
||||
flex-shrink: 0;
|
||||
|
||||
&:checked {
|
||||
background: #FFFFFF;
|
||||
border-color: #2C2C2C;
|
||||
background: #1b4ab7;
|
||||
border-color: #1b4ab7;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@@ -128,22 +170,22 @@
|
||||
left: 50%;
|
||||
top: 45%;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid #2C2C2C;
|
||||
border-width: 0 2px 2px 0;
|
||||
width: 3px;
|
||||
height: 6px;
|
||||
border: solid #ffffff;
|
||||
border-width: 0 1.5px 1.5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-family: $font-family-primary;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
color: #767e89;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
line-height: 24px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -151,26 +193,26 @@
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
height: 58px;
|
||||
padding: 10px;
|
||||
font-family: $font-family-primary;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
background: #0049B4;
|
||||
background: #1b4ab7;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 40px;
|
||||
line-height: 1;
|
||||
margin-bottom: 30px;
|
||||
line-height: 20px;
|
||||
|
||||
&:hover {
|
||||
background: darken(#0049B4, 5%);
|
||||
background: darken(#1b4ab7, 5%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: darken(#0049B4, 10%);
|
||||
background: darken(#1b4ab7, 10%);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -179,31 +221,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.login-links {
|
||||
.login-links-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 26px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
.link-btn {
|
||||
flex: 1;
|
||||
height: 38px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ecf0fa;
|
||||
border: 1px solid #2a69de;
|
||||
font-family: $font-family-primary;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
color: #2a69de;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
line-height: 1;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
|
||||
&.first {
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&.middle {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&.last {
|
||||
border-top-right-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #0049B4;
|
||||
text-decoration: underline;
|
||||
}
|
||||
background: darken(#ecf0fa, 4%);
|
||||
}
|
||||
|
||||
.link-separator {
|
||||
color: #000000;
|
||||
font-size: 16px;
|
||||
&:active {
|
||||
background: darken(#ecf0fa, 8%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,9 +344,8 @@
|
||||
@include respond-to('sm') {
|
||||
.login-page {
|
||||
// Figma: 전체 배경 #ebfbff, 풀 높이
|
||||
background: #ebfbff;
|
||||
min-height: calc(100vh - 44px); // 모바일 헤더 높이 제외
|
||||
padding: 40px 20px;
|
||||
padding: 100px 20px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-radius: 0;
|
||||
|
||||
@@ -26,13 +26,14 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Corporate Manager Profile Form Styles
|
||||
// -----------------------------------------------------------------------------
|
||||
.corp-manager-wrapper, .password-change-wrapper {
|
||||
.corp-manager-wrapper,
|
||||
.password-change-wrapper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 40px 0;
|
||||
// padding: 40px 0;
|
||||
|
||||
.page-outer-title {
|
||||
font-size: 36px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
margin-bottom: 24px;
|
||||
@@ -87,6 +88,11 @@
|
||||
flex-direction: column;
|
||||
gap: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
flex-direction: column;
|
||||
gap: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +224,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.btn-withdrawal {
|
||||
@@ -291,6 +297,14 @@
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background: #fdf3f3;
|
||||
|
||||
p {
|
||||
color: #f4253c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.password-rules-list {
|
||||
|
||||
@@ -93,7 +93,9 @@
|
||||
th:if="${notice.noticeType == '4'}">테스트</span>
|
||||
<span class="notice-type-badge notice-type-badge--normal"
|
||||
th:if="${notice.noticeType == '1'}">공지</span>
|
||||
<span th:text="${#strings.replace(#strings.replace(#strings.replace(#strings.replace(notice.noticeSubject, '[장애]', ''), '[점검]', ''), '[테스트]', ''), '[공지]', '')}">공지사항 제목</span>
|
||||
<span
|
||||
th:text="${#strings.replace(#strings.replace(#strings.replace(#strings.replace(notice.noticeSubject, '[장애]', ''), '[점검]', ''), '[테스트]', ''), '[공지]', '')}">공지사항
|
||||
제목</span>
|
||||
<span class="file-icon" th:if="${notice.fileId != null and !notice.fileId.isEmpty()}">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
||||
@@ -39,11 +39,9 @@
|
||||
<section class="service-content">
|
||||
<!-- 상단: 최근 작성한 글 목록 -->
|
||||
<div class="recent-apps">
|
||||
<div class="recent-apps-header-group">
|
||||
<p class="recent-apps-title" style="font-size: 24px; font-weight: 700; color: #000; margin-bottom: 8px;">
|
||||
피드백 / 개선 요청</p>
|
||||
<p class="recent-apps-subtitle" style="font-size: 14px; color: #7f8a95; margin-bottom: 24px;">최근 최대 3개의 나의
|
||||
피드백/ 개선 요청을 보여줍니다.</p>
|
||||
<div class="board-header">
|
||||
<h2 class="board-title">피드백 / 개선 요청</h2>
|
||||
<p class="board-desc">최근 최대 3개의 나의 피드백/ 개선 요청을 보여줍니다.</p>
|
||||
</div>
|
||||
|
||||
<ul class="recent-apps-list" th:if="${recentApplications != null and !recentApplications.isEmpty()}">
|
||||
@@ -73,10 +71,10 @@
|
||||
<!-- 하단: 신규 작성 폼 카드 -->
|
||||
<div class="djb-board-write-container">
|
||||
<!-- Title Bar -->
|
||||
<div class="djb-board-write-header" style="margin-bottom: 30px;">
|
||||
<h2 class="title" style="font-size: 24px; font-weight: 700; color: #000; margin-bottom: 8px;">피드백 / 개선 요청
|
||||
<div class="board-header">
|
||||
<h2 class="board-title">피드백 / 개선 요청
|
||||
</h2>
|
||||
<p class="subtitle" style="font-size: 14px; color: #7f8a95; margin: 0;">DJ Bank은 온라인 비즈니스 혁신을 위한 피드백/개선요청을
|
||||
<p class="board-desc">DJ Bank은 온라인 비즈니스 혁신을 위한 피드백/개선요청을
|
||||
환영합니다.</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/djbank_base_layout}">
|
||||
|
||||
<body>
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<!-- Logo -->
|
||||
<div class="login-logo">
|
||||
<img th:src="@{/img/logo/logo_box.png}" alt="DJBank">
|
||||
<!-- Logo & Title -->
|
||||
<div class="login-header">
|
||||
<div class="header-main-row">
|
||||
<div class="login-lock-icon">
|
||||
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#000"
|
||||
stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="login-title">로그인</h2>
|
||||
</div>
|
||||
<p class="login-message-sub">DJ Bank에 오신걸 환영합니다</p>
|
||||
</div>
|
||||
|
||||
<!-- Header Message -->
|
||||
<p class="login-message">로그인 하시면 더 많은 서비스를 이용하실 수 있습니다.</p>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
<div th:if="${param.logout}" class="login-alert alert-info">
|
||||
@@ -33,30 +41,36 @@
|
||||
</div>
|
||||
|
||||
<!-- Login Form -->
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post" class="login-form">
|
||||
<form id="loginForm" role="form" name="loginForm" th:action="@{/actionLogin.do}" method="post"
|
||||
class="login-form">
|
||||
<input type="hidden" id="message" name="message" th:value="${loginMessage}">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<input type="hidden" id="loginId" th:value="${loginId}">
|
||||
|
||||
<!-- ID Field -->
|
||||
<div class="form-group">
|
||||
<input type="text"
|
||||
id="id"
|
||||
name="id"
|
||||
th:value="${loginId}"
|
||||
class="form-input"
|
||||
placeholder="아이디"
|
||||
required>
|
||||
<div class="form-group input-with-icon">
|
||||
<span class="input-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#767e89"
|
||||
stroke-width="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" id="id" name="id" th:value="${loginId}" class="form-input"
|
||||
placeholder="아이디를 입력해주세요" required>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="form-group">
|
||||
<input type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="form-input"
|
||||
placeholder="비밀번호"
|
||||
required>
|
||||
<div class="form-group input-with-icon">
|
||||
<span class="input-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#767e89"
|
||||
stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
</span>
|
||||
<input type="password" id="password" name="password" class="form-input"
|
||||
placeholder="비밀번호를 입력해주세요" required>
|
||||
</div>
|
||||
|
||||
<!-- Remember Me Checkbox -->
|
||||
@@ -72,12 +86,10 @@
|
||||
</button>
|
||||
|
||||
<!-- Additional Links -->
|
||||
<div class="login-links">
|
||||
<a th:href="@{/account_recovery(tab='findId')}">아이디 찾기</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a>
|
||||
<span class="link-separator">|</span>
|
||||
<a th:href="@{/signup}">회원가입</a>
|
||||
<div class="login-links-container">
|
||||
<a th:href="@{/account_recovery(tab='findId')}" class="link-btn first">아이디 찾기</a>
|
||||
<a th:href="@{/account_recovery(tab='resetPassword')}" class="link-btn middle">비밀번호 초기화</a>
|
||||
<a th:href="@{/signup}" class="link-btn last">회원가입</a>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
@@ -252,4 +264,5 @@
|
||||
});
|
||||
</script>
|
||||
</th:block>
|
||||
|
||||
</html>
|
||||
@@ -25,9 +25,13 @@
|
||||
<!-- 모든 스타일은 detail-wrap 안에서만 적용 -->
|
||||
<div class="detail-wrap">
|
||||
<!-- Title Bar (Figma 및 스크린샷 대응) -->
|
||||
<div class="common-title-bar">
|
||||
<h2 class="common-title" th:text="${appRequest.approval != null and appRequest.approval.approvalStatus != null and appRequest.approval.approvalStatus.toString() == 'APPROVED' ? '인증키 정보' : '신청 상세'}">신청 상세</h2>
|
||||
<span class="common-subtitle" th:text="${appRequest.approval != null and appRequest.approval.approvalStatus != null and appRequest.approval.approvalStatus.toString() == 'APPROVED' ? '등록된 앱의 상세 정보를 확인할 수 있습니다.' : '신청하신 API Key의 상세 정보를 확인할 수 있습니다.'}">신청하신 API Key의 상세 정보를 확인할 수 있습니다.</span>
|
||||
<div class="board-header">
|
||||
<h2 class="board-title"
|
||||
th:text="${appRequest.approval != null and appRequest.approval.approvalStatus != null and appRequest.approval.approvalStatus.toString() == 'APPROVED' ? '인증키 정보' : '신청 상세'}">
|
||||
신청 상세</h2>
|
||||
<span class="board-desc"
|
||||
th:text="${appRequest.approval != null and appRequest.approval.approvalStatus != null and appRequest.approval.approvalStatus.toString() == 'APPROVED' ? '등록된 앱의 상세 정보를 확인할 수 있습니다.' : '신청하신 API Key의 상세 정보를 확인할 수 있습니다.'}">신청하신
|
||||
API Key의 상세 정보를 확인할 수 있습니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- App Basic Info Card -->
|
||||
@@ -49,7 +53,7 @@
|
||||
<div class="dt-app-status-row" th:if="${appRequest.approval != null}">
|
||||
<span class="dt-status-badge"
|
||||
th:classappend="${appRequest.approval.approvalStatus.toString() == 'APPROVED' ? 'status-approved' :
|
||||
(appRequest.approval.approvalStatus.toString() == 'REJECTED' ? 'status-rejected' : 'status-pending')}"
|
||||
(appRequest.approval.approvalStatus.toString() == 'REJECTED' or appRequest.approval.approvalStatus.toString() == 'REQUESTED' ? 'status-rejected' : 'status-pending')}"
|
||||
th:text="${appRequest.approval.approvalStatus.description}">
|
||||
대기중
|
||||
</span>
|
||||
@@ -80,7 +84,8 @@
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">
|
||||
</path>
|
||||
</svg>
|
||||
<span>복사</span>
|
||||
</button>
|
||||
@@ -176,7 +181,8 @@
|
||||
</div>
|
||||
|
||||
<!-- 승인일시 (approvalDate 가 있을 때만) -->
|
||||
<div class="dt-row" th:if="${appRequest.approval != null and appRequest.approval.approvalDate != null}">
|
||||
<div class="dt-row"
|
||||
th:if="${appRequest.approval != null and appRequest.approval.approvalDate != null}">
|
||||
<div class="dt-col">
|
||||
<label class="dt-label">승인일시</label>
|
||||
<div class="dt-input-group">
|
||||
@@ -204,11 +210,16 @@
|
||||
</div>
|
||||
|
||||
<!-- 내부 결재 진행중: 취소 불가 안내 -->
|
||||
<p class="dt-cancel-notice" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')"
|
||||
th:if="${appRequest.approval != null and appRequest.approval.approvalStatus != null and
|
||||
<div class="info-notice-box danger" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" th:if="${appRequest.approval != null and appRequest.approval.approvalStatus != null and
|
||||
appRequest.approval.approvalStatus.toString() == 'PROCESSING'}">
|
||||
내부 결재가 진행 중이라 신청을 취소할 수 없습니다. 취소가 필요한 경우 관리자에게 문의해 주세요.
|
||||
</p>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f4253c"
|
||||
stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||
</svg>
|
||||
<p>내부 결재가 진행 중이라 신청을 취소할 수 없습니다. 취소가 필요한 경우 관리자에게 문의해 주세요.</p>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Navigation Actions -->
|
||||
<div class="dt-actions">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
|
||||
layout:decorate="~{layout/djbank_title_layout}">
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/djbank_title_layout}">
|
||||
|
||||
<head>
|
||||
<title>API 키 상세</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section layout:fragment="title">
|
||||
<div class="page-title-banner">
|
||||
@@ -24,16 +24,18 @@
|
||||
<div class="app-management-content">
|
||||
<div class="detail-wrap">
|
||||
<!-- Title Bar -->
|
||||
<div class="common-title-bar">
|
||||
<h2 class="common-title">인증키 정보</h2>
|
||||
<span class="common-subtitle">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
|
||||
<div class="board-header">
|
||||
<h2 class="board-title">인증키 정보</h2>
|
||||
<span class="board-desc">등록된 앱의 상세 정보를 확인할 수 있습니다.</span>
|
||||
</div>
|
||||
|
||||
<!-- App Basic Info Card -->
|
||||
<div class="dt-app-card">
|
||||
<div class="dt-app-icon-box">
|
||||
<img th:if="${apiKey.appIconFileId != null}" th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}" alt="앱 아이콘">
|
||||
<svg th:unless="${apiKey.appIconFileId != null}" width="46" height="46" viewBox="0 0 24 24" fill="none" stroke="#64748b" stroke-width="1.5">
|
||||
<img th:if="${apiKey.appIconFileId != null}"
|
||||
th:src="@{/file/download(fileSn=1,fileId=${apiKey.appIconFileId})}" alt="앱 아이콘">
|
||||
<svg th:unless="${apiKey.appIconFileId != null}" width="46" height="46" viewBox="0 0 24 24" fill="none"
|
||||
stroke="#64748b" stroke-width="1.5">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||
<polyline points="21 15 16 10 5 21"></polyline>
|
||||
@@ -41,10 +43,12 @@
|
||||
</div>
|
||||
<div class="dt-app-info">
|
||||
<div class="dt-app-status-row">
|
||||
<span th:classappend="${apiKey.appstatus == '1' ? 'status-approved' : 'status-inactive'}" class="dt-status-badge" th:text="${apiKey.appstatus == '1' ? '승인' : '비활성화'}">승인</span>
|
||||
<span th:classappend="${apiKey.appstatus == '1' ? 'status-approved' : 'status-inactive'}"
|
||||
class="dt-status-badge" th:text="${apiKey.appstatus == '1' ? '승인' : '비활성화'}">승인</span>
|
||||
</div>
|
||||
<h3 class="dt-app-name" th:text="${apiKey.clientname}">앱 이름</h3>
|
||||
<p class="dt-app-desc" th:text="${apiKey.appDescription != null ? apiKey.appDescription : '앱 설명이 없습니다.'}">앱 설명이 표시됩니다.</p>
|
||||
<p class="dt-app-desc"
|
||||
th:text="${apiKey.appDescription != null ? apiKey.appDescription : '앱 설명이 없습니다.'}">앱 설명이 표시됩니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -57,8 +61,10 @@
|
||||
<label class="dt-label">Client ID</label>
|
||||
<div class="dt-input-group">
|
||||
<div class="dt-input-box" th:text="${apiKey.clientid}">K00000001</div>
|
||||
<button type="button" class="dt-btn-copy" th:data-secret="${apiKey.clientid}" onclick="copyToClipboardFromButton(this)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<button type="button" class="dt-btn-copy" th:data-secret="${apiKey.clientid}"
|
||||
onclick="copyToClipboardFromButton(this)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
@@ -73,7 +79,8 @@
|
||||
<div class="dt-col">
|
||||
<label class="dt-label">
|
||||
Client Secret
|
||||
<button type="button" class="btn-guide-info" onclick="showLostKeyGuide()" title="비밀정보 안내" aria-label="비밀정보 안내"
|
||||
<button type="button" class="btn-guide-info" onclick="showLostKeyGuide()" title="비밀정보 안내"
|
||||
aria-label="비밀정보 안내"
|
||||
style="margin-left:6px;width:18px;height:18px;border-radius:50%;border:1px solid #bbb;background:#f5f5f5;color:#666;font-size:12px;line-height:1;cursor:pointer;padding:0;vertical-align:middle;">?</button>
|
||||
</label>
|
||||
|
||||
@@ -81,8 +88,13 @@
|
||||
<th:block th:if="${secretAvailable}">
|
||||
<!-- View Button (shown initially) -->
|
||||
<div class="dt-input-group" id="hiddenSecretBox">
|
||||
<button type="button" style="width: 100%; border-radius: 10px; background-color: #2a69de; color: #fff; height: 47px; display: flex; justify-content: center; align-items: center; border: none; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s;" onmouseover="this.style.backgroundColor='#1d56c0'" onmouseout="this.style.backgroundColor='#2a69de'" onclick="showPasswordPrompt()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 8px;">
|
||||
<button type="button"
|
||||
style="width: 100%; border-radius: 10px; background-color: #2a69de; color: #fff; height: 47px; display: flex; justify-content: center; align-items: center; border: none; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s;"
|
||||
onmouseover="this.style.backgroundColor='#1d56c0'"
|
||||
onmouseout="this.style.backgroundColor='#2a69de'" onclick="showPasswordPrompt()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
style="margin-right: 8px;">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.35-4.35"></path>
|
||||
</svg>
|
||||
@@ -94,15 +106,18 @@
|
||||
<div id="revealedSecretBox" style="display: none; width: 100%;">
|
||||
<div class="dt-input-group">
|
||||
<div class="dt-input-box" id="revealedSecretValue" style="flex: 1;"></div>
|
||||
<button type="button" class="dt-btn-copy" id="revealedSecretCopyBtn" onclick="copyToClipboardFromButton(this)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<button type="button" class="dt-btn-copy" id="revealedSecretCopyBtn"
|
||||
onclick="copyToClipboardFromButton(this)">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<span>복사</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="secret-warning" style="margin:10px 0 0;padding:10px 12px;background:#fdecea;border:1px solid #f5c6c2;border-radius:6px;color:#c0392b;font-size:13px;line-height:1.6;">
|
||||
<p class="secret-warning"
|
||||
style="margin:10px 0 0;padding:10px 12px;background:#fdecea;border:1px solid #f5c6c2;border-radius:6px;color:#c0392b;font-size:13px;line-height:1.6;">
|
||||
⚠ 이 값은 <strong>지금 한 번만</strong> 표시되며, 이후에는 다시 조회할 수 없습니다.<br>
|
||||
반드시 <strong>안전한 곳에 복사하여 보관</strong>해 주세요.
|
||||
</p>
|
||||
@@ -110,10 +125,12 @@
|
||||
</th:block>
|
||||
|
||||
<!-- secret이 이미 노출·삭제됨 -->
|
||||
<div th:unless="${secretAvailable}" class="secret-revealed-notice" style="display:flex;align-items:center;gap:12px;">
|
||||
<div th:unless="${secretAvailable}" class="secret-revealed-notice"
|
||||
style="display:flex;align-items:center;gap:12px;">
|
||||
<span class="secret-revealed-text" style="color:#888;font-size:14px;">이미 1회 노출되어 삭제된 인증정보입니다.</span>
|
||||
<button type="button" class="btn-guide-link" onclick="showLostKeyGuide()"
|
||||
style="background:none;border:none;color:#2b6cb0;text-decoration:underline;cursor:pointer;font-size:13px;padding:0;">분실 시 안내</button>
|
||||
style="background:none;border:none;color:#2b6cb0;text-decoration:underline;cursor:pointer;font-size:13px;padding:0;">분실
|
||||
시 안내</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,7 +152,8 @@
|
||||
<div class="dt-col">
|
||||
<label class="dt-label">화이트 리스트</label>
|
||||
<div class="dt-input-group">
|
||||
<div class="dt-input-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">설정되지 않음</div>
|
||||
<div class="dt-input-box" th:text="${apiKey.allowedips != null ? apiKey.allowedips : '설정되지 않음'}">
|
||||
설정되지 않음</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,7 +179,8 @@
|
||||
<div class="dt-col">
|
||||
<label class="dt-label">최종 수정일자</label>
|
||||
<div class="dt-input-group">
|
||||
<div class="dt-input-box" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd')}">2025.11.22</div>
|
||||
<div class="dt-input-box" th:text="${#temporals.format(apiKey.modifiedon, 'yyyy.MM.dd')}">2025.11.22
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,8 +189,10 @@
|
||||
<!-- Bottom Navigation Actions -->
|
||||
<div class="dt-actions" style="justify-content: flex-end; gap: 11px; margin-top: 30px;">
|
||||
<a th:href="@{/myapikey}" class="dt-btn-gray">목록</a>
|
||||
<button type="button" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-red" th:data-client-id="${apiKey.clientid}" onclick="deleteApiKeyFromButton(this)">인증키 삭제</button>
|
||||
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-blue" th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">수정</a>
|
||||
<button type="button" sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-red"
|
||||
th:data-client-id="${apiKey.clientid}" onclick="deleteApiKeyFromButton(this)">인증키 삭제</button>
|
||||
<a sec:authorize="hasRole('ROLE_API_KEY_REQUEST')" class="dt-btn-blue"
|
||||
th:href="@{/myapikey/modify/step1(clientId=${apiKey.clientid})}">수정</a>
|
||||
</div>
|
||||
|
||||
</div><!-- /detail-wrap -->
|
||||
@@ -314,4 +335,5 @@
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -9,8 +9,8 @@
|
||||
</div>
|
||||
</section>
|
||||
<section layout:fragment="contentFragment">
|
||||
<div class="service-main app-management-layout">
|
||||
<th:block th:replace="~{fragment/djbank/service_sidebar :: sidebar('apiKey')}"></th:block>
|
||||
<div class="service-main container" style="padding-top: 70px; padding-bottom:100px;">
|
||||
<th:block th:replace="~{fragment/djbank/service_sidebar :: sidebar('profile')}"></th:block>
|
||||
<div class="app-management-content">
|
||||
<div class="corp-manager-wrapper">
|
||||
<h2 class="page-outer-title">법인 기본 정보</h2>
|
||||
|
||||
@@ -11,93 +11,7 @@
|
||||
</section>
|
||||
|
||||
<th:block layout:fragment="contentFragment">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/api-statistics.css}"/>
|
||||
<style>
|
||||
/* 통계 테이블 (실제 table 태그) 공통 */
|
||||
.api-statistics-container .stats-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
.api-statistics-container .stats-table th,
|
||||
.api-statistics-container .stats-table td {
|
||||
padding: 14px 12px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.api-statistics-container .stats-table thead th {
|
||||
text-align: center;
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.api-statistics-container .stats-table tfoot td {
|
||||
font-weight: 600;
|
||||
background: #f8fafc;
|
||||
}
|
||||
/* API별 표: auto layout — 숫자열은 내용에 맞춰 최소(nowrap), 통계는 고정, API명이 남은 폭 전체 */
|
||||
.api-statistics-container .stats-table--api { table-layout: auto; }
|
||||
.api-statistics-container .stats-table--api .col-name {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
padding-left: 4px;
|
||||
}
|
||||
/* API명 헤더 셀은 가운데 정렬 (데이터 셀은 좌측 유지) */
|
||||
.api-statistics-container .stats-table--api thead .col-name {
|
||||
text-align: center;
|
||||
}
|
||||
.api-statistics-container .stats-table--api .col-num {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
.api-statistics-container .stats-table--api .col-chart {
|
||||
text-align: center;
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
}
|
||||
.api-statistics-container .stats-table--api .col-chart .stats-progress {
|
||||
min-width: 150px;
|
||||
}
|
||||
.api-statistics-container .stats-table--api .api-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.api-statistics-container .stats-table--api .api-icon svg { display: block; }
|
||||
.api-statistics-container .stats-table--api .api-name-text { vertical-align: middle; }
|
||||
/* 일별/기간별 표: fixed layout — 5열 동일 너비, 전부 가운데 */
|
||||
.api-statistics-container .stats-table--period { table-layout: fixed; }
|
||||
.api-statistics-container .stats-table--period th,
|
||||
.api-statistics-container .stats-table--period td {
|
||||
text-align: center;
|
||||
width: 20%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* progress bar */
|
||||
.api-statistics-container .stats-progress {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* empty state */
|
||||
.api-statistics-container .stats-empty td {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #64748b;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
<!-- <link rel="stylesheet" type="text/css" th:href="@{/css/api-statistics.css}" /> -->
|
||||
<div class="service-main app-management-layout">
|
||||
<th:block th:replace="~{fragment/djbank/service_sidebar :: sidebar('statistics')}"></th:block>
|
||||
<div class="app-management-content">
|
||||
@@ -156,9 +70,7 @@
|
||||
<div class="app-select-wrapper">
|
||||
<select id="appSelect" class="app-select">
|
||||
<option value="">전체 앱</option>
|
||||
<option th:each="app : ${appList}"
|
||||
th:value="${app.clientId}"
|
||||
th:text="${app.clientName}"
|
||||
<option th:each="app : ${appList}" th:value="${app.clientId}" th:text="${app.clientName}"
|
||||
th:selected="${searchDto.clientId != null and searchDto.clientId == app.clientId}">
|
||||
앱 이름
|
||||
</option>
|
||||
@@ -167,32 +79,45 @@
|
||||
|
||||
<div class="summary-info">
|
||||
<p class="summary-total-text">
|
||||
총 API 호출 수는 <strong id="totalCount" th:text="${#numbers.formatInteger(summary.totalCount, 0, 'COMMA')}">0</strong>건입니다.
|
||||
총 API 호출 수는 <strong id="totalCount"
|
||||
th:text="${#numbers.formatInteger(summary.totalCount, 0, 'COMMA')}">0</strong>건입니다.
|
||||
</p>
|
||||
<div class="summary-detail-list">
|
||||
<div class="summary-detail-item success">
|
||||
<span class="detail-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><circle cx="8" cy="8" r="6"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<circle cx="8" cy="8" r="6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="detail-label">성공</span>
|
||||
<span class="detail-value" id="successCount" th:text="${#numbers.formatInteger(summary.successCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="successRateText" th:text="${summary.totalCount == 0 ? '-' : summary.successRate}">0</span>%)</span>
|
||||
<span class="detail-value" id="successCount"
|
||||
th:text="${#numbers.formatInteger(summary.successCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="successRateText"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.successRate}">0</span>%)</span>
|
||||
</div>
|
||||
<div class="summary-detail-item timeout">
|
||||
<span class="detail-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><circle cx="8" cy="8" r="6"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<circle cx="8" cy="8" r="6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="detail-label">타임아웃</span>
|
||||
<span class="detail-value" id="timeoutCount" th:text="${#numbers.formatInteger(summary.timeoutCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="timeoutRateText" th:text="${summary.totalCount == 0 ? '-' : summary.timeoutRate}">0</span>%)</span>
|
||||
<span class="detail-value" id="timeoutCount"
|
||||
th:text="${#numbers.formatInteger(summary.timeoutCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="timeoutRateText"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.timeoutRate}">0</span>%)</span>
|
||||
</div>
|
||||
<div class="summary-detail-item failure">
|
||||
<span class="detail-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><circle cx="8" cy="8" r="6"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<circle cx="8" cy="8" r="6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="detail-label">실패</span>
|
||||
<span class="detail-value" id="errorCount" th:text="${#numbers.formatInteger(summary.errorCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="errorRateText" th:text="${summary.totalCount == 0 ? '-' : summary.errorRate}">0</span>%)</span>
|
||||
<span class="detail-value" id="errorCount"
|
||||
th:text="${#numbers.formatInteger(summary.errorCount, 0, 'COMMA')}">0</span>
|
||||
<span class="detail-percent">(<span id="errorRateText"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.errorRate}">0</span>%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,18 +127,15 @@
|
||||
<div class="summary-center">
|
||||
<svg id="donutChart" width="200" height="200" viewBox="0 0 200 200">
|
||||
<circle class="donut-bg" cx="100" cy="100" r="70" fill="none" stroke="#e0e0e0" stroke-width="24" />
|
||||
<circle id="donutSuccess" class="donut-segment" cx="100" cy="100" r="70" fill="none"
|
||||
stroke="#4A90D9" stroke-width="24" stroke-linecap="butt"
|
||||
th:attr="stroke-dasharray=${summary.successRate * 4.398} + ' 439.8'"
|
||||
stroke-dashoffset="0" transform="rotate(-90 100 100)"/>
|
||||
<circle id="donutTimeout" class="donut-segment" cx="100" cy="100" r="70" fill="none"
|
||||
stroke="#F5C36B" stroke-width="24" stroke-linecap="butt"
|
||||
th:attr="stroke-dasharray=${summary.timeoutRate * 4.398} + ' 439.8',
|
||||
stroke-dashoffset='-' + ${summary.successRate * 4.398}"
|
||||
<circle id="donutSuccess" class="donut-segment" cx="100" cy="100" r="70" fill="none" stroke="#4A90D9"
|
||||
stroke-width="24" stroke-linecap="butt"
|
||||
th:attr="stroke-dasharray=${summary.successRate * 4.398} + ' 439.8'" stroke-dashoffset="0"
|
||||
transform="rotate(-90 100 100)" />
|
||||
<circle id="donutError" class="donut-segment" cx="100" cy="100" r="70" fill="none"
|
||||
stroke="#F5A3B5" stroke-width="24" stroke-linecap="butt"
|
||||
th:attr="stroke-dasharray=${summary.errorRate * 4.398} + ' 439.8',
|
||||
<circle id="donutTimeout" class="donut-segment" cx="100" cy="100" r="70" fill="none" stroke="#F5C36B"
|
||||
stroke-width="24" stroke-linecap="butt" th:attr="stroke-dasharray=${summary.timeoutRate * 4.398} + ' 439.8',
|
||||
stroke-dashoffset='-' + ${summary.successRate * 4.398}" transform="rotate(-90 100 100)" />
|
||||
<circle id="donutError" class="donut-segment" cx="100" cy="100" r="70" fill="none" stroke="#F5A3B5"
|
||||
stroke-width="24" stroke-linecap="butt" th:attr="stroke-dasharray=${summary.errorRate * 4.398} + ' 439.8',
|
||||
stroke-dashoffset='-' + ${(summary.successRate + summary.timeoutRate) * 4.398}"
|
||||
transform="rotate(-90 100 100)" />
|
||||
</svg>
|
||||
@@ -224,19 +146,22 @@
|
||||
<div class="rate-display success-rate">
|
||||
<span class="rate-indicator success"></span>
|
||||
<span class="rate-label">성공률</span>
|
||||
<span class="rate-value" id="successRate" th:text="${summary.totalCount == 0 ? '-' : summary.successRate}">0</span>
|
||||
<span class="rate-value" id="successRate"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.successRate}">0</span>
|
||||
<span class="rate-percent">%</span>
|
||||
</div>
|
||||
<div class="rate-display timeout-rate">
|
||||
<span class="rate-indicator timeout"></span>
|
||||
<span class="rate-label">타임아웃율</span>
|
||||
<span class="rate-value" id="timeoutRate" th:text="${summary.totalCount == 0 ? '-' : summary.timeoutRate}">0</span>
|
||||
<span class="rate-value" id="timeoutRate"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.timeoutRate}">0</span>
|
||||
<span class="rate-percent">%</span>
|
||||
</div>
|
||||
<div class="rate-display failure-rate">
|
||||
<span class="rate-indicator failure"></span>
|
||||
<span class="rate-label">실패률</span>
|
||||
<span class="rate-value" id="errorRate" th:text="${summary.totalCount == 0 ? '-' : summary.errorRate}">0</span>
|
||||
<span class="rate-value" id="errorRate"
|
||||
th:text="${summary.totalCount == 0 ? '-' : summary.errorRate}">0</span>
|
||||
<span class="rate-percent">%</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,83 +170,104 @@
|
||||
|
||||
<!-- API별 통계 Table Section -->
|
||||
<div class="statistics-table-section" style="margin-top: 40px;">
|
||||
<div class="table-controls" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; background: none; border: none; padding: 0;">
|
||||
<h3 class="table-title" style="font-size: 20px; font-weight: 700; color: #1e293b; display: flex; align-items: center; gap: 8px; margin: 0;">
|
||||
<div class="table-controls">
|
||||
<h3 class="table-title">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="M18 9l-5 5-4-4-4 4" />
|
||||
</svg>
|
||||
API별 통계
|
||||
</h3>
|
||||
<!-- 다운로드 버튼은 UI에서 제거됨 (백엔드 /statistics/api/download 는 유지) -->
|
||||
</div>
|
||||
|
||||
<table class="stats-table stats-table--api">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-name">API명</th>
|
||||
<th class="col-num">API호출수</th>
|
||||
<th class="col-num">성공</th>
|
||||
<th class="col-num">실패</th>
|
||||
<th class="col-num">타임아웃</th>
|
||||
<th class="col-chart">통계</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="statisticsTableBody">
|
||||
<tr th:each="detail : ${details}">
|
||||
<td class="col-name">
|
||||
<div class="board-table">
|
||||
<!-- Table Header -->
|
||||
<div class="board-table-header">
|
||||
<div class="header-cell col-name" style="flex: 2;">API명</div>
|
||||
<div class="header-cell col-num" style="width: 100px;">API호출수</div>
|
||||
<div class="header-cell col-num" style="width: 100px;">성공</div>
|
||||
<div class="header-cell col-num" style="width: 100px;">실패</div>
|
||||
<div class="header-cell col-num" style="width: 100px;">타임아웃</div>
|
||||
<div class="header-cell col-chart" style="width: 180px;">통계</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Body -->
|
||||
<div class="board-table-body" id="statisticsTableBody">
|
||||
<div class="board-table-row" th:each="detail : ${details}">
|
||||
<div class="row-cell col-name" style="flex: 2;">
|
||||
<span class="api-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
</svg>
|
||||
</span><span class="api-name-text" th:text="${detail.apiName}">API 이름</span>
|
||||
</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(detail.totalCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(detail.successCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(detail.errorCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(detail.timeoutCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-chart">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success" th:style="'width:' + ${detail.successRate} + '%; background: #4A90D9;'"></div>
|
||||
<div class="progress-timeout" th:style="'width:' + ${detail.timeoutRate} + '%; background: #F5C36B;'"></div>
|
||||
<div class="progress-failure" th:style="'width:' + ${detail.errorRate} + '%; background: #F5A3B5;'"></div>
|
||||
</span>
|
||||
<span class="api-name-text" th:text="${detail.apiName}">API 이름</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${details == null or details.isEmpty()}" class="stats-empty">
|
||||
<td colspan="6">데이터가 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot id="statisticsTableFoot">
|
||||
<tr class="total-row" th:if="${details != null and !details.isEmpty()}">
|
||||
<td class="col-name">
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="API호출수"
|
||||
th:text="${#numbers.formatInteger(detail.totalCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="성공"
|
||||
th:text="${#numbers.formatInteger(detail.successCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="실패"
|
||||
th:text="${#numbers.formatInteger(detail.errorCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="타임아웃"
|
||||
th:text="${#numbers.formatInteger(detail.timeoutCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-chart" style="width: 180px;" data-label="통계">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success"
|
||||
th:style="'width:' + ${detail.successRate} + '%; background: #4A90D9;'"></div>
|
||||
<div class="progress-timeout"
|
||||
th:style="'width:' + ${detail.timeoutRate} + '%; background: #F5C36B;'"></div>
|
||||
<div class="progress-failure"
|
||||
th:style="'width:' + ${detail.errorRate} + '%; background: #F5A3B5;'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:if="${details == null or details.isEmpty()}" class="stats-empty">
|
||||
데이터가 없습니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Footer -->
|
||||
<div class="board-table-body" id="statisticsTableFoot">
|
||||
<div class="board-table-row total-row" th:if="${details != null and !details.isEmpty()}">
|
||||
<div class="row-cell col-name" style="flex: 2;">
|
||||
<span class="api-icon total">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2">
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="M18 9l-5 5-4-4-4 4" />
|
||||
</svg>
|
||||
</span><span class="api-name-text">전체</span>
|
||||
</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(summary.totalCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(summary.successCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(summary.errorCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-num" th:text="${#numbers.formatInteger(summary.timeoutCount, 0, 'COMMA')}">0</td>
|
||||
<td class="col-chart">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success" th:style="'width:' + ${summary.successRate} + '%; background: #4A90D9;'"></div>
|
||||
<div class="progress-timeout" th:style="'width:' + ${summary.timeoutRate} + '%; background: #F5C36B;'"></div>
|
||||
<div class="progress-failure" th:style="'width:' + ${summary.errorRate} + '%; background: #F5A3B5;'"></div>
|
||||
</span>
|
||||
<span class="api-name-text">전체</span>
|
||||
</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 API호출수"
|
||||
th:text="${#numbers.formatInteger(summary.totalCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 성공"
|
||||
th:text="${#numbers.formatInteger(summary.successCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 실패"
|
||||
th:text="${#numbers.formatInteger(summary.errorCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 타임아웃"
|
||||
th:text="${#numbers.formatInteger(summary.timeoutCount, 0, 'COMMA')}">0</div>
|
||||
<div class="row-cell col-chart" style="width: 180px;" data-label="전체 통계">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success"
|
||||
th:style="'width:' + ${summary.successRate} + '%; background: #4A90D9;'"></div>
|
||||
<div class="progress-timeout"
|
||||
th:style="'width:' + ${summary.timeoutRate} + '%; background: #F5C36B;'"></div>
|
||||
<div class="progress-failure"
|
||||
th:style="'width:' + ${summary.errorRate} + '%; background: #F5A3B5;'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 기간별(일별/월별) 누적 통계 Table Section -->
|
||||
<div class="statistics-table-section" style="margin-top: 40px;">
|
||||
<div class="table-controls" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; background: none; border: none; padding: 0;">
|
||||
<h3 class="table-title" style="font-size: 20px; font-weight: 700; color: #1e293b; display: flex; align-items: center; gap: 8px; margin: 0;">
|
||||
<div class="table-controls">
|
||||
<h3 class="table-title">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||||
<path d="M16 2v4M8 2v4M3 10h18" />
|
||||
@@ -330,31 +276,34 @@
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<table class="stats-table stats-table--period">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="periodLabel">일자</th>
|
||||
<th>API호출수</th>
|
||||
<th>성공</th>
|
||||
<th>실패</th>
|
||||
<th>타임아웃</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="periodTableBody">
|
||||
<tr th:each="p : ${periods}">
|
||||
<td th:text="${p.period}">2026-07-15 (수)</td>
|
||||
<td th:text="${p.hasData ? #numbers.formatInteger(p.totalCount, 0, 'COMMA') : '-'}">0</td>
|
||||
<td th:text="${p.hasData ? #numbers.formatInteger(p.successCount, 0, 'COMMA') : '-'}">0</td>
|
||||
<td th:text="${p.hasData ? #numbers.formatInteger(p.errorCount, 0, 'COMMA') : '-'}">0</td>
|
||||
<td th:text="${p.hasData ? #numbers.formatInteger(p.timeoutCount, 0, 'COMMA') : '-'}">0</td>
|
||||
</tr>
|
||||
<tr th:if="${periods == null or periods.isEmpty()}" class="stats-empty">
|
||||
<td colspan="5">데이터가 없습니다.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="board-table">
|
||||
<!-- Table Header -->
|
||||
<div class="board-table-header">
|
||||
<div class="header-cell" id="periodLabel" style="flex: 1;">일자</div>
|
||||
<div class="header-cell" style="flex: 1;">API호출수</div>
|
||||
<div class="header-cell" style="flex: 1;">성공</div>
|
||||
<div class="header-cell" style="flex: 1;">실패</div>
|
||||
<div class="header-cell" style="flex: 1;">타임아웃</div>
|
||||
</div>
|
||||
<!-- Table Body -->
|
||||
<div class="board-table-body" id="periodTableBody">
|
||||
<div class="board-table-row" th:each="p : ${periods}">
|
||||
<div class="row-cell" style="flex: 1;" data-label="일자" th:text="${p.period}">2026-07-15 (수)</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="API호출수"
|
||||
th:text="${p.hasData ? #numbers.formatInteger(p.totalCount, 0, 'COMMA') : '-'}">0</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="성공"
|
||||
th:text="${p.hasData ? #numbers.formatInteger(p.successCount, 0, 'COMMA') : '-'}">0</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="실패"
|
||||
th:text="${p.hasData ? #numbers.formatInteger(p.errorCount, 0, 'COMMA') : '-'}">0</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="타임아웃"
|
||||
th:text="${p.hasData ? #numbers.formatInteger(p.timeoutCount, 0, 'COMMA') : '-'}">0</div>
|
||||
</div>
|
||||
<div th:if="${periods == null or periods.isEmpty()}" class="stats-empty">
|
||||
데이터가 없습니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,30 +366,30 @@
|
||||
if (currentData.details && currentData.details.length > 0) {
|
||||
currentData.details.forEach(function (d) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td class="col-name">
|
||||
<div class="board-table-row">
|
||||
<div class="row-cell col-name" style="flex: 2;">
|
||||
<span class="api-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||
</svg>
|
||||
</span><span class="api-name-text">${d.apiName || '-'}</span>
|
||||
</td>
|
||||
<td class="col-num">${numberWithCommas(d.totalCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(d.successCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(d.errorCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(d.timeoutCount)}</td>
|
||||
<td class="col-chart">
|
||||
</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="API호출수">${numberWithCommas(d.totalCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="성공">${numberWithCommas(d.successCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="실패">${numberWithCommas(d.errorCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="타임아웃">${numberWithCommas(d.timeoutCount)}</div>
|
||||
<div class="row-cell col-chart" style="width: 180px;" data-label="통계">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success" style="width:${d.successRate}%; background: #4A90D9;"></div>
|
||||
<div class="progress-timeout" style="width:${d.timeoutRate}%; background: #F5C36B;"></div>
|
||||
<div class="progress-failure" style="width:${d.errorRate}%; background: #F5A3B5;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
</div>
|
||||
</div>`);
|
||||
});
|
||||
renderTotalFooter();
|
||||
} else {
|
||||
tbody.html('<tr class="stats-empty"><td colspan="6">데이터가 없습니다.</td></tr>');
|
||||
tbody.html('<div class="stats-empty">데이터가 없습니다.</div>');
|
||||
$('#statisticsTableFoot').empty();
|
||||
}
|
||||
}
|
||||
@@ -448,26 +397,26 @@
|
||||
function renderTotalFooter() {
|
||||
var sm = currentData.summary;
|
||||
$('#statisticsTableFoot').html(`
|
||||
<tr class="total-row">
|
||||
<td class="col-name">
|
||||
<div class="board-table-row total-row">
|
||||
<div class="row-cell col-name" style="flex: 2;">
|
||||
<span class="api-icon total">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 3v18h18"/><path d="M18 9l-5 5-4-4-4 4"/>
|
||||
</svg>
|
||||
</span><span class="api-name-text">전체</span>
|
||||
</td>
|
||||
<td class="col-num">${numberWithCommas(sm.totalCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(sm.successCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(sm.errorCount)}</td>
|
||||
<td class="col-num">${numberWithCommas(sm.timeoutCount)}</td>
|
||||
<td class="col-chart">
|
||||
</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 API호출수">${numberWithCommas(sm.totalCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 성공">${numberWithCommas(sm.successCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 실패">${numberWithCommas(sm.errorCount)}</div>
|
||||
<div class="row-cell col-num" style="width: 100px;" data-label="전체 타임아웃">${numberWithCommas(sm.timeoutCount)}</div>
|
||||
<div class="row-cell col-chart" style="width: 180px;" data-label="전체 통계">
|
||||
<div class="stats-progress">
|
||||
<div class="progress-success" style="width:${sm.successRate}%; background: #4A90D9;"></div>
|
||||
<div class="progress-timeout" style="width:${sm.timeoutRate}%; background: #F5C36B;"></div>
|
||||
<div class="progress-failure" style="width:${sm.errorRate}%; background: #F5A3B5;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`);
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
// ── 기간별(일/월) 표 (table DOM) ──
|
||||
@@ -477,16 +426,16 @@
|
||||
if (currentData.periods && currentData.periods.length > 0) {
|
||||
currentData.periods.forEach(function (p) {
|
||||
tbody.append(`
|
||||
<tr>
|
||||
<td>${p.period || '-'}</td>
|
||||
<td>${p.hasData ? numberWithCommas(p.totalCount) : '-'}</td>
|
||||
<td>${p.hasData ? numberWithCommas(p.successCount) : '-'}</td>
|
||||
<td>${p.hasData ? numberWithCommas(p.errorCount) : '-'}</td>
|
||||
<td>${p.hasData ? numberWithCommas(p.timeoutCount) : '-'}</td>
|
||||
</tr>`);
|
||||
<div class="board-table-row">
|
||||
<div class="row-cell" style="flex: 1;" data-label="일자">${p.period || '-'}</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="API호출수">${p.hasData ? numberWithCommas(p.totalCount) : '-'}</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="성공">${p.hasData ? numberWithCommas(p.successCount) : '-'}</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="실패">${p.hasData ? numberWithCommas(p.errorCount) : '-'}</div>
|
||||
<div class="row-cell" style="flex: 1;" data-label="타임아웃">${p.hasData ? numberWithCommas(p.timeoutCount) : '-'}</div>
|
||||
</div>`);
|
||||
});
|
||||
} else {
|
||||
tbody.html('<tr class="stats-empty"><td colspan="5">데이터가 없습니다.</td></tr>');
|
||||
tbody.html('<div class="stats-empty">데이터가 없습니다.</div>');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,4 +572,5 @@
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user