update 22
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as e,a as s,h as r,G as n,i as o,t as c,n as d}from"./index-BB0X_WMV.js";const l={__name:"Badge",props:{text:String,variant:{type:String,default:"default"}},setup(a){return(t,i)=>(r(),s("span",{class:d(["badge",`badge-${a.variant}`])},[n(t.$slots,"default",{},()=>[o(c(a.text),1)])],2))}},g=e(l,[["__scopeId","data-v-b7bd2350"]]);export{g as B};
|
import"./index--fsvNaiQ.js";import{_ as e,a as s,h as r,G as n,i as o,t as c,n as d}from"./index-DFoOAXeQ.js";const l={__name:"Badge",props:{text:String,variant:{type:String,default:"default"}},setup(a){return(t,i)=>(r(),s("span",{class:d(["badge",`badge-${a.variant}`])},[n(t.$slots,"default",{},()=>[o(c(a.text),1)])],2))}},g=e(l,[["__scopeId","data-v-b7bd2350"]]);export{g as B};
|
||||||
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as d,r,a,e as c,G as u,n as f,h as o}from"./index-BB0X_WMV.js";const m=["type","disabled"],b={key:0,class:"spinner"},y={__name:"Button",props:{type:{type:String,default:"button"},variant:{type:String,default:"primary"},size:{type:String,default:"md"},disabled:Boolean,loading:Boolean},emits:["click"],setup(e,{expose:l}){const n=r(null);return l({focus:()=>{var t;(t=n.value)==null||t.focus()}}),(t,s)=>(o(),a("button",{ref_key:"buttonRef",ref:n,type:e.type,class:f(["btn",`btn-${e.variant}`,{"btn-sm":e.size==="sm","btn-lg":e.size==="lg"}]),disabled:e.disabled||e.loading,onClick:s[0]||(s[0]=i=>t.$emit("click",i))},[e.loading?(o(),a("span",b)):c("",!0),u(t.$slots,"default",{},void 0)],10,m))}},B=d(y,[["__scopeId","data-v-c92354e1"]]);export{B};
|
import"./index--fsvNaiQ.js";import{_ as d,r,a,e as c,G as u,n as f,h as o}from"./index-DFoOAXeQ.js";const m=["type","disabled"],b={key:0,class:"spinner"},y={__name:"Button",props:{type:{type:String,default:"button"},variant:{type:String,default:"primary"},size:{type:String,default:"md"},disabled:Boolean,loading:Boolean},emits:["click"],setup(e,{expose:l}){const n=r(null);return l({focus:()=>{var t;(t=n.value)==null||t.focus()}}),(t,s)=>(o(),a("button",{ref_key:"buttonRef",ref:n,type:e.type,class:f(["btn",`btn-${e.variant}`,{"btn-sm":e.size==="sm","btn-lg":e.size==="lg"}]),disabled:e.disabled||e.loading,onClick:s[0]||(s[0]=i=>t.$emit("click",i))},[e.loading?(o(),a("span",b)):c("",!0),u(t.$slots,"default",{},void 0)],10,m))}},B=d(y,[["__scopeId","data-v-c92354e1"]]);export{B};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as C,r as g,o as x,a as l,b as s,l as w,s as I,t as d,F as B,g as T,h as o,d as R,w as m,f as L,u as _}from"./index-BB0X_WMV.js";import{a as A,C as F}from"./index-09HB4Lmg.js";import{C as O,a as W,L as N,B as V,p as E,b as M,c as $,d as P,e as z}from"./chartjs-plugin-datalabels.esm-DiDzp_cw.js";const U={class:"daily-stats"},Y={class:"page-header"},Z={class:"filter-section"},j={key:0,class:"loading"},q={key:1,class:"no-data"},G={key:2,class:"server-charts"},H={class:"chart-header"},J={class:"chart-subtitle"},K={class:"chart-wrapper"},Q={class:"chart-container"},X={__name:"DailyStats",setup(tt){O.register(W,N,V,E,M,$,P);const c=g(!1),r=g([]),n=g(f());function f(){return new Date().toISOString().split("T")[0]}function b(){const t=new Date(n.value);t.setDate(t.getDate()-1),n.value=t.toISOString().split("T")[0],i()}function y(){const t=new Date(n.value);t.setDate(t.getDate()+1),n.value=t.toISOString().split("T")[0],i()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:t=>{if(t.datasetIndex!==2)return!1;const e=t.chart.data.datasets,a=t.dataIndex;return e.reduce((u,p)=>u+(p.data[a]||0),0)>0},anchor:"end",align:"end",offset:0,font:{size:9},color:"#666",formatter:(t,e)=>{const a=e.chart.data.datasets,v=e.dataIndex;return a.reduce((u,p)=>u+(p.data[v]||0),0)}}},scales:{x:{stacked:!0,ticks:{maxRotation:0,autoSkip:!0,maxTicksLimit:24,callback:function(t,e){const a=this.getLabelForValue(t);return a&&a.endsWith(":00")?a:""}},grid:{display:!1}},y:{stacked:!0,beginAtZero:!0}},barPercentage:.8,categoryPercentage:.9},D=t=>({labels:t.timeStats.map(a=>a.time),datasets:[{label:"CRITICAL",data:t.timeStats.map(a=>a.critical),backgroundColor:"#9b59b6",borderWidth:0},{label:"ERROR",data:t.timeStats.map(a=>a.error),backgroundColor:"#e74c3c",borderWidth:0},{label:"WARN",data:t.timeStats.map(a=>a.warn),backgroundColor:"#f39c12",borderWidth:0}]}),i=async()=>{c.value=!0;try{r.value=await A.getTimeStatsByServer(n.value,15)}catch(t){console.error("Failed to load stats:",t),r.value=[]}finally{c.value=!1}},h=t=>{const e=new Date(t);return`${e.getFullYear()}년 ${e.getMonth()+1}월 ${e.getDate()}일`},k=t=>t.timeStats.reduce((e,a)=>e+a.total,0);return x(()=>{i()}),(t,e)=>(o(),l("div",U,[s("div",Y,[e[1]||(e[1]=s("h2",null,"일별 에러현황",-1)),s("div",Z,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),w(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=a=>n.value=a),onChange:i},null,544),[[I,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),c.value?(o(),l("div",j,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",q,[s("p",null,d(h(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",G,[(o(!0),l(B,null,T(r.value,a=>(o(),R(_(F),{key:a.serverId,class:"server-chart-card"},{header:m(()=>[s("div",H,[s("h3",null,"🖥️ "+d(a.serverName),1),s("span",J,d(h(n.value))+" 15분 단위 에러 ("+d(k(a))+"건)",1)])]),default:m(()=>[s("div",K,[s("div",Q,[L(_(z),{data:D(a),options:S},null,8,["data"])])])]),_:2},1024))),128))]))]))}},nt=C(X,[["__scopeId","data-v-b7241be8"]]);export{nt as default};
|
import{_ as C,r as g,o as x,a as l,b as s,l as w,s as I,t as d,F as B,g as T,h as o,d as R,w as m,f as L,u as _}from"./index-DFoOAXeQ.js";import{a as A,C as F}from"./index--fsvNaiQ.js";import{C as O,a as W,L as N,B as V,p as E,b as M,c as $,d as P,e as z}from"./chartjs-plugin-datalabels.esm-B8xtR40N.js";const U={class:"daily-stats"},Y={class:"page-header"},Z={class:"filter-section"},j={key:0,class:"loading"},q={key:1,class:"no-data"},G={key:2,class:"server-charts"},H={class:"chart-header"},J={class:"chart-subtitle"},K={class:"chart-wrapper"},Q={class:"chart-container"},X={__name:"DailyStats",setup(tt){O.register(W,N,V,E,M,$,P);const c=g(!1),r=g([]),n=g(f());function f(){return new Date().toISOString().split("T")[0]}function b(){const t=new Date(n.value);t.setDate(t.getDate()-1),n.value=t.toISOString().split("T")[0],i()}function y(){const t=new Date(n.value);t.setDate(t.getDate()+1),n.value=t.toISOString().split("T")[0],i()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:t=>{if(t.datasetIndex!==2)return!1;const e=t.chart.data.datasets,a=t.dataIndex;return e.reduce((u,p)=>u+(p.data[a]||0),0)>0},anchor:"end",align:"end",offset:0,font:{size:9},color:"#666",formatter:(t,e)=>{const a=e.chart.data.datasets,v=e.dataIndex;return a.reduce((u,p)=>u+(p.data[v]||0),0)}}},scales:{x:{stacked:!0,ticks:{maxRotation:0,autoSkip:!0,maxTicksLimit:24,callback:function(t,e){const a=this.getLabelForValue(t);return a&&a.endsWith(":00")?a:""}},grid:{display:!1}},y:{stacked:!0,beginAtZero:!0}},barPercentage:.8,categoryPercentage:.9},D=t=>({labels:t.timeStats.map(a=>a.time),datasets:[{label:"CRITICAL",data:t.timeStats.map(a=>a.critical),backgroundColor:"#9b59b6",borderWidth:0},{label:"ERROR",data:t.timeStats.map(a=>a.error),backgroundColor:"#e74c3c",borderWidth:0},{label:"WARN",data:t.timeStats.map(a=>a.warn),backgroundColor:"#f39c12",borderWidth:0}]}),i=async()=>{c.value=!0;try{r.value=await A.getTimeStatsByServer(n.value,15)}catch(t){console.error("Failed to load stats:",t),r.value=[]}finally{c.value=!1}},h=t=>{const e=new Date(t);return`${e.getFullYear()}년 ${e.getMonth()+1}월 ${e.getDate()}일`},k=t=>t.timeStats.reduce((e,a)=>e+a.total,0);return x(()=>{i()}),(t,e)=>(o(),l("div",U,[s("div",Y,[e[1]||(e[1]=s("h2",null,"일별 에러현황",-1)),s("div",Z,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),w(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=a=>n.value=a),onChange:i},null,544),[[I,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),c.value?(o(),l("div",j,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",q,[s("p",null,d(h(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",G,[(o(!0),l(B,null,T(r.value,a=>(o(),R(_(F),{key:a.serverId,class:"server-chart-card"},{header:m(()=>[s("div",H,[s("h3",null,"🖥️ "+d(a.serverName),1),s("span",J,d(h(n.value))+" 15분 단위 에러 ("+d(k(a))+"건)",1)])]),default:m(()=>[s("div",K,[s("div",Q,[L(_(z),{data:D(a),options:S},null,8,["data"])])])]),_:2},1024))),128))]))]))}},nt=C(X,[["__scopeId","data-v-b7241be8"]]);export{nt as default};
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as h,a as e,h as a,b as o,e as u,F as d,g as i,j as f,t as c,G as y,i as g}from"./index-BB0X_WMV.js";const b={class:"data-table-wrapper"},p={class:"data-table"},$={key:0,class:"actions-col"},_={key:0},D=["colspan"],S={key:1},T=["colspan"],B=["onClick"],N={key:0,class:"actions-col"},V={__name:"DataTable",props:{columns:{type:Array,required:!0},data:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},emptyText:{type:String,default:"데이터가 없습니다."}},emits:["row-click"],setup(n){const m=(t,r)=>t==null?"-":r.type==="date"&&t?new Date(t).toLocaleString("ko-KR"):r.type==="boolean"?t?"Y":"N":t;return(t,r)=>(a(),e("div",b,[o("table",p,[o("thead",null,[o("tr",null,[(a(!0),e(d,null,i(n.columns,s=>(a(),e("th",{key:s.key,style:f({width:s.width})},c(s.label),5))),128)),t.$slots.actions?(a(),e("th",$,"작업")):u("",!0)])]),o("tbody",null,[n.loading?(a(),e("tr",_,[o("td",{colspan:n.columns.length+(t.$slots.actions?1:0),class:"loading-cell"}," 로딩 중... ",8,D)])):!n.data||n.data.length===0?(a(),e("tr",S,[o("td",{colspan:n.columns.length+(t.$slots.actions?1:0),class:"empty-cell"},c(n.emptyText),9,T)])):(a(!0),e(d,{key:2},i(n.data,(s,k)=>(a(),e("tr",{key:s.id||k,onClick:l=>t.$emit("row-click",s)},[(a(!0),e(d,null,i(n.columns,l=>(a(),e("td",{key:l.key},[y(t.$slots,l.key,{row:s,value:s[l.key]},()=>[g(c(m(s[l.key],l)),1)])]))),128)),t.$slots.actions?(a(),e("td",N,[y(t.$slots,"actions",{row:s},void 0)])):u("",!0)],8,B))),128))])])]))}},A=h(V,[["__scopeId","data-v-db5e24a9"]]);export{A as D};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as i,c as s,a as t,h as l,e as o,i as c,t as n,F as m,g as y}from"./index-BB0X_WMV.js";const h={class:"form-group"},v=["for"],b={key:0,class:"required"},g=["id","type","value","placeholder","disabled","readonly"],f=["id","value","placeholder","disabled","readonly","rows"],k=["id","value","disabled"],V={key:0,value:""},S=["value"],x={key:4,class:"error-text"},I={key:5,class:"hint-text"},B={__name:"FormInput",props:{modelValue:{type:[String,Number],default:""},label:String,type:{type:String,default:"text"},placeholder:String,required:Boolean,disabled:Boolean,readonly:Boolean,error:String,hint:String,rows:{type:Number,default:3},options:{type:Array,default:()=>[]}},emits:["update:modelValue"],setup(e){const r=s(()=>`input-${Math.random().toString(36).slice(2,9)}`);return(u,d)=>(l(),t("div",h,[e.label?(l(),t("label",{key:0,for:r.value},[c(n(e.label)+" ",1),e.required?(l(),t("span",b,"*")):o("",!0)],8,v)):o("",!0),e.type!=="textarea"&&e.type!=="select"?(l(),t("input",{key:1,id:r.value,type:e.type,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,class:"form-input",onInput:d[0]||(d[0]=a=>u.$emit("update:modelValue",a.target.value))},null,40,g)):e.type==="textarea"?(l(),t("textarea",{key:2,id:r.value,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,rows:e.rows,class:"form-input",onInput:d[1]||(d[1]=a=>u.$emit("update:modelValue",a.target.value))},null,40,f)):e.type==="select"?(l(),t("select",{key:3,id:r.value,value:e.modelValue,disabled:e.disabled,class:"form-input",onChange:d[2]||(d[2]=a=>u.$emit("update:modelValue",a.target.value))},[e.placeholder?(l(),t("option",V,n(e.placeholder),1)):o("",!0),(l(!0),t(m,null,y(e.options,a=>(l(),t("option",{key:a.value,value:a.value},n(a.label),9,S))),128))],40,k)):o("",!0),e.error?(l(),t("span",x,n(e.error),1)):o("",!0),e.hint?(l(),t("span",I,n(e.hint),1)):o("",!0)]))}},N=i(B,[["__scopeId","data-v-45f49038"]]);export{N as F};
|
import"./index--fsvNaiQ.js";import{_ as i,c as s,a as t,h as l,e as o,i as c,t as n,F as m,g as y}from"./index-DFoOAXeQ.js";const h={class:"form-group"},v=["for"],b={key:0,class:"required"},g=["id","type","value","placeholder","disabled","readonly"],f=["id","value","placeholder","disabled","readonly","rows"],k=["id","value","disabled"],V={key:0,value:""},S=["value"],x={key:4,class:"error-text"},I={key:5,class:"hint-text"},B={__name:"FormInput",props:{modelValue:{type:[String,Number],default:""},label:String,type:{type:String,default:"text"},placeholder:String,required:Boolean,disabled:Boolean,readonly:Boolean,error:String,hint:String,rows:{type:Number,default:3},options:{type:Array,default:()=>[]}},emits:["update:modelValue"],setup(e){const r=s(()=>`input-${Math.random().toString(36).slice(2,9)}`);return(u,d)=>(l(),t("div",h,[e.label?(l(),t("label",{key:0,for:r.value},[c(n(e.label)+" ",1),e.required?(l(),t("span",b,"*")):o("",!0)],8,v)):o("",!0),e.type!=="textarea"&&e.type!=="select"?(l(),t("input",{key:1,id:r.value,type:e.type,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,class:"form-input",onInput:d[0]||(d[0]=a=>u.$emit("update:modelValue",a.target.value))},null,40,g)):e.type==="textarea"?(l(),t("textarea",{key:2,id:r.value,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,rows:e.rows,class:"form-input",onInput:d[1]||(d[1]=a=>u.$emit("update:modelValue",a.target.value))},null,40,f)):e.type==="select"?(l(),t("select",{key:3,id:r.value,value:e.modelValue,disabled:e.disabled,class:"form-input",onChange:d[2]||(d[2]=a=>u.$emit("update:modelValue",a.target.value))},[e.placeholder?(l(),t("option",V,n(e.placeholder),1)):o("",!0),(l(!0),t(m,null,y(e.options,a=>(l(),t("option",{key:a.value,value:a.value},n(a.label),9,S))),128))],40,k)):o("",!0),e.error?(l(),t("span",x,n(e.error),1)):o("",!0),e.hint?(l(),t("span",I,n(e.hint),1)):o("",!0)]))}},N=i(B,[["__scopeId","data-v-45f49038"]]);export{N as F};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as r,d as m,a as d,e as i,b as e,t as u,G as c,j as f,m as p,T as h,h as s}from"./index-BB0X_WMV.js";import"./index-09HB4Lmg.js";const y={class:"modal-header"},_={class:"modal-body"},k={key:0,class:"modal-footer"},v={__name:"Modal",props:{modelValue:{type:Boolean,default:!1},title:{type:String,default:""},width:{type:String,default:"500px"}},emits:["update:modelValue","close"],setup(t,{emit:n}){const a=n,l=()=>{a("update:modelValue",!1),a("close")};return(o,V)=>(s(),m(h,{to:"body"},[t.modelValue?(s(),d("div",{key:0,class:"modal-overlay",onClick:p(l,["self"])},[e("div",{class:"modal",style:f({width:t.width})},[e("div",y,[e("h3",null,u(t.title),1),e("button",{class:"close-btn",onClick:l},"×")]),e("div",_,[c(o.$slots,"default",{},void 0)]),o.$slots.footer?(s(),d("div",k,[c(o.$slots,"footer",{},void 0)])):i("",!0)],4)])):i("",!0)]))}},S=r(v,[["__scopeId","data-v-90993dd3"]]);export{S as M};
|
import{_ as r,d as m,a as d,e as i,b as e,t as u,G as c,j as f,m as p,T as h,h as s}from"./index-DFoOAXeQ.js";import"./index--fsvNaiQ.js";const y={class:"modal-header"},_={class:"modal-body"},k={key:0,class:"modal-footer"},v={__name:"Modal",props:{modelValue:{type:Boolean,default:!1},title:{type:String,default:""},width:{type:String,default:"500px"}},emits:["update:modelValue","close"],setup(t,{emit:n}){const a=n,l=()=>{a("update:modelValue",!1),a("close")};return(o,V)=>(s(),m(h,{to:"body"},[t.modelValue?(s(),d("div",{key:0,class:"modal-overlay",onClick:p(l,["self"])},[e("div",{class:"modal",style:f({width:t.width})},[e("div",y,[e("h3",null,u(t.title),1),e("button",{class:"close-btn",onClick:l},"×")]),e("div",_,[c(o.$slots,"default",{},void 0)]),o.$slots.footer?(s(),d("div",k,[c(o.$slots,"footer",{},void 0)])):i("",!0)],4)])):i("",!0)]))}},S=r(v,[["__scopeId","data-v-90993dd3"]]);export{S as M};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as M,r as g,o as w,a as l,b as s,l as x,s as R,t as c,F as $,g as B,h as o,d as D,w as v,f as I,u as _}from"./index-BB0X_WMV.js";import{a as N,C as F}from"./index-09HB4Lmg.js";import{C as A,a as L,L as E,B as T,p as V,b as Y,c as O,d as z,e as U}from"./chartjs-plugin-datalabels.esm-DiDzp_cw.js";const W={class:"monthly-stats"},Z={class:"page-header"},j={class:"filter-section"},q={key:0,class:"loading"},G={key:1,class:"no-data"},H={key:2,class:"server-charts"},J={class:"chart-header"},K={class:"chart-subtitle"},P={class:"chart-container"},Q={__name:"MonthlyStats",setup(X){A.register(L,E,T,V,Y,O,z);const i=g(!1),r=g([]),n=g(f());function f(){const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}function b(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e-2,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}function y(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:a=>{if(a.datasetIndex!==2)return!1;const e=a.chart.data.datasets,t=a.dataIndex;return e.reduce((p,h)=>p+(h.data[t]||0),0)>0},anchor:"end",align:"end",offset:2,font:{size:10,weight:"bold"},color:"#666",formatter:(a,e)=>{const t=e.chart.data.datasets,u=e.dataIndex;return t.reduce((p,h)=>p+(h.data[u]||0),0)}}},scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0}}},C=a=>({labels:a.dailyStats.map(t=>`${new Date(t.date).getDate()}일`),datasets:[{label:"CRITICAL",data:a.dailyStats.map(t=>t.critical),backgroundColor:"#9b59b6",borderRadius:2},{label:"ERROR",data:a.dailyStats.map(t=>t.error),backgroundColor:"#e74c3c",borderRadius:2},{label:"WARN",data:a.dailyStats.map(t=>t.warn),backgroundColor:"#f39c12",borderRadius:2}]}),d=async()=>{i.value=!0;try{const[a,e]=n.value.split("-").map(Number);r.value=await N.getMonthlyStatsByServer(a,e)}catch(a){console.error("Failed to load stats:",a),r.value=[]}finally{i.value=!1}},m=a=>{const[e,t]=a.split("-");return`${e}년 ${parseInt(t)}월`},k=a=>a.dailyStats.reduce((e,t)=>e+t.total,0);return w(()=>{d()}),(a,e)=>(o(),l("div",W,[s("div",Z,[e[1]||(e[1]=s("h2",null,"월별 에러현황",-1)),s("div",j,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),x(s("input",{type:"month","onUpdate:modelValue":e[0]||(e[0]=t=>n.value=t),onChange:d},null,544),[[R,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),i.value?(o(),l("div",q,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",G,[s("p",null,c(m(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",H,[(o(!0),l($,null,B(r.value,t=>(o(),D(_(F),{key:t.serverId,class:"server-chart-card"},{header:v(()=>[s("div",J,[s("h3",null,"🖥️ "+c(t.serverName),1),s("span",K,c(m(n.value))+" 일별 에러 ("+c(k(t))+"건)",1)])]),default:v(()=>[s("div",P,[I(_(U),{data:C(t),options:S},null,8,["data"])])]),_:2},1024))),128))]))]))}},st=M(Q,[["__scopeId","data-v-7c1c78fe"]]);export{st as default};
|
import{_ as M,r as g,o as w,a as l,b as s,l as x,s as R,t as c,F as $,g as B,h as o,d as D,w as v,f as I,u as _}from"./index-DFoOAXeQ.js";import{a as N,C as F}from"./index--fsvNaiQ.js";import{C as A,a as L,L as E,B as T,p as V,b as Y,c as O,d as z,e as U}from"./chartjs-plugin-datalabels.esm-B8xtR40N.js";const W={class:"monthly-stats"},Z={class:"page-header"},j={class:"filter-section"},q={key:0,class:"loading"},G={key:1,class:"no-data"},H={key:2,class:"server-charts"},J={class:"chart-header"},K={class:"chart-subtitle"},P={class:"chart-container"},Q={__name:"MonthlyStats",setup(X){A.register(L,E,T,V,Y,O,z);const i=g(!1),r=g([]),n=g(f());function f(){const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}function b(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e-2,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}function y(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:a=>{if(a.datasetIndex!==2)return!1;const e=a.chart.data.datasets,t=a.dataIndex;return e.reduce((p,h)=>p+(h.data[t]||0),0)>0},anchor:"end",align:"end",offset:2,font:{size:10,weight:"bold"},color:"#666",formatter:(a,e)=>{const t=e.chart.data.datasets,u=e.dataIndex;return t.reduce((p,h)=>p+(h.data[u]||0),0)}}},scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0}}},C=a=>({labels:a.dailyStats.map(t=>`${new Date(t.date).getDate()}일`),datasets:[{label:"CRITICAL",data:a.dailyStats.map(t=>t.critical),backgroundColor:"#9b59b6",borderRadius:2},{label:"ERROR",data:a.dailyStats.map(t=>t.error),backgroundColor:"#e74c3c",borderRadius:2},{label:"WARN",data:a.dailyStats.map(t=>t.warn),backgroundColor:"#f39c12",borderRadius:2}]}),d=async()=>{i.value=!0;try{const[a,e]=n.value.split("-").map(Number);r.value=await N.getMonthlyStatsByServer(a,e)}catch(a){console.error("Failed to load stats:",a),r.value=[]}finally{i.value=!1}},m=a=>{const[e,t]=a.split("-");return`${e}년 ${parseInt(t)}월`},k=a=>a.dailyStats.reduce((e,t)=>e+t.total,0);return w(()=>{d()}),(a,e)=>(o(),l("div",W,[s("div",Z,[e[1]||(e[1]=s("h2",null,"월별 에러현황",-1)),s("div",j,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),x(s("input",{type:"month","onUpdate:modelValue":e[0]||(e[0]=t=>n.value=t),onChange:d},null,544),[[R,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),i.value?(o(),l("div",q,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",G,[s("p",null,c(m(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",H,[(o(!0),l($,null,B(r.value,t=>(o(),D(_(F),{key:t.serverId,class:"server-chart-card"},{header:v(()=>[s("div",J,[s("h3",null,"🖥️ "+c(t.serverName),1),s("span",K,c(m(n.value))+" 일별 에러 ("+c(k(t))+"건)",1)])]),default:v(()=>[s("div",P,[I(_(U),{data:C(t),options:S},null,8,["data"])])]),_:2},1024))),128))]))]))}},st=M(Q,[["__scopeId","data-v-7c1c78fe"]]);export{st as default};
|
||||||
@@ -1 +0,0 @@
|
|||||||
.card-header-content[data-v-fc6fccb5]{display:flex;justify-content:space-between;align-items:center}.card-header-content h3[data-v-fc6fccb5]{margin:0}.action-buttons[data-v-fc6fccb5]{display:flex;gap:4px}.regex-code[data-v-fc6fccb5]{font-family:monospace;background:#f1f3f4;padding:2px 6px;border-radius:3px;font-size:12px}.form-group[data-v-fc6fccb5]{margin-bottom:16px}.form-group label[data-v-fc6fccb5]{display:flex;align-items:center;gap:8px;cursor:pointer}.test-section[data-v-fc6fccb5]{display:flex;flex-direction:column;gap:16px}.test-pattern label[data-v-fc6fccb5]{display:block;font-weight:500;margin-bottom:6px}.regex-display[data-v-fc6fccb5]{display:block;font-family:monospace;background:#f8f9fa;padding:12px;border-radius:4px;font-size:13px;word-break:break-all}.test-result[data-v-fc6fccb5]{padding:16px;border-radius:8px;margin-top:8px}.test-result.success[data-v-fc6fccb5]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-fc6fccb5]{background:#f8d7da;border:1px solid #f5c6cb}.test-result h4[data-v-fc6fccb5]{margin:0 0 12px}.test-result p[data-v-fc6fccb5]{margin:0}.match-info[data-v-fc6fccb5]{margin-top:8px}.match-info label[data-v-fc6fccb5]{font-weight:500;margin-right:8px}.match-info code[data-v-fc6fccb5]{background:#0000001a;padding:2px 6px;border-radius:3px}.error-msg[data-v-fc6fccb5]{color:#721c24}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.page-header[data-v-821062ae]{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.page-header h2[data-v-821062ae]{margin:0}.loading[data-v-821062ae],.empty-state[data-v-821062ae]{text-align:center;padding:60px;background:#fff;border-radius:8px;color:#666}.empty-state p[data-v-821062ae]{margin-bottom:16px}.pattern-grid[data-v-821062ae]{display:grid;grid-template-columns:repeat(auto-fill,minmax(400px,1fr));gap:20px}.pattern-card[data-v-821062ae]{transition:box-shadow .2s,transform .2s}.pattern-card[data-v-821062ae]:hover{box-shadow:0 4px 12px #00000026;transform:translateY(-2px)}.pattern-header[data-v-821062ae]{display:flex;justify-content:space-between;align-items:center}.pattern-title[data-v-821062ae]{display:flex;align-items:center;gap:10px}.pattern-title h4[data-v-821062ae]{margin:0;font-size:16px;font-weight:600}.pattern-body[data-v-821062ae]{padding:4px 0}.pattern-info[data-v-821062ae]{margin-bottom:12px}.pattern-info label[data-v-821062ae]{display:block;font-size:12px;color:#666;margin-bottom:4px}.regex-box[data-v-821062ae]{display:block;font-family:Consolas,Monaco,monospace;background:#f1f3f5;padding:10px 12px;border-radius:6px;font-size:13px;word-break:break-all;line-height:1.4;border-left:3px solid #3498db}.regex-box.exclude[data-v-821062ae]{border-left-color:#e67e22;background:#fef5e7}.pattern-meta[data-v-821062ae]{display:flex;flex-wrap:wrap;gap:12px;padding-top:8px;border-top:1px solid #eee;font-size:13px}.meta-item[data-v-821062ae]{display:flex;align-items:center;gap:4px}.meta-label[data-v-821062ae]{color:#888}.meta-value[data-v-821062ae]{font-weight:500;color:#333}.meta-item.description[data-v-821062ae]{flex-basis:100%;color:#666;font-style:italic}.pattern-actions[data-v-821062ae]{display:flex;gap:8px;padding-top:12px;border-top:1px solid #eee;margin-top:12px}.action-btn[data-v-821062ae]{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:10px 12px;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:500;transition:all .2s}.action-btn.test[data-v-821062ae]{background:#e8f4fd;color:#2980b9}.action-btn.test[data-v-821062ae]:hover{background:#d4e9f7}.action-btn.edit[data-v-821062ae]{background:#fef3e2;color:#d68910}.action-btn.edit[data-v-821062ae]:hover{background:#fce8c9}.action-btn.delete[data-v-821062ae]{background:#fdeaea;color:#c0392b}.action-btn.delete[data-v-821062ae]:hover{background:#f9d6d6}.form-group[data-v-821062ae]{margin-bottom:16px}.form-group label[data-v-821062ae]{display:flex;align-items:center;gap:8px;cursor:pointer}.test-section[data-v-821062ae]{display:flex;flex-direction:column;gap:16px}.test-pattern label[data-v-821062ae]{display:block;font-weight:500;margin-bottom:6px}.regex-display[data-v-821062ae]{display:block;font-family:monospace;background:#f8f9fa;padding:12px;border-radius:4px;font-size:13px;word-break:break-all}.test-result[data-v-821062ae]{padding:16px;border-radius:8px;margin-top:8px}.test-result.success[data-v-821062ae]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-821062ae]{background:#f8d7da;border:1px solid #f5c6cb}.test-result h4[data-v-821062ae]{margin:0 0 12px}.test-result p[data-v-821062ae]{margin:0}.match-info[data-v-821062ae]{margin-top:8px}.match-info label[data-v-821062ae]{font-weight:500;margin-right:8px}.match-info code[data-v-821062ae]{background:#0000001a;padding:2px 6px;border-radius:3px}.error-msg[data-v-821062ae]{color:#721c24}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.card-header-content[data-v-6f59a208]{display:flex;justify-content:space-between;align-items:center}.card-header-content h3[data-v-6f59a208]{margin:0}.action-buttons[data-v-6f59a208]{display:flex;gap:4px}.form-group[data-v-6f59a208]{margin-bottom:16px}.form-group label[data-v-6f59a208]{display:flex;align-items:center;gap:8px;cursor:pointer}.log-path-section h4[data-v-6f59a208]{margin:0 0 12px;font-size:14px;color:#333}.log-path-form[data-v-6f59a208]{padding:16px;background:#f8f9fa;border-radius:8px;margin-bottom:20px}.log-path-inputs[data-v-6f59a208]{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:12px}.log-path-list table[data-v-6f59a208]{width:100%;border-collapse:collapse}.log-path-list th[data-v-6f59a208],.log-path-list td[data-v-6f59a208]{padding:10px 12px;text-align:left;border-bottom:1px solid #eee}.log-path-list th[data-v-6f59a208]{background:#f8f9fa;font-weight:600;font-size:13px}.empty-text[data-v-6f59a208]{color:#6c757d;text-align:center;padding:20px}.warning-text[data-v-6f59a208]{color:#e74c3c;font-size:14px}.test-result[data-v-6f59a208]{padding:16px;border-radius:8px}.test-result.success[data-v-6f59a208]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-6f59a208]{background:#f8d7da;border:1px solid #f5c6cb}.test-result p[data-v-6f59a208]{margin:0}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as S,r as m,o as F,a as p,f as a,w as i,u as n,h as v,m as B,b as t,i as c}from"./index-BB0X_WMV.js";import{C as V,b}from"./index-09HB4Lmg.js";import{F as u}from"./FormInput-BwPgkOAn.js";import{B as y}from"./Button-CoXLXkk7.js";const k={class:"settings"},w={key:0,class:"loading"},C={class:"setting-section"},U={class:"setting-section"},h={class:"setting-section"},M={class:"setting-section"},z={class:"form-actions"},A={__name:"Settings",setup(N){const r=m(!1),d=m(!1),l=m({}),f={"server.port":"8080","export.path":"./exports","retention.days":"90","scan.timeout":"30","scan.maxFileSize":"100"},g=async()=>{r.value=!0;try{const o=await b.getAllAsMap();l.value={...f,...o}}catch(o){console.error("Failed to load settings:",o),l.value={...f}}finally{r.value=!1}},x=async()=>{d.value=!0;try{for(const[o,e]of Object.entries(l.value))await b.save({key:o,value:String(e)});alert("설정이 저장되었습니다.")}catch(o){console.error("Failed to save settings:",o),alert("설정 저장에 실패했습니다.")}finally{d.value=!1}};return F(()=>{g()}),(o,e)=>(v(),p("div",k,[a(n(V),null,{header:i(()=>[...e[5]||(e[5]=[t("div",{class:"card-header-content"},[t("h3",null,"설정")],-1)])]),default:i(()=>[r.value?(v(),p("div",w,"로딩중...")):(v(),p("form",{key:1,onSubmit:B(x,["prevent"]),class:"settings-form"},[t("div",C,[e[6]||(e[6]=t("h4",null,"일반 설정",-1)),a(n(u),{modelValue:l.value["server.port"],"onUpdate:modelValue":e[0]||(e[0]=s=>l.value["server.port"]=s),label:"서버 포트",type:"number",hint:"애플리케이션이 실행될 포트 번호 (기본: 8080)"},null,8,["modelValue"])]),t("div",U,[e[7]||(e[7]=t("h4",null,"내보내기 설정",-1)),a(n(u),{modelValue:l.value["export.path"],"onUpdate:modelValue":e[1]||(e[1]=s=>l.value["export.path"]=s),label:"내보내기 경로",placeholder:"예: C:\\LogHunter\\exports",hint:"리포트 파일이 저장될 기본 경로"},null,8,["modelValue"])]),t("div",h,[e[8]||(e[8]=t("h4",null,"데이터 관리",-1)),a(n(u),{modelValue:l.value["retention.days"],"onUpdate:modelValue":e[2]||(e[2]=s=>l.value["retention.days"]=s),label:"로그 보관 기간 (일)",type:"number",hint:"에러 로그 데이터 보관 기간 (0 = 무제한)"},null,8,["modelValue"])]),t("div",M,[e[9]||(e[9]=t("h4",null,"스캔 설정",-1)),a(n(u),{modelValue:l.value["scan.timeout"],"onUpdate:modelValue":e[3]||(e[3]=s=>l.value["scan.timeout"]=s),label:"스캔 타임아웃 (초)",type:"number",hint:"SFTP 연결 및 파일 다운로드 타임아웃"},null,8,["modelValue"]),a(n(u),{modelValue:l.value["scan.maxFileSize"],"onUpdate:modelValue":e[4]||(e[4]=s=>l.value["scan.maxFileSize"]=s),label:"최대 파일 크기 (MB)",type:"number",hint:"분석할 로그 파일의 최대 크기"},null,8,["modelValue"])]),t("div",z,[a(n(y),{onClick:g,variant:"secondary"},{default:i(()=>[...e[10]||(e[10]=[c("초기화",-1)])]),_:1}),a(n(y),{type:"submit",loading:d.value},{default:i(()=>[...e[11]||(e[11]=[c("저장",-1)])]),_:1},8,["loading"])])],32))]),_:1}),a(n(V),{class:"app-info"},{header:i(()=>[...e[12]||(e[12]=[t("h3",null,"애플리케이션 정보",-1)])]),default:i(()=>[e[13]||(e[13]=t("div",{class:"info-list"},[t("div",{class:"info-item"},[t("span",{class:"label"},"버전"),t("span",{class:"value"},"1.0.0")]),t("div",{class:"info-item"},[t("span",{class:"label"},"프레임워크"),t("span",{class:"value"},"Spring Boot 3.2 + Vue 3")]),t("div",{class:"info-item"},[t("span",{class:"label"},"데이터베이스"),t("span",{class:"value"},"SQLite (./data/loghunter.db)")])],-1))]),_:1})]))}},j=S(A,[["__scopeId","data-v-fdca948e"]]);export{j as default};
|
import{_ as S,r as m,o as F,a as p,f as a,w as i,u as n,h as v,m as B,b as t,i as c}from"./index-DFoOAXeQ.js";import{C as V,b}from"./index--fsvNaiQ.js";import{F as u}from"./FormInput-BsW78DWl.js";import{B as y}from"./Button-Ddldcbk7.js";const k={class:"settings"},w={key:0,class:"loading"},C={class:"setting-section"},U={class:"setting-section"},h={class:"setting-section"},M={class:"setting-section"},z={class:"form-actions"},A={__name:"Settings",setup(N){const r=m(!1),d=m(!1),l=m({}),f={"server.port":"8080","export.path":"./exports","retention.days":"90","scan.timeout":"30","scan.maxFileSize":"100"},g=async()=>{r.value=!0;try{const o=await b.getAllAsMap();l.value={...f,...o}}catch(o){console.error("Failed to load settings:",o),l.value={...f}}finally{r.value=!1}},x=async()=>{d.value=!0;try{for(const[o,e]of Object.entries(l.value))await b.save({key:o,value:String(e)});alert("설정이 저장되었습니다.")}catch(o){console.error("Failed to save settings:",o),alert("설정 저장에 실패했습니다.")}finally{d.value=!1}};return F(()=>{g()}),(o,e)=>(v(),p("div",k,[a(n(V),null,{header:i(()=>[...e[5]||(e[5]=[t("div",{class:"card-header-content"},[t("h3",null,"설정")],-1)])]),default:i(()=>[r.value?(v(),p("div",w,"로딩중...")):(v(),p("form",{key:1,onSubmit:B(x,["prevent"]),class:"settings-form"},[t("div",C,[e[6]||(e[6]=t("h4",null,"일반 설정",-1)),a(n(u),{modelValue:l.value["server.port"],"onUpdate:modelValue":e[0]||(e[0]=s=>l.value["server.port"]=s),label:"서버 포트",type:"number",hint:"애플리케이션이 실행될 포트 번호 (기본: 8080)"},null,8,["modelValue"])]),t("div",U,[e[7]||(e[7]=t("h4",null,"내보내기 설정",-1)),a(n(u),{modelValue:l.value["export.path"],"onUpdate:modelValue":e[1]||(e[1]=s=>l.value["export.path"]=s),label:"내보내기 경로",placeholder:"예: C:\\LogHunter\\exports",hint:"리포트 파일이 저장될 기본 경로"},null,8,["modelValue"])]),t("div",h,[e[8]||(e[8]=t("h4",null,"데이터 관리",-1)),a(n(u),{modelValue:l.value["retention.days"],"onUpdate:modelValue":e[2]||(e[2]=s=>l.value["retention.days"]=s),label:"로그 보관 기간 (일)",type:"number",hint:"에러 로그 데이터 보관 기간 (0 = 무제한)"},null,8,["modelValue"])]),t("div",M,[e[9]||(e[9]=t("h4",null,"스캔 설정",-1)),a(n(u),{modelValue:l.value["scan.timeout"],"onUpdate:modelValue":e[3]||(e[3]=s=>l.value["scan.timeout"]=s),label:"스캔 타임아웃 (초)",type:"number",hint:"SFTP 연결 및 파일 다운로드 타임아웃"},null,8,["modelValue"]),a(n(u),{modelValue:l.value["scan.maxFileSize"],"onUpdate:modelValue":e[4]||(e[4]=s=>l.value["scan.maxFileSize"]=s),label:"최대 파일 크기 (MB)",type:"number",hint:"분석할 로그 파일의 최대 크기"},null,8,["modelValue"])]),t("div",z,[a(n(y),{onClick:g,variant:"secondary"},{default:i(()=>[...e[10]||(e[10]=[c("초기화",-1)])]),_:1}),a(n(y),{type:"submit",loading:d.value},{default:i(()=>[...e[11]||(e[11]=[c("저장",-1)])]),_:1},8,["loading"])])],32))]),_:1}),a(n(V),{class:"app-info"},{header:i(()=>[...e[12]||(e[12]=[t("h3",null,"애플리케이션 정보",-1)])]),default:i(()=>[e[13]||(e[13]=t("div",{class:"info-list"},[t("div",{class:"info-item"},[t("span",{class:"label"},"버전"),t("span",{class:"value"},"1.0.0")]),t("div",{class:"info-item"},[t("span",{class:"label"},"프레임워크"),t("span",{class:"value"},"Spring Boot 3.2 + Vue 3")]),t("div",{class:"info-item"},[t("span",{class:"label"},"데이터베이스"),t("span",{class:"value"},"SQLite (./data/loghunter.db)")])],-1))]),_:1})]))}},j=S(A,[["__scopeId","data-v-fdca948e"]]);export{j as default};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var Yn=Object.defineProperty;var Un=(i,t,e)=>t in i?Yn(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var w=(i,t,e)=>Un(i,typeof t!="symbol"?t+"":t,e);import{x as Us,y as Xs,z as oi,A as Xn,r as Kn,o as qn,B as Gn,C as Zn,D as ri,E as Ks,p as Qn}from"./index-BB0X_WMV.js";/*!
|
var Yn=Object.defineProperty;var Un=(i,t,e)=>t in i?Yn(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var w=(i,t,e)=>Un(i,typeof t!="symbol"?t+"":t,e);import{x as Us,y as Xs,z as oi,A as Xn,r as Kn,o as qn,B as Gn,C as Zn,D as ri,E as Ks,p as Qn}from"./index-DFoOAXeQ.js";/*!
|
||||||
* @kurkle/color v0.3.4
|
* @kurkle/color v0.3.4
|
||||||
* https://github.com/kurkle/color#readme
|
* https://github.com/kurkle/color#readme
|
||||||
* (c) 2024 Jukka Kurkela
|
* (c) 2024 Jukka Kurkela
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-BB0X_WMV.js"></script>
|
<script type="module" crossorigin src="/assets/index-DFoOAXeQ.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BJ4-9mFZ.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BJ4-9mFZ.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,39 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pattern-manage">
|
<div class="pattern-manage">
|
||||||
<Card>
|
<div class="page-header">
|
||||||
<template #header>
|
<h2>패턴 관리</h2>
|
||||||
<div class="card-header-content">
|
<Button @click="openAddModal">+ 패턴 추가</Button>
|
||||||
<h3>패턴 관리</h3>
|
</div>
|
||||||
<Button @click="openAddModal">+ 패턴 추가</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<DataTable
|
<div v-if="loading" class="loading">
|
||||||
:columns="columns"
|
<p>로딩중...</p>
|
||||||
:data="patterns"
|
</div>
|
||||||
:loading="loading"
|
|
||||||
empty-text="등록된 패턴이 없습니다."
|
<div v-else-if="patterns.length === 0" class="empty-state">
|
||||||
>
|
<p>등록된 패턴이 없습니다.</p>
|
||||||
<template #severity="{ value }">
|
<Button @click="openAddModal">첫 패턴 추가하기</Button>
|
||||||
<Badge :variant="getSeverityVariant(value)">{{ value }}</Badge>
|
</div>
|
||||||
</template>
|
|
||||||
<template #regex="{ value }">
|
<!-- 패턴 카드 그리드 -->
|
||||||
<code class="regex-code">{{ truncate(value, 50) }}</code>
|
<div v-else class="pattern-grid">
|
||||||
</template>
|
<Card v-for="pattern in patterns" :key="pattern.id" class="pattern-card">
|
||||||
<template #active="{ value }">
|
<template #header>
|
||||||
<Badge :variant="value ? 'success' : 'default'">
|
<div class="pattern-header">
|
||||||
{{ value ? '활성' : '비활성' }}
|
<div class="pattern-title">
|
||||||
</Badge>
|
<Badge :variant="getSeverityVariant(pattern.severity)" size="sm">
|
||||||
</template>
|
{{ pattern.severity }}
|
||||||
<template #actions="{ row }">
|
</Badge>
|
||||||
<div class="action-buttons">
|
<h4>{{ pattern.name }}</h4>
|
||||||
<Button size="sm" variant="secondary" @click="openTestModal(row)">테스트</Button>
|
</div>
|
||||||
<Button size="sm" @click="openEditModal(row)">수정</Button>
|
<Badge :variant="pattern.active ? 'success' : 'default'" size="sm">
|
||||||
<Button size="sm" variant="danger" @click="confirmDelete(row)">삭제</Button>
|
{{ pattern.active ? '활성' : '비활성' }}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</DataTable>
|
|
||||||
</Card>
|
<div class="pattern-body">
|
||||||
|
<div class="pattern-info">
|
||||||
|
<label>정규식</label>
|
||||||
|
<code class="regex-box">{{ pattern.regex }}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="pattern.excludeRegex" class="pattern-info">
|
||||||
|
<label>제외 정규식</label>
|
||||||
|
<code class="regex-box exclude">{{ pattern.excludeRegex }}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pattern-meta">
|
||||||
|
<span class="meta-item">
|
||||||
|
<span class="meta-label">컨텍스트</span>
|
||||||
|
<span class="meta-value">{{ pattern.contextLines }}줄</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="pattern.description" class="meta-item description">
|
||||||
|
{{ pattern.description }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pattern-actions">
|
||||||
|
<button class="action-btn test" @click="openTestModal(pattern)" title="테스트">
|
||||||
|
🧪 테스트
|
||||||
|
</button>
|
||||||
|
<button class="action-btn edit" @click="openEditModal(pattern)" title="수정">
|
||||||
|
✏️ 수정
|
||||||
|
</button>
|
||||||
|
<button class="action-btn delete" @click="confirmDelete(pattern)" title="삭제">
|
||||||
|
🗑️ 삭제
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 패턴 추가/수정 모달 -->
|
<!-- 패턴 추가/수정 모달 -->
|
||||||
<Modal v-model="showPatternModal" :title="isEdit ? '패턴 수정' : '패턴 추가'" width="600px">
|
<Modal v-model="showPatternModal" :title="isEdit ? '패턴 수정' : '패턴 추가'" width="600px">
|
||||||
@@ -154,17 +186,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { DataTable, Modal, FormInput, Button, Badge, Card } from '@/components'
|
import { Modal, FormInput, Button, Badge, Card } from '@/components'
|
||||||
import { patternApi } from '@/api'
|
import { patternApi } from '@/api'
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ key: 'name', label: '패턴명', width: '150px' },
|
|
||||||
{ key: 'regex', label: '정규식' },
|
|
||||||
{ key: 'severity', label: '심각도', width: '100px' },
|
|
||||||
{ key: 'contextLines', label: '컨텍스트', width: '90px' },
|
|
||||||
{ key: 'active', label: '상태', width: '80px' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const severityOptions = [
|
const severityOptions = [
|
||||||
{ value: 'CRITICAL', label: 'CRITICAL' },
|
{ value: 'CRITICAL', label: 'CRITICAL' },
|
||||||
{ value: 'ERROR', label: 'ERROR' },
|
{ value: 'ERROR', label: 'ERROR' },
|
||||||
@@ -323,40 +347,184 @@ const getSeverityVariant = (severity) => {
|
|||||||
return map[severity] || 'default'
|
return map[severity] || 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
const truncate = (str, len) => {
|
|
||||||
if (!str) return ''
|
|
||||||
return str.length > len ? str.substring(0, len) + '...' : str
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadPatterns()
|
loadPatterns()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card-header-content {
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 패턴 그리드 */
|
||||||
|
.pattern-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-card {
|
||||||
|
transition: box-shadow 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-card:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-content h3 {
|
.pattern-title {
|
||||||
margin: 0;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.pattern-title h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-body {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-info {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-info label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regex-box {
|
||||||
|
display: block;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
background: #f1f3f5;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regex-box.exclude {
|
||||||
|
border-left-color: #e67e22;
|
||||||
|
background: #fef5e7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pattern-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.regex-code {
|
.meta-label {
|
||||||
font-family: monospace;
|
color: #888;
|
||||||
background: #f1f3f4;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-item.description {
|
||||||
|
flex-basis: 100%;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 액션 버튼 */
|
||||||
|
.pattern-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.test {
|
||||||
|
background: #e8f4fd;
|
||||||
|
color: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.test:hover {
|
||||||
|
background: #d4e9f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.edit {
|
||||||
|
background: #fef3e2;
|
||||||
|
color: #d68910;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.edit:hover {
|
||||||
|
background: #fce8c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.delete {
|
||||||
|
background: #fdeaea;
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.delete:hover {
|
||||||
|
background: #f9d6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 폼 */
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@@ -368,6 +536,7 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 테스트 섹션 */
|
||||||
.test-section {
|
.test-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -1,40 +1,105 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="server-manage">
|
<div class="server-manage">
|
||||||
<Card>
|
<div class="page-header">
|
||||||
<template #header>
|
<h2>서버 관리</h2>
|
||||||
<div class="card-header-content">
|
<Button @click="openAddModal">+ 서버 추가</Button>
|
||||||
<h3>서버 관리</h3>
|
</div>
|
||||||
<Button @click="openAddModal">+ 서버 추가</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<DataTable
|
<div v-if="loading" class="loading">
|
||||||
:columns="columns"
|
<p>로딩중...</p>
|
||||||
:data="servers"
|
</div>
|
||||||
:loading="loading"
|
|
||||||
empty-text="등록된 서버가 없습니다."
|
<div v-else-if="servers.length === 0" class="empty-state">
|
||||||
>
|
<p>등록된 서버가 없습니다.</p>
|
||||||
<template #active="{ value }">
|
<Button @click="openAddModal">첫 서버 추가하기</Button>
|
||||||
<Badge :variant="value ? 'success' : 'default'">
|
</div>
|
||||||
{{ value ? '활성' : '비활성' }}
|
|
||||||
</Badge>
|
<!-- 서버 카드 그리드 -->
|
||||||
</template>
|
<div v-else class="server-grid">
|
||||||
<template #authType="{ value }">
|
<Card v-for="server in servers" :key="server.id" class="server-card">
|
||||||
{{ value === 'PASSWORD' ? '비밀번호' : '키 파일' }}
|
<template #header>
|
||||||
</template>
|
<div class="server-header">
|
||||||
<template #lastScanAt="{ value }">
|
<div class="server-title">
|
||||||
{{ value ? formatDate(value) : '-' }}
|
<Badge :variant="server.active ? 'success' : 'default'" size="sm">
|
||||||
</template>
|
{{ server.active ? '활성' : '비활성' }}
|
||||||
<template #actions="{ row }">
|
</Badge>
|
||||||
<div class="action-buttons">
|
<h4>{{ server.name }}</h4>
|
||||||
<Button size="sm" variant="success" @click="testConnection(row)" :loading="testingId === row.id">테스트</Button>
|
</div>
|
||||||
<Button size="sm" variant="secondary" @click="openLogPathModal(row)">경로</Button>
|
|
||||||
<Button size="sm" @click="openEditModal(row)">수정</Button>
|
|
||||||
<Button size="sm" variant="danger" @click="confirmDelete(row)">삭제</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</DataTable>
|
|
||||||
</Card>
|
<div class="server-body">
|
||||||
|
<div class="server-info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-icon">🌐</span>
|
||||||
|
<div class="info-content">
|
||||||
|
<span class="info-label">호스트</span>
|
||||||
|
<span class="info-value">{{ server.host }}:{{ server.port }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-icon">👤</span>
|
||||||
|
<div class="info-content">
|
||||||
|
<span class="info-label">사용자</span>
|
||||||
|
<span class="info-value">{{ server.username }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-icon">🔑</span>
|
||||||
|
<div class="info-content">
|
||||||
|
<span class="info-label">인증방식</span>
|
||||||
|
<span class="info-value">{{ server.authType === 'PASSWORD' ? '비밀번호' : '키 파일' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-icon">📅</span>
|
||||||
|
<div class="info-content">
|
||||||
|
<span class="info-label">마지막 분석</span>
|
||||||
|
<span class="info-value">{{ server.lastScanAt ? formatDate(server.lastScanAt) : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 진행 상황 -->
|
||||||
|
<div v-if="progressMap[server.id]" class="progress-section">
|
||||||
|
<div class="progress-header">
|
||||||
|
<span class="status-text">{{ getStatusText(progressMap[server.id]) }}</span>
|
||||||
|
<Badge :variant="progressMap[server.id].status === 'RUNNING' ? 'warn' : (progressMap[server.id].status === 'SUCCESS' ? 'success' : 'error')">
|
||||||
|
{{ progressMap[server.id].status }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
:style="{ width: getProgressPercent(progressMap[server.id]) + '%' }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-details">
|
||||||
|
<span>파일: {{ progressMap[server.id].scannedFiles }} / {{ progressMap[server.id].totalFiles }}</span>
|
||||||
|
<span>에러: {{ progressMap[server.id].errorsFound }}건</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="server-actions">
|
||||||
|
<button class="action-btn scan" @click="scanServer(server)" :disabled="!server.active || scanningId === server.id">
|
||||||
|
{{ scanningId === server.id ? '⏳ 분석중...' : '▶️ 분석 실행' }}
|
||||||
|
</button>
|
||||||
|
<button class="action-btn test" @click="testConnection(server)" :disabled="testingId === server.id">
|
||||||
|
{{ testingId === server.id ? '⏳' : '🔌' }}
|
||||||
|
</button>
|
||||||
|
<button class="action-btn path" @click="openLogPathModal(server)">
|
||||||
|
📁
|
||||||
|
</button>
|
||||||
|
<button class="action-btn edit" @click="openEditModal(server)">
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button class="action-btn delete" @click="confirmDelete(server)">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 서버 추가/수정 모달 -->
|
<!-- 서버 추가/수정 모달 -->
|
||||||
<Modal v-model="showServerModal" :title="isEdit ? '서버 수정' : '서버 추가'" width="500px">
|
<Modal v-model="showServerModal" :title="isEdit ? '서버 수정' : '서버 추가'" width="500px">
|
||||||
@@ -106,10 +171,10 @@
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!-- 로그 경로 관리 모달 -->
|
<!-- 로그 경로 관리 모달 -->
|
||||||
<Modal v-model="showLogPathModal" :title="`로그 경로 관리 - ${selectedServer?.name || ''}`" width="700px">
|
<Modal v-model="showLogPathModal" :title="`로그 경로 관리 - ${selectedServer?.name || ''}`" width="750px">
|
||||||
<div class="log-path-section">
|
<div class="log-path-section">
|
||||||
<div class="log-path-form">
|
<div class="log-path-form">
|
||||||
<h4>경로 추가</h4>
|
<h4>➕ 경로 추가</h4>
|
||||||
<div class="log-path-inputs">
|
<div class="log-path-inputs">
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="logPathForm.path"
|
v-model="logPathForm.path"
|
||||||
@@ -119,7 +184,7 @@
|
|||||||
<FormInput
|
<FormInput
|
||||||
v-model="logPathForm.filePattern"
|
v-model="logPathForm.filePattern"
|
||||||
label="파일 패턴"
|
label="파일 패턴"
|
||||||
placeholder="예: *.log, catalina.*.log"
|
placeholder="예: *.log"
|
||||||
/>
|
/>
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="logPathForm.description"
|
v-model="logPathForm.description"
|
||||||
@@ -128,39 +193,36 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button size="sm" @click="addLogPath" :disabled="!logPathForm.path || !logPathForm.filePattern">
|
<Button size="sm" @click="addLogPath" :disabled="!logPathForm.path || !logPathForm.filePattern">
|
||||||
추가
|
경로 추가
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="log-path-list">
|
<div class="log-path-list">
|
||||||
<h4>등록된 경로</h4>
|
<h4>📂 등록된 경로 ({{ logPaths.length }}개)</h4>
|
||||||
<table v-if="logPaths.length > 0">
|
|
||||||
<thead>
|
<div v-if="logPaths.length === 0" class="empty-paths">
|
||||||
<tr>
|
등록된 경로가 없습니다.
|
||||||
<th>경로</th>
|
</div>
|
||||||
<th>파일 패턴</th>
|
|
||||||
<th>설명</th>
|
<div v-else class="path-cards">
|
||||||
<th>활성</th>
|
<div v-for="lp in logPaths" :key="lp.id" class="path-card">
|
||||||
<th></th>
|
<div class="path-info">
|
||||||
</tr>
|
<div class="path-main">
|
||||||
</thead>
|
<code class="path-value">{{ lp.path }}</code>
|
||||||
<tbody>
|
<span class="path-pattern">{{ lp.filePattern }}</span>
|
||||||
<tr v-for="lp in logPaths" :key="lp.id">
|
</div>
|
||||||
<td>{{ lp.path }}</td>
|
<div class="path-meta">
|
||||||
<td>{{ lp.filePattern }}</td>
|
<span v-if="lp.description" class="path-desc">{{ lp.description }}</span>
|
||||||
<td>{{ lp.description || '-' }}</td>
|
<Badge :variant="lp.active ? 'success' : 'default'" size="sm">
|
||||||
<td>
|
{{ lp.active ? '활성' : '비활성' }}
|
||||||
<Badge :variant="lp.active ? 'success' : 'default'">
|
|
||||||
{{ lp.active ? 'Y' : 'N' }}
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Button size="sm" variant="danger" @click="deleteLogPath(lp.id)">삭제</Button>
|
<button class="path-delete" @click="deleteLogPath(lp.id)" title="삭제">
|
||||||
</td>
|
🗑️
|
||||||
</tr>
|
</button>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
<p v-else class="empty-text">등록된 경로가 없습니다.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -171,7 +233,7 @@
|
|||||||
<!-- 삭제 확인 모달 -->
|
<!-- 삭제 확인 모달 -->
|
||||||
<Modal v-model="showDeleteModal" title="서버 삭제" width="400px">
|
<Modal v-model="showDeleteModal" title="서버 삭제" width="400px">
|
||||||
<p>정말로 <strong>{{ deleteTarget?.name }}</strong> 서버를 삭제하시겠습니까?</p>
|
<p>정말로 <strong>{{ deleteTarget?.name }}</strong> 서버를 삭제하시겠습니까?</p>
|
||||||
<p class="warning-text">관련된 모든 로그 경로와 에러 이력도 함께 삭제됩니다.</p>
|
<p class="warning-text">⚠️ 관련된 모든 로그 경로와 에러 이력도 함께 삭제됩니다.</p>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<Button variant="secondary" @click="showDeleteModal = false">취소</Button>
|
<Button variant="secondary" @click="showDeleteModal = false">취소</Button>
|
||||||
<Button variant="danger" @click="deleteServer" :loading="deleting">삭제</Button>
|
<Button variant="danger" @click="deleteServer" :loading="deleting">삭제</Button>
|
||||||
@@ -197,17 +259,8 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { DataTable, Modal, FormInput, Button, Badge, Card } from '@/components'
|
import { Modal, FormInput, Button, Badge, Card } from '@/components'
|
||||||
import { serverApi, logPathApi } from '@/api'
|
import { serverApi, logPathApi, scanApi } from '@/api'
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{ key: 'name', label: '서버명', width: '150px' },
|
|
||||||
{ key: 'host', label: '호스트' },
|
|
||||||
{ key: 'port', label: '포트', width: '80px' },
|
|
||||||
{ key: 'authType', label: '인증방식', width: '100px' },
|
|
||||||
{ key: 'active', label: '상태', width: '80px' },
|
|
||||||
{ key: 'lastScanAt', label: '마지막 분석', width: '150px' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const authTypeOptions = [
|
const authTypeOptions = [
|
||||||
{ value: 'PASSWORD', label: '비밀번호' },
|
{ value: 'PASSWORD', label: '비밀번호' },
|
||||||
@@ -250,6 +303,10 @@ const logPathForm = ref({
|
|||||||
const showDeleteModal = ref(false)
|
const showDeleteModal = ref(false)
|
||||||
const deleteTarget = ref(null)
|
const deleteTarget = ref(null)
|
||||||
|
|
||||||
|
// Scan
|
||||||
|
const scanningId = ref(null)
|
||||||
|
const progressMap = ref({})
|
||||||
|
|
||||||
// Test Connection
|
// Test Connection
|
||||||
const testingId = ref(null)
|
const testingId = ref(null)
|
||||||
const showTestResultModal = ref(false)
|
const showTestResultModal = ref(false)
|
||||||
@@ -348,6 +405,68 @@ const deleteServer = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scan Server
|
||||||
|
const scanServer = (server) => {
|
||||||
|
scanningId.value = server.id
|
||||||
|
progressMap.value[server.id] = {
|
||||||
|
status: 'RUNNING',
|
||||||
|
currentPath: '',
|
||||||
|
currentFile: '',
|
||||||
|
totalFiles: 0,
|
||||||
|
scannedFiles: 0,
|
||||||
|
errorsFound: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
scanApi.startWithProgress(
|
||||||
|
server.id,
|
||||||
|
(progress) => {
|
||||||
|
progressMap.value[server.id] = progress
|
||||||
|
},
|
||||||
|
(result) => {
|
||||||
|
scanningId.value = null
|
||||||
|
if (result.success) {
|
||||||
|
progressMap.value[server.id] = {
|
||||||
|
...progressMap.value[server.id],
|
||||||
|
status: 'SUCCESS',
|
||||||
|
message: `완료: ${result.filesScanned}개 파일, ${result.errorsFound}개 에러`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
progressMap.value[server.id] = {
|
||||||
|
...progressMap.value[server.id],
|
||||||
|
status: 'FAILED',
|
||||||
|
message: result.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadServers()
|
||||||
|
// 5초 후 진행상황 제거
|
||||||
|
setTimeout(() => {
|
||||||
|
delete progressMap.value[server.id]
|
||||||
|
}, 5000)
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
scanningId.value = null
|
||||||
|
progressMap.value[server.id] = {
|
||||||
|
...progressMap.value[server.id],
|
||||||
|
status: 'FAILED',
|
||||||
|
message: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (progress) => {
|
||||||
|
if (progress.status === 'SUCCESS') return progress.message || '완료'
|
||||||
|
if (progress.status === 'FAILED') return progress.message || '실패'
|
||||||
|
if (progress.currentFile) return `분석중: ${progress.currentFile}`
|
||||||
|
if (progress.currentPath) return `경로: ${progress.currentPath}`
|
||||||
|
return '준비중...'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProgressPercent = (progress) => {
|
||||||
|
if (progress.totalFiles === 0) return 0
|
||||||
|
return Math.round((progress.scannedFiles / progress.totalFiles) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
// Test Connection
|
// Test Connection
|
||||||
const testConnection = async (server) => {
|
const testConnection = async (server) => {
|
||||||
testingId.value = server.id
|
testingId.value = server.id
|
||||||
@@ -415,21 +534,236 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card-header-content {
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 서버 그리드 */
|
||||||
|
.server-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card {
|
||||||
|
transition: box-shadow 0.2s, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-card:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-content h3 {
|
.server-title {
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.server-title h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-body {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 진행 상황 */
|
||||||
|
.progress-section {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
height: 8px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: #3498db;
|
||||||
|
transition: width 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #888;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 액션 버튼 */
|
||||||
|
.server-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.scan {
|
||||||
|
flex: 1.5;
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.scan:hover:not(:disabled) {
|
||||||
|
background: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.scan:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.test {
|
||||||
|
flex: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #e8f8f0;
|
||||||
|
color: #27ae60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.test:hover:not(:disabled) {
|
||||||
|
background: #d4f0e3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.test:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.path {
|
||||||
|
flex: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #e8f4fd;
|
||||||
|
color: #2980b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.path:hover {
|
||||||
|
background: #d4e9f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.edit {
|
||||||
|
flex: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fef3e2;
|
||||||
|
color: #d68910;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.edit:hover {
|
||||||
|
background: #fce8c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.delete {
|
||||||
|
flex: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #fdeaea;
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.delete:hover {
|
||||||
|
background: #f9d6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 폼 */
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@@ -441,6 +775,7 @@ onMounted(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 로그 경로 섹션 */
|
||||||
.log-path-section h4 {
|
.log-path-section h4 {
|
||||||
margin: 0 0 12px 0;
|
margin: 0 0 12px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -456,33 +791,90 @@ onMounted(() => {
|
|||||||
|
|
||||||
.log-path-inputs {
|
.log-path-inputs {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1.5fr 1fr 1fr;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-path-list table {
|
.log-path-list {
|
||||||
width: 100%;
|
padding: 16px;
|
||||||
border-collapse: collapse;
|
background: white;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-path-list th,
|
.empty-paths {
|
||||||
.log-path-list td {
|
|
||||||
padding: 10px 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-path-list th {
|
|
||||||
background: #f8f9fa;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
color: #6c757d;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-value {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #e9ecef;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-pattern {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
background: #fff3cd;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-delete {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-delete:hover {
|
||||||
|
background: #fdeaea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-text {
|
.warning-text {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package research.loghunter.service;
|
package research.loghunter.service;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -7,6 +8,10 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import research.loghunter.entity.*;
|
import research.loghunter.entity.*;
|
||||||
import research.loghunter.repository.*;
|
import research.loghunter.repository.*;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
@@ -32,6 +37,8 @@ public class ScanService {
|
|||||||
private final ScannedFileRepository scannedFileRepository;
|
private final ScannedFileRepository scannedFileRepository;
|
||||||
private final SftpService sftpService;
|
private final SftpService sftpService;
|
||||||
private final LogParserService logParserService;
|
private final LogParserService logParserService;
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
// 진행 상황 저장 (serverId -> ScanProgress)
|
// 진행 상황 저장 (serverId -> ScanProgress)
|
||||||
private final ConcurrentHashMap<Long, ScanProgress> progressMap = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<Long, ScanProgress> progressMap = new ConcurrentHashMap<>();
|
||||||
@@ -241,6 +248,8 @@ public class ScanService {
|
|||||||
return new ScanResult(server.getId(), server.getName(), false, 0, 0, e.getMessage());
|
return new ScanResult(server.getId(), server.getName(), false, 0, 0, e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
progressMap.remove(server.getId());
|
progressMap.remove(server.getId());
|
||||||
|
// DB 최적화 (삭제된 데이터 공간 회수)
|
||||||
|
vacuumDatabase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +409,23 @@ public class ScanService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite VACUUM 실행 (DB 최적화)
|
||||||
|
* 삭제된 데이터가 차지하는 공간을 회수함
|
||||||
|
*/
|
||||||
|
private void vacuumDatabase() {
|
||||||
|
try (Connection conn = dataSource.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
log.info("Running VACUUM to optimize database...");
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
stmt.execute("VACUUM");
|
||||||
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
log.info("VACUUM completed in {}ms", elapsed);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to vacuum database: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DTOs
|
// DTOs
|
||||||
public static class ScanProgress {
|
public static class ScanProgress {
|
||||||
private Long serverId;
|
private Long serverId;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as e,a as s,h as r,G as n,i as o,t as c,n as d}from"./index-BB0X_WMV.js";const l={__name:"Badge",props:{text:String,variant:{type:String,default:"default"}},setup(a){return(t,i)=>(r(),s("span",{class:d(["badge",`badge-${a.variant}`])},[n(t.$slots,"default",{},()=>[o(c(a.text),1)])],2))}},g=e(l,[["__scopeId","data-v-b7bd2350"]]);export{g as B};
|
import"./index--fsvNaiQ.js";import{_ as e,a as s,h as r,G as n,i as o,t as c,n as d}from"./index-DFoOAXeQ.js";const l={__name:"Badge",props:{text:String,variant:{type:String,default:"default"}},setup(a){return(t,i)=>(r(),s("span",{class:d(["badge",`badge-${a.variant}`])},[n(t.$slots,"default",{},()=>[o(c(a.text),1)])],2))}},g=e(l,[["__scopeId","data-v-b7bd2350"]]);export{g as B};
|
||||||
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as d,r,a,e as c,G as u,n as f,h as o}from"./index-BB0X_WMV.js";const m=["type","disabled"],b={key:0,class:"spinner"},y={__name:"Button",props:{type:{type:String,default:"button"},variant:{type:String,default:"primary"},size:{type:String,default:"md"},disabled:Boolean,loading:Boolean},emits:["click"],setup(e,{expose:l}){const n=r(null);return l({focus:()=>{var t;(t=n.value)==null||t.focus()}}),(t,s)=>(o(),a("button",{ref_key:"buttonRef",ref:n,type:e.type,class:f(["btn",`btn-${e.variant}`,{"btn-sm":e.size==="sm","btn-lg":e.size==="lg"}]),disabled:e.disabled||e.loading,onClick:s[0]||(s[0]=i=>t.$emit("click",i))},[e.loading?(o(),a("span",b)):c("",!0),u(t.$slots,"default",{},void 0)],10,m))}},B=d(y,[["__scopeId","data-v-c92354e1"]]);export{B};
|
import"./index--fsvNaiQ.js";import{_ as d,r,a,e as c,G as u,n as f,h as o}from"./index-DFoOAXeQ.js";const m=["type","disabled"],b={key:0,class:"spinner"},y={__name:"Button",props:{type:{type:String,default:"button"},variant:{type:String,default:"primary"},size:{type:String,default:"md"},disabled:Boolean,loading:Boolean},emits:["click"],setup(e,{expose:l}){const n=r(null);return l({focus:()=>{var t;(t=n.value)==null||t.focus()}}),(t,s)=>(o(),a("button",{ref_key:"buttonRef",ref:n,type:e.type,class:f(["btn",`btn-${e.variant}`,{"btn-sm":e.size==="sm","btn-lg":e.size==="lg"}]),disabled:e.disabled||e.loading,onClick:s[0]||(s[0]=i=>t.$emit("click",i))},[e.loading?(o(),a("span",b)):c("",!0),u(t.$slots,"default",{},void 0)],10,m))}},B=d(y,[["__scopeId","data-v-c92354e1"]]);export{B};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as C,r as g,o as x,a as l,b as s,l as w,s as I,t as d,F as B,g as T,h as o,d as R,w as m,f as L,u as _}from"./index-BB0X_WMV.js";import{a as A,C as F}from"./index-09HB4Lmg.js";import{C as O,a as W,L as N,B as V,p as E,b as M,c as $,d as P,e as z}from"./chartjs-plugin-datalabels.esm-DiDzp_cw.js";const U={class:"daily-stats"},Y={class:"page-header"},Z={class:"filter-section"},j={key:0,class:"loading"},q={key:1,class:"no-data"},G={key:2,class:"server-charts"},H={class:"chart-header"},J={class:"chart-subtitle"},K={class:"chart-wrapper"},Q={class:"chart-container"},X={__name:"DailyStats",setup(tt){O.register(W,N,V,E,M,$,P);const c=g(!1),r=g([]),n=g(f());function f(){return new Date().toISOString().split("T")[0]}function b(){const t=new Date(n.value);t.setDate(t.getDate()-1),n.value=t.toISOString().split("T")[0],i()}function y(){const t=new Date(n.value);t.setDate(t.getDate()+1),n.value=t.toISOString().split("T")[0],i()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:t=>{if(t.datasetIndex!==2)return!1;const e=t.chart.data.datasets,a=t.dataIndex;return e.reduce((u,p)=>u+(p.data[a]||0),0)>0},anchor:"end",align:"end",offset:0,font:{size:9},color:"#666",formatter:(t,e)=>{const a=e.chart.data.datasets,v=e.dataIndex;return a.reduce((u,p)=>u+(p.data[v]||0),0)}}},scales:{x:{stacked:!0,ticks:{maxRotation:0,autoSkip:!0,maxTicksLimit:24,callback:function(t,e){const a=this.getLabelForValue(t);return a&&a.endsWith(":00")?a:""}},grid:{display:!1}},y:{stacked:!0,beginAtZero:!0}},barPercentage:.8,categoryPercentage:.9},D=t=>({labels:t.timeStats.map(a=>a.time),datasets:[{label:"CRITICAL",data:t.timeStats.map(a=>a.critical),backgroundColor:"#9b59b6",borderWidth:0},{label:"ERROR",data:t.timeStats.map(a=>a.error),backgroundColor:"#e74c3c",borderWidth:0},{label:"WARN",data:t.timeStats.map(a=>a.warn),backgroundColor:"#f39c12",borderWidth:0}]}),i=async()=>{c.value=!0;try{r.value=await A.getTimeStatsByServer(n.value,15)}catch(t){console.error("Failed to load stats:",t),r.value=[]}finally{c.value=!1}},h=t=>{const e=new Date(t);return`${e.getFullYear()}년 ${e.getMonth()+1}월 ${e.getDate()}일`},k=t=>t.timeStats.reduce((e,a)=>e+a.total,0);return x(()=>{i()}),(t,e)=>(o(),l("div",U,[s("div",Y,[e[1]||(e[1]=s("h2",null,"일별 에러현황",-1)),s("div",Z,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),w(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=a=>n.value=a),onChange:i},null,544),[[I,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),c.value?(o(),l("div",j,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",q,[s("p",null,d(h(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",G,[(o(!0),l(B,null,T(r.value,a=>(o(),R(_(F),{key:a.serverId,class:"server-chart-card"},{header:m(()=>[s("div",H,[s("h3",null,"🖥️ "+d(a.serverName),1),s("span",J,d(h(n.value))+" 15분 단위 에러 ("+d(k(a))+"건)",1)])]),default:m(()=>[s("div",K,[s("div",Q,[L(_(z),{data:D(a),options:S},null,8,["data"])])])]),_:2},1024))),128))]))]))}},nt=C(X,[["__scopeId","data-v-b7241be8"]]);export{nt as default};
|
import{_ as C,r as g,o as x,a as l,b as s,l as w,s as I,t as d,F as B,g as T,h as o,d as R,w as m,f as L,u as _}from"./index-DFoOAXeQ.js";import{a as A,C as F}from"./index--fsvNaiQ.js";import{C as O,a as W,L as N,B as V,p as E,b as M,c as $,d as P,e as z}from"./chartjs-plugin-datalabels.esm-B8xtR40N.js";const U={class:"daily-stats"},Y={class:"page-header"},Z={class:"filter-section"},j={key:0,class:"loading"},q={key:1,class:"no-data"},G={key:2,class:"server-charts"},H={class:"chart-header"},J={class:"chart-subtitle"},K={class:"chart-wrapper"},Q={class:"chart-container"},X={__name:"DailyStats",setup(tt){O.register(W,N,V,E,M,$,P);const c=g(!1),r=g([]),n=g(f());function f(){return new Date().toISOString().split("T")[0]}function b(){const t=new Date(n.value);t.setDate(t.getDate()-1),n.value=t.toISOString().split("T")[0],i()}function y(){const t=new Date(n.value);t.setDate(t.getDate()+1),n.value=t.toISOString().split("T")[0],i()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:t=>{if(t.datasetIndex!==2)return!1;const e=t.chart.data.datasets,a=t.dataIndex;return e.reduce((u,p)=>u+(p.data[a]||0),0)>0},anchor:"end",align:"end",offset:0,font:{size:9},color:"#666",formatter:(t,e)=>{const a=e.chart.data.datasets,v=e.dataIndex;return a.reduce((u,p)=>u+(p.data[v]||0),0)}}},scales:{x:{stacked:!0,ticks:{maxRotation:0,autoSkip:!0,maxTicksLimit:24,callback:function(t,e){const a=this.getLabelForValue(t);return a&&a.endsWith(":00")?a:""}},grid:{display:!1}},y:{stacked:!0,beginAtZero:!0}},barPercentage:.8,categoryPercentage:.9},D=t=>({labels:t.timeStats.map(a=>a.time),datasets:[{label:"CRITICAL",data:t.timeStats.map(a=>a.critical),backgroundColor:"#9b59b6",borderWidth:0},{label:"ERROR",data:t.timeStats.map(a=>a.error),backgroundColor:"#e74c3c",borderWidth:0},{label:"WARN",data:t.timeStats.map(a=>a.warn),backgroundColor:"#f39c12",borderWidth:0}]}),i=async()=>{c.value=!0;try{r.value=await A.getTimeStatsByServer(n.value,15)}catch(t){console.error("Failed to load stats:",t),r.value=[]}finally{c.value=!1}},h=t=>{const e=new Date(t);return`${e.getFullYear()}년 ${e.getMonth()+1}월 ${e.getDate()}일`},k=t=>t.timeStats.reduce((e,a)=>e+a.total,0);return x(()=>{i()}),(t,e)=>(o(),l("div",U,[s("div",Y,[e[1]||(e[1]=s("h2",null,"일별 에러현황",-1)),s("div",Z,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),w(s("input",{type:"date","onUpdate:modelValue":e[0]||(e[0]=a=>n.value=a),onChange:i},null,544),[[I,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),c.value?(o(),l("div",j,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",q,[s("p",null,d(h(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",G,[(o(!0),l(B,null,T(r.value,a=>(o(),R(_(F),{key:a.serverId,class:"server-chart-card"},{header:m(()=>[s("div",H,[s("h3",null,"🖥️ "+d(a.serverName),1),s("span",J,d(h(n.value))+" 15분 단위 에러 ("+d(k(a))+"건)",1)])]),default:m(()=>[s("div",K,[s("div",Q,[L(_(z),{data:D(a),options:S},null,8,["data"])])])]),_:2},1024))),128))]))]))}},nt=C(X,[["__scopeId","data-v-b7241be8"]]);export{nt as default};
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as h,a as e,h as a,b as o,e as u,F as d,g as i,j as f,t as c,G as y,i as g}from"./index-BB0X_WMV.js";const b={class:"data-table-wrapper"},p={class:"data-table"},$={key:0,class:"actions-col"},_={key:0},D=["colspan"],S={key:1},T=["colspan"],B=["onClick"],N={key:0,class:"actions-col"},V={__name:"DataTable",props:{columns:{type:Array,required:!0},data:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},emptyText:{type:String,default:"데이터가 없습니다."}},emits:["row-click"],setup(n){const m=(t,r)=>t==null?"-":r.type==="date"&&t?new Date(t).toLocaleString("ko-KR"):r.type==="boolean"?t?"Y":"N":t;return(t,r)=>(a(),e("div",b,[o("table",p,[o("thead",null,[o("tr",null,[(a(!0),e(d,null,i(n.columns,s=>(a(),e("th",{key:s.key,style:f({width:s.width})},c(s.label),5))),128)),t.$slots.actions?(a(),e("th",$,"작업")):u("",!0)])]),o("tbody",null,[n.loading?(a(),e("tr",_,[o("td",{colspan:n.columns.length+(t.$slots.actions?1:0),class:"loading-cell"}," 로딩 중... ",8,D)])):!n.data||n.data.length===0?(a(),e("tr",S,[o("td",{colspan:n.columns.length+(t.$slots.actions?1:0),class:"empty-cell"},c(n.emptyText),9,T)])):(a(!0),e(d,{key:2},i(n.data,(s,k)=>(a(),e("tr",{key:s.id||k,onClick:l=>t.$emit("row-click",s)},[(a(!0),e(d,null,i(n.columns,l=>(a(),e("td",{key:l.key},[y(t.$slots,l.key,{row:s,value:s[l.key]},()=>[g(c(m(s[l.key],l)),1)])]))),128)),t.$slots.actions?(a(),e("td",N,[y(t.$slots,"actions",{row:s},void 0)])):u("",!0)],8,B))),128))])])]))}},A=h(V,[["__scopeId","data-v-db5e24a9"]]);export{A as D};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import"./index-09HB4Lmg.js";import{_ as i,c as s,a as t,h as l,e as o,i as c,t as n,F as m,g as y}from"./index-BB0X_WMV.js";const h={class:"form-group"},v=["for"],b={key:0,class:"required"},g=["id","type","value","placeholder","disabled","readonly"],f=["id","value","placeholder","disabled","readonly","rows"],k=["id","value","disabled"],V={key:0,value:""},S=["value"],x={key:4,class:"error-text"},I={key:5,class:"hint-text"},B={__name:"FormInput",props:{modelValue:{type:[String,Number],default:""},label:String,type:{type:String,default:"text"},placeholder:String,required:Boolean,disabled:Boolean,readonly:Boolean,error:String,hint:String,rows:{type:Number,default:3},options:{type:Array,default:()=>[]}},emits:["update:modelValue"],setup(e){const r=s(()=>`input-${Math.random().toString(36).slice(2,9)}`);return(u,d)=>(l(),t("div",h,[e.label?(l(),t("label",{key:0,for:r.value},[c(n(e.label)+" ",1),e.required?(l(),t("span",b,"*")):o("",!0)],8,v)):o("",!0),e.type!=="textarea"&&e.type!=="select"?(l(),t("input",{key:1,id:r.value,type:e.type,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,class:"form-input",onInput:d[0]||(d[0]=a=>u.$emit("update:modelValue",a.target.value))},null,40,g)):e.type==="textarea"?(l(),t("textarea",{key:2,id:r.value,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,rows:e.rows,class:"form-input",onInput:d[1]||(d[1]=a=>u.$emit("update:modelValue",a.target.value))},null,40,f)):e.type==="select"?(l(),t("select",{key:3,id:r.value,value:e.modelValue,disabled:e.disabled,class:"form-input",onChange:d[2]||(d[2]=a=>u.$emit("update:modelValue",a.target.value))},[e.placeholder?(l(),t("option",V,n(e.placeholder),1)):o("",!0),(l(!0),t(m,null,y(e.options,a=>(l(),t("option",{key:a.value,value:a.value},n(a.label),9,S))),128))],40,k)):o("",!0),e.error?(l(),t("span",x,n(e.error),1)):o("",!0),e.hint?(l(),t("span",I,n(e.hint),1)):o("",!0)]))}},N=i(B,[["__scopeId","data-v-45f49038"]]);export{N as F};
|
import"./index--fsvNaiQ.js";import{_ as i,c as s,a as t,h as l,e as o,i as c,t as n,F as m,g as y}from"./index-DFoOAXeQ.js";const h={class:"form-group"},v=["for"],b={key:0,class:"required"},g=["id","type","value","placeholder","disabled","readonly"],f=["id","value","placeholder","disabled","readonly","rows"],k=["id","value","disabled"],V={key:0,value:""},S=["value"],x={key:4,class:"error-text"},I={key:5,class:"hint-text"},B={__name:"FormInput",props:{modelValue:{type:[String,Number],default:""},label:String,type:{type:String,default:"text"},placeholder:String,required:Boolean,disabled:Boolean,readonly:Boolean,error:String,hint:String,rows:{type:Number,default:3},options:{type:Array,default:()=>[]}},emits:["update:modelValue"],setup(e){const r=s(()=>`input-${Math.random().toString(36).slice(2,9)}`);return(u,d)=>(l(),t("div",h,[e.label?(l(),t("label",{key:0,for:r.value},[c(n(e.label)+" ",1),e.required?(l(),t("span",b,"*")):o("",!0)],8,v)):o("",!0),e.type!=="textarea"&&e.type!=="select"?(l(),t("input",{key:1,id:r.value,type:e.type,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,class:"form-input",onInput:d[0]||(d[0]=a=>u.$emit("update:modelValue",a.target.value))},null,40,g)):e.type==="textarea"?(l(),t("textarea",{key:2,id:r.value,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,readonly:e.readonly,rows:e.rows,class:"form-input",onInput:d[1]||(d[1]=a=>u.$emit("update:modelValue",a.target.value))},null,40,f)):e.type==="select"?(l(),t("select",{key:3,id:r.value,value:e.modelValue,disabled:e.disabled,class:"form-input",onChange:d[2]||(d[2]=a=>u.$emit("update:modelValue",a.target.value))},[e.placeholder?(l(),t("option",V,n(e.placeholder),1)):o("",!0),(l(!0),t(m,null,y(e.options,a=>(l(),t("option",{key:a.value,value:a.value},n(a.label),9,S))),128))],40,k)):o("",!0),e.error?(l(),t("span",x,n(e.error),1)):o("",!0),e.hint?(l(),t("span",I,n(e.hint),1)):o("",!0)]))}},N=i(B,[["__scopeId","data-v-45f49038"]]);export{N as F};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as r,d as m,a as d,e as i,b as e,t as u,G as c,j as f,m as p,T as h,h as s}from"./index-BB0X_WMV.js";import"./index-09HB4Lmg.js";const y={class:"modal-header"},_={class:"modal-body"},k={key:0,class:"modal-footer"},v={__name:"Modal",props:{modelValue:{type:Boolean,default:!1},title:{type:String,default:""},width:{type:String,default:"500px"}},emits:["update:modelValue","close"],setup(t,{emit:n}){const a=n,l=()=>{a("update:modelValue",!1),a("close")};return(o,V)=>(s(),m(h,{to:"body"},[t.modelValue?(s(),d("div",{key:0,class:"modal-overlay",onClick:p(l,["self"])},[e("div",{class:"modal",style:f({width:t.width})},[e("div",y,[e("h3",null,u(t.title),1),e("button",{class:"close-btn",onClick:l},"×")]),e("div",_,[c(o.$slots,"default",{},void 0)]),o.$slots.footer?(s(),d("div",k,[c(o.$slots,"footer",{},void 0)])):i("",!0)],4)])):i("",!0)]))}},S=r(v,[["__scopeId","data-v-90993dd3"]]);export{S as M};
|
import{_ as r,d as m,a as d,e as i,b as e,t as u,G as c,j as f,m as p,T as h,h as s}from"./index-DFoOAXeQ.js";import"./index--fsvNaiQ.js";const y={class:"modal-header"},_={class:"modal-body"},k={key:0,class:"modal-footer"},v={__name:"Modal",props:{modelValue:{type:Boolean,default:!1},title:{type:String,default:""},width:{type:String,default:"500px"}},emits:["update:modelValue","close"],setup(t,{emit:n}){const a=n,l=()=>{a("update:modelValue",!1),a("close")};return(o,V)=>(s(),m(h,{to:"body"},[t.modelValue?(s(),d("div",{key:0,class:"modal-overlay",onClick:p(l,["self"])},[e("div",{class:"modal",style:f({width:t.width})},[e("div",y,[e("h3",null,u(t.title),1),e("button",{class:"close-btn",onClick:l},"×")]),e("div",_,[c(o.$slots,"default",{},void 0)]),o.$slots.footer?(s(),d("div",k,[c(o.$slots,"footer",{},void 0)])):i("",!0)],4)])):i("",!0)]))}},S=r(v,[["__scopeId","data-v-90993dd3"]]);export{S as M};
|
||||||
@@ -1 +1 @@
|
|||||||
import{_ as M,r as g,o as w,a as l,b as s,l as x,s as R,t as c,F as $,g as B,h as o,d as D,w as v,f as I,u as _}from"./index-BB0X_WMV.js";import{a as N,C as F}from"./index-09HB4Lmg.js";import{C as A,a as L,L as E,B as T,p as V,b as Y,c as O,d as z,e as U}from"./chartjs-plugin-datalabels.esm-DiDzp_cw.js";const W={class:"monthly-stats"},Z={class:"page-header"},j={class:"filter-section"},q={key:0,class:"loading"},G={key:1,class:"no-data"},H={key:2,class:"server-charts"},J={class:"chart-header"},K={class:"chart-subtitle"},P={class:"chart-container"},Q={__name:"MonthlyStats",setup(X){A.register(L,E,T,V,Y,O,z);const i=g(!1),r=g([]),n=g(f());function f(){const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}function b(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e-2,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}function y(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:a=>{if(a.datasetIndex!==2)return!1;const e=a.chart.data.datasets,t=a.dataIndex;return e.reduce((p,h)=>p+(h.data[t]||0),0)>0},anchor:"end",align:"end",offset:2,font:{size:10,weight:"bold"},color:"#666",formatter:(a,e)=>{const t=e.chart.data.datasets,u=e.dataIndex;return t.reduce((p,h)=>p+(h.data[u]||0),0)}}},scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0}}},C=a=>({labels:a.dailyStats.map(t=>`${new Date(t.date).getDate()}일`),datasets:[{label:"CRITICAL",data:a.dailyStats.map(t=>t.critical),backgroundColor:"#9b59b6",borderRadius:2},{label:"ERROR",data:a.dailyStats.map(t=>t.error),backgroundColor:"#e74c3c",borderRadius:2},{label:"WARN",data:a.dailyStats.map(t=>t.warn),backgroundColor:"#f39c12",borderRadius:2}]}),d=async()=>{i.value=!0;try{const[a,e]=n.value.split("-").map(Number);r.value=await N.getMonthlyStatsByServer(a,e)}catch(a){console.error("Failed to load stats:",a),r.value=[]}finally{i.value=!1}},m=a=>{const[e,t]=a.split("-");return`${e}년 ${parseInt(t)}월`},k=a=>a.dailyStats.reduce((e,t)=>e+t.total,0);return w(()=>{d()}),(a,e)=>(o(),l("div",W,[s("div",Z,[e[1]||(e[1]=s("h2",null,"월별 에러현황",-1)),s("div",j,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),x(s("input",{type:"month","onUpdate:modelValue":e[0]||(e[0]=t=>n.value=t),onChange:d},null,544),[[R,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),i.value?(o(),l("div",q,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",G,[s("p",null,c(m(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",H,[(o(!0),l($,null,B(r.value,t=>(o(),D(_(F),{key:t.serverId,class:"server-chart-card"},{header:v(()=>[s("div",J,[s("h3",null,"🖥️ "+c(t.serverName),1),s("span",K,c(m(n.value))+" 일별 에러 ("+c(k(t))+"건)",1)])]),default:v(()=>[s("div",P,[I(_(U),{data:C(t),options:S},null,8,["data"])])]),_:2},1024))),128))]))]))}},st=M(Q,[["__scopeId","data-v-7c1c78fe"]]);export{st as default};
|
import{_ as M,r as g,o as w,a as l,b as s,l as x,s as R,t as c,F as $,g as B,h as o,d as D,w as v,f as I,u as _}from"./index-DFoOAXeQ.js";import{a as N,C as F}from"./index--fsvNaiQ.js";import{C as A,a as L,L as E,B as T,p as V,b as Y,c as O,d as z,e as U}from"./chartjs-plugin-datalabels.esm-B8xtR40N.js";const W={class:"monthly-stats"},Z={class:"page-header"},j={class:"filter-section"},q={key:0,class:"loading"},G={key:1,class:"no-data"},H={key:2,class:"server-charts"},J={class:"chart-header"},K={class:"chart-subtitle"},P={class:"chart-container"},Q={__name:"MonthlyStats",setup(X){A.register(L,E,T,V,Y,O,z);const i=g(!1),r=g([]),n=g(f());function f(){const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}function b(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e-2,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}function y(){const[a,e]=n.value.split("-").map(Number),t=new Date(a,e,1);n.value=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`,d()}const S={responsive:!0,maintainAspectRatio:!1,plugins:{legend:{position:"top"},tooltip:{mode:"index",intersect:!1},datalabels:{display:a=>{if(a.datasetIndex!==2)return!1;const e=a.chart.data.datasets,t=a.dataIndex;return e.reduce((p,h)=>p+(h.data[t]||0),0)>0},anchor:"end",align:"end",offset:2,font:{size:10,weight:"bold"},color:"#666",formatter:(a,e)=>{const t=e.chart.data.datasets,u=e.dataIndex;return t.reduce((p,h)=>p+(h.data[u]||0),0)}}},scales:{x:{stacked:!0},y:{stacked:!0,beginAtZero:!0}}},C=a=>({labels:a.dailyStats.map(t=>`${new Date(t.date).getDate()}일`),datasets:[{label:"CRITICAL",data:a.dailyStats.map(t=>t.critical),backgroundColor:"#9b59b6",borderRadius:2},{label:"ERROR",data:a.dailyStats.map(t=>t.error),backgroundColor:"#e74c3c",borderRadius:2},{label:"WARN",data:a.dailyStats.map(t=>t.warn),backgroundColor:"#f39c12",borderRadius:2}]}),d=async()=>{i.value=!0;try{const[a,e]=n.value.split("-").map(Number);r.value=await N.getMonthlyStatsByServer(a,e)}catch(a){console.error("Failed to load stats:",a),r.value=[]}finally{i.value=!1}},m=a=>{const[e,t]=a.split("-");return`${e}년 ${parseInt(t)}월`},k=a=>a.dailyStats.reduce((e,t)=>e+t.total,0);return w(()=>{d()}),(a,e)=>(o(),l("div",W,[s("div",Z,[e[1]||(e[1]=s("h2",null,"월별 에러현황",-1)),s("div",j,[s("button",{class:"nav-btn",onClick:b},"◀ 이전"),x(s("input",{type:"month","onUpdate:modelValue":e[0]||(e[0]=t=>n.value=t),onChange:d},null,544),[[R,n.value]]),s("button",{class:"nav-btn",onClick:y},"다음 ▶")])]),i.value?(o(),l("div",q,[...e[2]||(e[2]=[s("p",null,"로딩중...",-1)])])):r.value.length===0?(o(),l("div",G,[s("p",null,c(m(n.value))+"에 분석된 에러 데이터가 없습니다.",1)])):(o(),l("div",H,[(o(!0),l($,null,B(r.value,t=>(o(),D(_(F),{key:t.serverId,class:"server-chart-card"},{header:v(()=>[s("div",J,[s("h3",null,"🖥️ "+c(t.serverName),1),s("span",K,c(m(n.value))+" 일별 에러 ("+c(k(t))+"건)",1)])]),default:v(()=>[s("div",P,[I(_(U),{data:C(t),options:S},null,8,["data"])])]),_:2},1024))),128))]))]))}},st=M(Q,[["__scopeId","data-v-7c1c78fe"]]);export{st as default};
|
||||||
@@ -1 +0,0 @@
|
|||||||
.card-header-content[data-v-fc6fccb5]{display:flex;justify-content:space-between;align-items:center}.card-header-content h3[data-v-fc6fccb5]{margin:0}.action-buttons[data-v-fc6fccb5]{display:flex;gap:4px}.regex-code[data-v-fc6fccb5]{font-family:monospace;background:#f1f3f4;padding:2px 6px;border-radius:3px;font-size:12px}.form-group[data-v-fc6fccb5]{margin-bottom:16px}.form-group label[data-v-fc6fccb5]{display:flex;align-items:center;gap:8px;cursor:pointer}.test-section[data-v-fc6fccb5]{display:flex;flex-direction:column;gap:16px}.test-pattern label[data-v-fc6fccb5]{display:block;font-weight:500;margin-bottom:6px}.regex-display[data-v-fc6fccb5]{display:block;font-family:monospace;background:#f8f9fa;padding:12px;border-radius:4px;font-size:13px;word-break:break-all}.test-result[data-v-fc6fccb5]{padding:16px;border-radius:8px;margin-top:8px}.test-result.success[data-v-fc6fccb5]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-fc6fccb5]{background:#f8d7da;border:1px solid #f5c6cb}.test-result h4[data-v-fc6fccb5]{margin:0 0 12px}.test-result p[data-v-fc6fccb5]{margin:0}.match-info[data-v-fc6fccb5]{margin-top:8px}.match-info label[data-v-fc6fccb5]{font-weight:500;margin-right:8px}.match-info code[data-v-fc6fccb5]{background:#0000001a;padding:2px 6px;border-radius:3px}.error-msg[data-v-fc6fccb5]{color:#721c24}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.page-header[data-v-821062ae]{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px}.page-header h2[data-v-821062ae]{margin:0}.loading[data-v-821062ae],.empty-state[data-v-821062ae]{text-align:center;padding:60px;background:#fff;border-radius:8px;color:#666}.empty-state p[data-v-821062ae]{margin-bottom:16px}.pattern-grid[data-v-821062ae]{display:grid;grid-template-columns:repeat(auto-fill,minmax(400px,1fr));gap:20px}.pattern-card[data-v-821062ae]{transition:box-shadow .2s,transform .2s}.pattern-card[data-v-821062ae]:hover{box-shadow:0 4px 12px #00000026;transform:translateY(-2px)}.pattern-header[data-v-821062ae]{display:flex;justify-content:space-between;align-items:center}.pattern-title[data-v-821062ae]{display:flex;align-items:center;gap:10px}.pattern-title h4[data-v-821062ae]{margin:0;font-size:16px;font-weight:600}.pattern-body[data-v-821062ae]{padding:4px 0}.pattern-info[data-v-821062ae]{margin-bottom:12px}.pattern-info label[data-v-821062ae]{display:block;font-size:12px;color:#666;margin-bottom:4px}.regex-box[data-v-821062ae]{display:block;font-family:Consolas,Monaco,monospace;background:#f1f3f5;padding:10px 12px;border-radius:6px;font-size:13px;word-break:break-all;line-height:1.4;border-left:3px solid #3498db}.regex-box.exclude[data-v-821062ae]{border-left-color:#e67e22;background:#fef5e7}.pattern-meta[data-v-821062ae]{display:flex;flex-wrap:wrap;gap:12px;padding-top:8px;border-top:1px solid #eee;font-size:13px}.meta-item[data-v-821062ae]{display:flex;align-items:center;gap:4px}.meta-label[data-v-821062ae]{color:#888}.meta-value[data-v-821062ae]{font-weight:500;color:#333}.meta-item.description[data-v-821062ae]{flex-basis:100%;color:#666;font-style:italic}.pattern-actions[data-v-821062ae]{display:flex;gap:8px;padding-top:12px;border-top:1px solid #eee;margin-top:12px}.action-btn[data-v-821062ae]{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:10px 12px;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:500;transition:all .2s}.action-btn.test[data-v-821062ae]{background:#e8f4fd;color:#2980b9}.action-btn.test[data-v-821062ae]:hover{background:#d4e9f7}.action-btn.edit[data-v-821062ae]{background:#fef3e2;color:#d68910}.action-btn.edit[data-v-821062ae]:hover{background:#fce8c9}.action-btn.delete[data-v-821062ae]{background:#fdeaea;color:#c0392b}.action-btn.delete[data-v-821062ae]:hover{background:#f9d6d6}.form-group[data-v-821062ae]{margin-bottom:16px}.form-group label[data-v-821062ae]{display:flex;align-items:center;gap:8px;cursor:pointer}.test-section[data-v-821062ae]{display:flex;flex-direction:column;gap:16px}.test-pattern label[data-v-821062ae]{display:block;font-weight:500;margin-bottom:6px}.regex-display[data-v-821062ae]{display:block;font-family:monospace;background:#f8f9fa;padding:12px;border-radius:4px;font-size:13px;word-break:break-all}.test-result[data-v-821062ae]{padding:16px;border-radius:8px;margin-top:8px}.test-result.success[data-v-821062ae]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-821062ae]{background:#f8d7da;border:1px solid #f5c6cb}.test-result h4[data-v-821062ae]{margin:0 0 12px}.test-result p[data-v-821062ae]{margin:0}.match-info[data-v-821062ae]{margin-top:8px}.match-info label[data-v-821062ae]{font-weight:500;margin-right:8px}.match-info code[data-v-821062ae]{background:#0000001a;padding:2px 6px;border-radius:3px}.error-msg[data-v-821062ae]{color:#721c24}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.card-header-content[data-v-6f59a208]{display:flex;justify-content:space-between;align-items:center}.card-header-content h3[data-v-6f59a208]{margin:0}.action-buttons[data-v-6f59a208]{display:flex;gap:4px}.form-group[data-v-6f59a208]{margin-bottom:16px}.form-group label[data-v-6f59a208]{display:flex;align-items:center;gap:8px;cursor:pointer}.log-path-section h4[data-v-6f59a208]{margin:0 0 12px;font-size:14px;color:#333}.log-path-form[data-v-6f59a208]{padding:16px;background:#f8f9fa;border-radius:8px;margin-bottom:20px}.log-path-inputs[data-v-6f59a208]{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:12px}.log-path-list table[data-v-6f59a208]{width:100%;border-collapse:collapse}.log-path-list th[data-v-6f59a208],.log-path-list td[data-v-6f59a208]{padding:10px 12px;text-align:left;border-bottom:1px solid #eee}.log-path-list th[data-v-6f59a208]{background:#f8f9fa;font-weight:600;font-size:13px}.empty-text[data-v-6f59a208]{color:#6c757d;text-align:center;padding:20px}.warning-text[data-v-6f59a208]{color:#e74c3c;font-size:14px}.test-result[data-v-6f59a208]{padding:16px;border-radius:8px}.test-result.success[data-v-6f59a208]{background:#d4edda;border:1px solid #c3e6cb}.test-result.fail[data-v-6f59a208]{background:#f8d7da;border:1px solid #f5c6cb}.test-result p[data-v-6f59a208]{margin:0}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{_ as S,r as m,o as F,a as p,f as a,w as i,u as n,h as v,m as B,b as t,i as c}from"./index-BB0X_WMV.js";import{C as V,b}from"./index-09HB4Lmg.js";import{F as u}from"./FormInput-BwPgkOAn.js";import{B as y}from"./Button-CoXLXkk7.js";const k={class:"settings"},w={key:0,class:"loading"},C={class:"setting-section"},U={class:"setting-section"},h={class:"setting-section"},M={class:"setting-section"},z={class:"form-actions"},A={__name:"Settings",setup(N){const r=m(!1),d=m(!1),l=m({}),f={"server.port":"8080","export.path":"./exports","retention.days":"90","scan.timeout":"30","scan.maxFileSize":"100"},g=async()=>{r.value=!0;try{const o=await b.getAllAsMap();l.value={...f,...o}}catch(o){console.error("Failed to load settings:",o),l.value={...f}}finally{r.value=!1}},x=async()=>{d.value=!0;try{for(const[o,e]of Object.entries(l.value))await b.save({key:o,value:String(e)});alert("설정이 저장되었습니다.")}catch(o){console.error("Failed to save settings:",o),alert("설정 저장에 실패했습니다.")}finally{d.value=!1}};return F(()=>{g()}),(o,e)=>(v(),p("div",k,[a(n(V),null,{header:i(()=>[...e[5]||(e[5]=[t("div",{class:"card-header-content"},[t("h3",null,"설정")],-1)])]),default:i(()=>[r.value?(v(),p("div",w,"로딩중...")):(v(),p("form",{key:1,onSubmit:B(x,["prevent"]),class:"settings-form"},[t("div",C,[e[6]||(e[6]=t("h4",null,"일반 설정",-1)),a(n(u),{modelValue:l.value["server.port"],"onUpdate:modelValue":e[0]||(e[0]=s=>l.value["server.port"]=s),label:"서버 포트",type:"number",hint:"애플리케이션이 실행될 포트 번호 (기본: 8080)"},null,8,["modelValue"])]),t("div",U,[e[7]||(e[7]=t("h4",null,"내보내기 설정",-1)),a(n(u),{modelValue:l.value["export.path"],"onUpdate:modelValue":e[1]||(e[1]=s=>l.value["export.path"]=s),label:"내보내기 경로",placeholder:"예: C:\\LogHunter\\exports",hint:"리포트 파일이 저장될 기본 경로"},null,8,["modelValue"])]),t("div",h,[e[8]||(e[8]=t("h4",null,"데이터 관리",-1)),a(n(u),{modelValue:l.value["retention.days"],"onUpdate:modelValue":e[2]||(e[2]=s=>l.value["retention.days"]=s),label:"로그 보관 기간 (일)",type:"number",hint:"에러 로그 데이터 보관 기간 (0 = 무제한)"},null,8,["modelValue"])]),t("div",M,[e[9]||(e[9]=t("h4",null,"스캔 설정",-1)),a(n(u),{modelValue:l.value["scan.timeout"],"onUpdate:modelValue":e[3]||(e[3]=s=>l.value["scan.timeout"]=s),label:"스캔 타임아웃 (초)",type:"number",hint:"SFTP 연결 및 파일 다운로드 타임아웃"},null,8,["modelValue"]),a(n(u),{modelValue:l.value["scan.maxFileSize"],"onUpdate:modelValue":e[4]||(e[4]=s=>l.value["scan.maxFileSize"]=s),label:"최대 파일 크기 (MB)",type:"number",hint:"분석할 로그 파일의 최대 크기"},null,8,["modelValue"])]),t("div",z,[a(n(y),{onClick:g,variant:"secondary"},{default:i(()=>[...e[10]||(e[10]=[c("초기화",-1)])]),_:1}),a(n(y),{type:"submit",loading:d.value},{default:i(()=>[...e[11]||(e[11]=[c("저장",-1)])]),_:1},8,["loading"])])],32))]),_:1}),a(n(V),{class:"app-info"},{header:i(()=>[...e[12]||(e[12]=[t("h3",null,"애플리케이션 정보",-1)])]),default:i(()=>[e[13]||(e[13]=t("div",{class:"info-list"},[t("div",{class:"info-item"},[t("span",{class:"label"},"버전"),t("span",{class:"value"},"1.0.0")]),t("div",{class:"info-item"},[t("span",{class:"label"},"프레임워크"),t("span",{class:"value"},"Spring Boot 3.2 + Vue 3")]),t("div",{class:"info-item"},[t("span",{class:"label"},"데이터베이스"),t("span",{class:"value"},"SQLite (./data/loghunter.db)")])],-1))]),_:1})]))}},j=S(A,[["__scopeId","data-v-fdca948e"]]);export{j as default};
|
import{_ as S,r as m,o as F,a as p,f as a,w as i,u as n,h as v,m as B,b as t,i as c}from"./index-DFoOAXeQ.js";import{C as V,b}from"./index--fsvNaiQ.js";import{F as u}from"./FormInput-BsW78DWl.js";import{B as y}from"./Button-Ddldcbk7.js";const k={class:"settings"},w={key:0,class:"loading"},C={class:"setting-section"},U={class:"setting-section"},h={class:"setting-section"},M={class:"setting-section"},z={class:"form-actions"},A={__name:"Settings",setup(N){const r=m(!1),d=m(!1),l=m({}),f={"server.port":"8080","export.path":"./exports","retention.days":"90","scan.timeout":"30","scan.maxFileSize":"100"},g=async()=>{r.value=!0;try{const o=await b.getAllAsMap();l.value={...f,...o}}catch(o){console.error("Failed to load settings:",o),l.value={...f}}finally{r.value=!1}},x=async()=>{d.value=!0;try{for(const[o,e]of Object.entries(l.value))await b.save({key:o,value:String(e)});alert("설정이 저장되었습니다.")}catch(o){console.error("Failed to save settings:",o),alert("설정 저장에 실패했습니다.")}finally{d.value=!1}};return F(()=>{g()}),(o,e)=>(v(),p("div",k,[a(n(V),null,{header:i(()=>[...e[5]||(e[5]=[t("div",{class:"card-header-content"},[t("h3",null,"설정")],-1)])]),default:i(()=>[r.value?(v(),p("div",w,"로딩중...")):(v(),p("form",{key:1,onSubmit:B(x,["prevent"]),class:"settings-form"},[t("div",C,[e[6]||(e[6]=t("h4",null,"일반 설정",-1)),a(n(u),{modelValue:l.value["server.port"],"onUpdate:modelValue":e[0]||(e[0]=s=>l.value["server.port"]=s),label:"서버 포트",type:"number",hint:"애플리케이션이 실행될 포트 번호 (기본: 8080)"},null,8,["modelValue"])]),t("div",U,[e[7]||(e[7]=t("h4",null,"내보내기 설정",-1)),a(n(u),{modelValue:l.value["export.path"],"onUpdate:modelValue":e[1]||(e[1]=s=>l.value["export.path"]=s),label:"내보내기 경로",placeholder:"예: C:\\LogHunter\\exports",hint:"리포트 파일이 저장될 기본 경로"},null,8,["modelValue"])]),t("div",h,[e[8]||(e[8]=t("h4",null,"데이터 관리",-1)),a(n(u),{modelValue:l.value["retention.days"],"onUpdate:modelValue":e[2]||(e[2]=s=>l.value["retention.days"]=s),label:"로그 보관 기간 (일)",type:"number",hint:"에러 로그 데이터 보관 기간 (0 = 무제한)"},null,8,["modelValue"])]),t("div",M,[e[9]||(e[9]=t("h4",null,"스캔 설정",-1)),a(n(u),{modelValue:l.value["scan.timeout"],"onUpdate:modelValue":e[3]||(e[3]=s=>l.value["scan.timeout"]=s),label:"스캔 타임아웃 (초)",type:"number",hint:"SFTP 연결 및 파일 다운로드 타임아웃"},null,8,["modelValue"]),a(n(u),{modelValue:l.value["scan.maxFileSize"],"onUpdate:modelValue":e[4]||(e[4]=s=>l.value["scan.maxFileSize"]=s),label:"최대 파일 크기 (MB)",type:"number",hint:"분석할 로그 파일의 최대 크기"},null,8,["modelValue"])]),t("div",z,[a(n(y),{onClick:g,variant:"secondary"},{default:i(()=>[...e[10]||(e[10]=[c("초기화",-1)])]),_:1}),a(n(y),{type:"submit",loading:d.value},{default:i(()=>[...e[11]||(e[11]=[c("저장",-1)])]),_:1},8,["loading"])])],32))]),_:1}),a(n(V),{class:"app-info"},{header:i(()=>[...e[12]||(e[12]=[t("h3",null,"애플리케이션 정보",-1)])]),default:i(()=>[e[13]||(e[13]=t("div",{class:"info-list"},[t("div",{class:"info-item"},[t("span",{class:"label"},"버전"),t("span",{class:"value"},"1.0.0")]),t("div",{class:"info-item"},[t("span",{class:"label"},"프레임워크"),t("span",{class:"value"},"Spring Boot 3.2 + Vue 3")]),t("div",{class:"info-item"},[t("span",{class:"label"},"데이터베이스"),t("span",{class:"value"},"SQLite (./data/loghunter.db)")])],-1))]),_:1})]))}},j=S(A,[["__scopeId","data-v-fdca948e"]]);export{j as default};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var Yn=Object.defineProperty;var Un=(i,t,e)=>t in i?Yn(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var w=(i,t,e)=>Un(i,typeof t!="symbol"?t+"":t,e);import{x as Us,y as Xs,z as oi,A as Xn,r as Kn,o as qn,B as Gn,C as Zn,D as ri,E as Ks,p as Qn}from"./index-BB0X_WMV.js";/*!
|
var Yn=Object.defineProperty;var Un=(i,t,e)=>t in i?Yn(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var w=(i,t,e)=>Un(i,typeof t!="symbol"?t+"":t,e);import{x as Us,y as Xs,z as oi,A as Xn,r as Kn,o as qn,B as Gn,C as Zn,D as ri,E as Ks,p as Qn}from"./index-DFoOAXeQ.js";/*!
|
||||||
* @kurkle/color v0.3.4
|
* @kurkle/color v0.3.4
|
||||||
* https://github.com/kurkle/color#readme
|
* https://github.com/kurkle/color#readme
|
||||||
* (c) 2024 Jukka Kurkela
|
* (c) 2024 Jukka Kurkela
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@
|
|||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/index-BB0X_WMV.js"></script>
|
<script type="module" crossorigin src="/assets/index-DFoOAXeQ.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BJ4-9mFZ.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BJ4-9mFZ.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
Reference in New Issue
Block a user