G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현 G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건) G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST) G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직 G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트 G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트 G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트 G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트 G-10: core/push_notify.py + routers/push.py + PushSubscription 모델 G-11: approvals 다중승인 (위임/서명/기한초과/마감연장) G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh 하네스: guardia-orchestrator 확장기능 Phase 반영 봇명령어: /sr /status /license /bulk 슬래시 명령어 추가 설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
290 lines
6.8 KiB
JavaScript
290 lines
6.8 KiB
JavaScript
/*------------------------------------------------------------------------------
|
|
* 1. 파일명: ui.js
|
|
* 2. 설 명: UI 조절과 관련된 함수를 정의한다.
|
|
* 3. 의존성: form.js, ui.js
|
|
* 4. 작성자:
|
|
* 5. 작성일: 2006.10.17.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/* 2007.05.10
|
|
* Object(버튼)의 클래스를 토글로 normal과 over로 바꿔준다.
|
|
*/
|
|
function toggleButtonClassName(btnElement) {
|
|
var normalClassName = "normal";
|
|
var overClassName = "over";
|
|
if (btnElement.className == normalClassName) {
|
|
Element.removeClassName(btnElement, normalClassName);
|
|
Element.addClassName(btnElement, overClassName);
|
|
} else if (btnElement.className == overClassName){
|
|
Element.removeClassName(btnElement, overClassName);
|
|
Element.addClassName(btnElement, normalClassName);
|
|
}
|
|
}
|
|
|
|
|
|
// 목록에서 선택된 요소의 하이라이트 색상
|
|
var SELECTED_BG_COLOR = "#99CCFF";
|
|
|
|
/**
|
|
* 해당 object의 자식 노드의 스타일을 모두 지정 색상으로 바꾼다.
|
|
*
|
|
*/
|
|
function changeAllChildNodesColor(obj, color) {
|
|
if (obj != null) {
|
|
var size = obj.childNodes.length;
|
|
for (i = 0; i < size; i++) {
|
|
var childEl = obj.childNodes[i]; // Not Recursive!
|
|
try {
|
|
childEl.style.background = color;
|
|
} catch (doNotApplyStyle) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 주어진 높이를 화면의 가운데 놓기위한
|
|
* Top 위치 값을 돌려 준다.
|
|
*/
|
|
function calcTopForCenter(height) {
|
|
return ( screen.height - height ) / 2;
|
|
}
|
|
|
|
/**
|
|
* 주어진 폭을 화면의 가운데 놓기 위한
|
|
* Left 위치 값을 돌려 준다.
|
|
*/
|
|
function calcLeftForCenter(width) {
|
|
return ( screen.width - width ) / 2;
|
|
}
|
|
|
|
/**
|
|
* 대화 창을 띄운다.
|
|
*/
|
|
function showDialog(name, url, width, height, option) {
|
|
option = "width=" + width
|
|
+ ",height=" + height
|
|
+ ",alwaysRaised=yes"
|
|
+ ",dependent=yes"
|
|
+ (option == undefined || option.length == 0 ? "" : "," + option);
|
|
|
|
return showWindow(name, url, width, height, option);
|
|
}
|
|
|
|
/**
|
|
* 윈도우를 주어진 좌표에 띄운다. 위치가 주어지지 않으면
|
|
* 윈도우가 화면 가운데 오도록 띄운다.
|
|
*/
|
|
function showWindow(name, url, width, height, option, topPx, leftPx) {
|
|
if ( ! topPx ) topPx = calcTopForCenter(height);
|
|
if ( ! leftPx ) leftPx = calcLeftForCenter(width);
|
|
|
|
var win = window.open(url, name, option);
|
|
|
|
win.moveTo(leftPx, topPx);
|
|
|
|
if ( win ) win.focus();
|
|
|
|
return win;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 메세지 창을 띄워 주어진 메세지를 보여 준다.
|
|
*/
|
|
function showMessage(msg) {
|
|
alert(msg);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* System 메세지를 보여 준다.
|
|
*/
|
|
function showSysMessage(msg) {
|
|
alert("System 메세지: " + msg);
|
|
}
|
|
|
|
|
|
/**
|
|
* 주어진 Select 객체의 값을 Input 객체에 세팅하고
|
|
* 다음 입력 항목으로 넘어간다.
|
|
*/
|
|
function setSelectedValueAndMoveNext(moveOrderIdList, selectObj, inputObj) {
|
|
ref(inputObj).value = getSelectedValue(selectObj);
|
|
|
|
moveNext(moveOrderIdList, ref(inputObj).id);
|
|
}
|
|
|
|
|
|
/**
|
|
* 다음 항목으로 이동한다.
|
|
* 이동시 disable 상태이거나, 보이지 않는 상태(display: none)이면
|
|
* 이 항목은 건너 띈다.
|
|
*
|
|
* @param moveOrderIdList 이동할 항목의 ID 배열.
|
|
* @param currentPositionId 현재 위치 ID. 배열에서 이 ID 다음 ID로 이동한다.
|
|
*/
|
|
function moveNext(moveOrderIdList, currentPositionId) {
|
|
var index = indexInArray(moveOrderIdList, currentPositionId);
|
|
|
|
if ( index == -1 ) alert("ID " + currentPositionId + "는 이동 목록(moveOrderIdList)에 없습니다.");
|
|
|
|
var nextIndex = -1;
|
|
var i = (index + 1) % moveOrderIdList.length;
|
|
for ( var count = 0; count < moveOrderIdList.length; count++, i = ((++i) % moveOrderIdList.length) ) {
|
|
var obj = ref(moveOrderIdList[i]);
|
|
|
|
if ( obj != undefined && obj != null && ! obj.disabled ) {
|
|
nextIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//alert(index + " -> " + nextIndex + " : " + moveOrderIdList[nextIndex]);
|
|
|
|
if ( nextIndex > -1 ) {
|
|
ref(moveOrderIdList[nextIndex]).focus();
|
|
ref(moveOrderIdList[nextIndex]).select();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 눌려진 Key가 Enter이면
|
|
* 다음 항목으로 이동한다.
|
|
*/
|
|
function moveNextIfEnter(moveOrderIdList, currentId) {
|
|
if ( event.keyCode == 13 ) {
|
|
moveNext(moveOrderIdList, currentId);
|
|
}
|
|
|
|
event.returnValue = ( event.keyCode != 13 );
|
|
}
|
|
|
|
|
|
function moveNextIfCtrlEnter(moveOrderIdList, currentId) {
|
|
if ( event.keyCode == 13 && event.ctrlKey ) {
|
|
moveNext(moveOrderIdList, currentId);
|
|
}
|
|
|
|
event.returnValue = ! ( event.keyCode == 13 && event.ctrlKey );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* 주어진 Form의 요소 중에 valid.required의 값이 true인
|
|
* 항목을 표시한다.
|
|
*/
|
|
function markRequiredInput(form) {
|
|
for ( var i = 0; i < form.elements.length; i++ ) {
|
|
var elem = form.elements[i];
|
|
|
|
if ( ! elem.disabled && ! elem.readOnly ) {
|
|
if ( elem.getAttribute("valid.required") == "true" ) {
|
|
elem.style.backgroundColor = "#FFFFCC";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 주어진 Form에 정의된 Element를 뒤져서
|
|
* 편집 가능한 Text나 TextArea인 경우
|
|
* Focus를 주었을 때와 잃었을 때의 처리 핸들러를
|
|
* 설정한다. 기본 값으로 테두리를 빨간색으로 했다가
|
|
* 원래대로 돌리는 핸들러가 세팅된다.
|
|
*/
|
|
function setFocusAndBlurAction(form, focusAction, blurAction) {
|
|
/* 정의가 안 되어 있으면 기본 값을 넣는다. */
|
|
if ( focusAction == undefined ) focusAction = setBorderRed;
|
|
|
|
if ( blurAction == undefined ) blurAction = restoreBorderColor;
|
|
|
|
for ( var i = 0; i < form.elements.length; i++ ) {
|
|
var elem = form.elements[i];
|
|
//alert(elem.type);
|
|
if ( elem.type != undefined
|
|
&& ( elem.type == "text"
|
|
|| elem.type == "textarea"
|
|
|| elem.type == "password" )
|
|
&& ! elem.disabled
|
|
&& ! elem.readOnly ) {
|
|
elem.attachEvent("onfocus", baseOnFocusHandler);
|
|
elem.attachEvent("onfocus", focusAction);
|
|
elem.attachEvent("onblur" , blurAction );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Focus 이벤트 발생시 기본적으로 걸 함수
|
|
*/
|
|
function baseOnFocusHandler() {
|
|
var eventSrc = event.srcElement;
|
|
|
|
/* 이벤트 발생한 항목의 값이 선택된 상태가 되게 한다. */
|
|
eventSrc.select();
|
|
}
|
|
|
|
/**
|
|
* Event가 발생한 객체의 테두리를 빨간선으로 바꾼다.
|
|
*/
|
|
function setBorderRed() {
|
|
var eventSrc = event.srcElement;
|
|
|
|
oldBorderValue = eventSrc.style.border;
|
|
|
|
eventSrc.style.border = "1px solid #CCCCCC";
|
|
}
|
|
|
|
/**
|
|
* Blur 이벤트가 발생한 테두리의 색상을 원래대로
|
|
* 돌려놓는다.
|
|
*/
|
|
function restoreBorderColor() {
|
|
var eventSrc = event.srcElement;
|
|
|
|
eventSrc.style.borderColor = "";
|
|
}
|
|
|
|
|
|
// 1024x768 해상도에서 Main 영역에 필요한 최소 높이값
|
|
var MIN_MAIN_HEIGHT = 494;
|
|
|
|
/**
|
|
* 주어진 고정 높이를 고려했을 때 Main 화면 영역의
|
|
* 변할 수 있는 영역 높이 값을 돌려 준다.
|
|
*/
|
|
function getVarHeightOfMain(minVarHeight) {
|
|
var clientHeight = document.body.clientHeight;
|
|
var fixHeight = MIN_MAIN_HEIGHT - minVarHeight;
|
|
|
|
/* 변경될 높이 값을 구한다. */
|
|
var varHeight = Math.max(MIN_MAIN_HEIGHT, clientHeight) - fixHeight;
|
|
|
|
return varHeight;
|
|
}
|
|
|
|
|
|
/**
|
|
* 확인창을 띄운후 참이면, path로 이동한다.
|
|
*
|
|
*/
|
|
function confirmation(path, message) {
|
|
if (confirm(message)){
|
|
document.location.href = path;
|
|
}
|
|
}
|
|
|
|
|
|
function center_popup(page,name,w,h,scroll){
|
|
LeftPosition = (screen.width) ? (screen.width-w)/2 : 0;
|
|
TopPosition = (screen.height) ? (screen.height-h)/2 : 0;
|
|
|
|
settings ='height='+h+',width='+w+',top='+TopPosition+',left='+LeftPosition+',scrollbars='+scroll+',resizable';
|
|
var w = window.open(page,name,settings);
|
|
return w;
|
|
}
|