CN-1173: 检测功能UI开发
This commit is contained in:
@@ -89,3 +89,9 @@
|
||||
|
||||
@import "views/entityExplorer/graphRightListBlock";
|
||||
@import "views/entityExplorer/graphRightDetailBlock";
|
||||
|
||||
@import "views/detections/detection-tools";
|
||||
@import "views/detections/detection-drawer";
|
||||
@import "views/detections/detection-table";
|
||||
@import "views/detections/detection-create/detection-form";
|
||||
@import "views/detections/detection-create/detection-form-setting";
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
.form-setting__block {
|
||||
width: 620px;
|
||||
|
||||
.el-form-item__label {
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
padding: 0;
|
||||
margin-bottom: 12px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.block-mode {
|
||||
width: 300px;
|
||||
height: 125px;
|
||||
padding: 16px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(226, 229, 236, 1);
|
||||
border-radius: 2px;
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
|
||||
.block-mode-left {
|
||||
line-height: 16px;
|
||||
margin-right: 10px;
|
||||
|
||||
.block-mode-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.block-mode-right {
|
||||
width: 240px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.block-mode-title {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.block-mode-content {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
color: #717171;
|
||||
line-height: 15px;
|
||||
font-weight: 400;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.block-mode-btn, .block-mode-btn-active {
|
||||
width: 240px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
background: #F5F6F7;
|
||||
border-radius: 2px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 12px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.block-mode-btn-active {
|
||||
background: #7E9F54;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block-title {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.block-title1 {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-setting__select {
|
||||
width: 620px !important;
|
||||
|
||||
.el-input--mini .el-input__inner {
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.form-setting__input {
|
||||
&.el-input {
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-setting__textarea {
|
||||
.el-textarea__inner {
|
||||
height: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-switch__label, .form-setting__block .el-switch__label {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.block-dimension {
|
||||
width: 620px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
border: 1px solid #e2e5ec;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.block-dimension-tag {
|
||||
width: auto;
|
||||
height: 19px;
|
||||
line-height: 19px;
|
||||
background: #E5E8EB;
|
||||
border-radius: 2px;
|
||||
opacity: 0.6;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input--mini .el-input__inner {
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.form-setting__block-key {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.block-key {
|
||||
height: 28px;
|
||||
background: #F5F8FA;
|
||||
border: 1px solid rgba(222, 222, 222, 1);
|
||||
box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.02);
|
||||
border-radius: 2px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 12px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
color: #575757;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-setting__btn, .form-setting__btn1 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.el-button {
|
||||
height: 30px !important;
|
||||
min-height: 30px !important;
|
||||
line-height: 30px !important;
|
||||
background: #38ACD2;
|
||||
border: 1px solid rgba(46, 136, 166, 1);
|
||||
border-radius: 2px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
padding: 0 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-setting__btn1 {
|
||||
.el-button {
|
||||
padding: 0 11px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.key-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
.el-input__inner {
|
||||
border-color: #DEDEDE !important;
|
||||
}
|
||||
}
|
||||
|
||||
.definition-filter-block {
|
||||
border: 1px #E2E5EC solid;
|
||||
min-height: 196px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
|
||||
.definition-filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 24px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.filter-item__select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.filter-item__input {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
.el-input--mini .el-input__inner {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
font-variant-caps: all-small-caps;
|
||||
color: #575757;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-block-footer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
font-variant-caps: all-small-caps;
|
||||
color: #575757;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block-filter-add {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
background: #F5F8FA;
|
||||
border: 1px solid rgba(222, 222, 222, 1);
|
||||
box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.02);
|
||||
border-radius: 2px;
|
||||
color: #575757;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.history-top-key {
|
||||
width: 396px;
|
||||
|
||||
.el-drawer__body {
|
||||
border: 1px #E2E5EC solid;
|
||||
}
|
||||
|
||||
.el-overlay {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.el-drawer, .rtl {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.key-table {
|
||||
.el-table th > .cell, .el-table .cell {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.el-table th {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-weight: 500;
|
||||
padding: 0 0 0 12px;
|
||||
color: #353636;
|
||||
background: #F9F9F9;
|
||||
height: 30px;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.el-table__body td {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-weight: 400;
|
||||
padding: 0 0 0 12px;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
height: 28px;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.el-table::before {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.definition-condition-block {
|
||||
border: 1px #E2E5EC solid;
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.condition-title {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.condition__select {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
||||
.el-input__inner {
|
||||
padding-left: 20px !important;
|
||||
}
|
||||
|
||||
.condition__select__icon {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.condition-metric {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.condition-metric-item1 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.metric-item1__text {
|
||||
height: 24px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
font-variant-caps: all-small-caps;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metric-item1-close {
|
||||
color: #E26154;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.metric-item1-close-disable {
|
||||
color: #ecb2ad;
|
||||
margin-left: 12px;
|
||||
cursor: no-drop;
|
||||
}
|
||||
}
|
||||
|
||||
.condition-metric-item2 {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
.metric-item2__select {
|
||||
margin-right: 12px;
|
||||
|
||||
.el-input__inner {
|
||||
width: 170px;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-item2__input {
|
||||
width: 112px;
|
||||
margin-right: 12px;
|
||||
height: 24px !important;
|
||||
margin-top: -1px !important;
|
||||
border-radius: 2px !important;
|
||||
|
||||
.el-input__inner {
|
||||
width: 112px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.condition__select {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
|
||||
.el-input__inner {
|
||||
padding-left: 20px !important;
|
||||
}
|
||||
|
||||
.condition__select__icon {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.condition-add {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
background: #F5F8FA;
|
||||
border: 1px solid rgba(222, 222, 222, 1);
|
||||
box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.02);
|
||||
border-radius: 2px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 12px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
font-variant-caps: all-small-caps;
|
||||
margin-right: 4px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.condition-divider {
|
||||
border: 1px #ECECEC dashed;
|
||||
|
||||
.el-divider__text {
|
||||
padding: 0 4px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
color: #BEBEBE;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-r-8 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.margin-b-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-b-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.margin-t-18 {
|
||||
margin-top: 18px;
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
.detection-form {
|
||||
padding: 20px;
|
||||
|
||||
.detection-form-header {
|
||||
font-family: NotoSansHans-Black;
|
||||
font-size: 24px;
|
||||
color: #353636;
|
||||
font-weight: 900;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.detection-form-content {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
padding-bottom: 40px;
|
||||
|
||||
.detection-form-collapse {
|
||||
margin-top: 20px;
|
||||
width: 1200px;
|
||||
|
||||
.el-collapse-item__header {
|
||||
width: 1200px !important;
|
||||
height: 56px !important;
|
||||
background: #FFFFFF !important;
|
||||
border-left: 1px solid #EFF2F5;
|
||||
border-right: 1px solid #EFF2F5;
|
||||
//box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02) !important;
|
||||
border-radius: 4px !important;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: inherit;
|
||||
position: relative;
|
||||
padding: 0 20px;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-collapse-item__wrap {
|
||||
border: 1px solid #EFF2F5;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.form-collapse-header {
|
||||
margin-left: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-collapse-header-no, .form-collapse-header-no-active {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
transition: 0.5s all;
|
||||
}
|
||||
|
||||
.form-collapse-header-no {
|
||||
background: #E2E5EC;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.form-collapse-header-no-active {
|
||||
background: #38ACD2;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.form-collapse-header-title {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.form-collapse-content {
|
||||
padding: 0 20px 0 46px;
|
||||
|
||||
.trigger-block {
|
||||
width: 620px;
|
||||
height: 90px;
|
||||
padding: 16px 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px #E2E5EC solid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
|
||||
.trigger-block-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.el-input--mini .el-input__inner {
|
||||
width: 112px;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.el-select .el-input .el-select__caret {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.form-trigger__select {
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
height: 24px;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-input--mini, .el-input--mini .el-input__inner {
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
border-radius: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
.detection-drawer {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
.el-drawer {
|
||||
width: 440px !important;
|
||||
}
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.el-overlay {
|
||||
top: 100px !important;
|
||||
background-color: rgba(0, 0, 0, 0.16) !important;
|
||||
}
|
||||
|
||||
.detection-drawer-title, .basic-function-value, basic-description-value {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #717171;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.drawer-basic {
|
||||
.drawer-basic-header {
|
||||
font-family: NotoSansHans-Black;
|
||||
font-size: 16px;
|
||||
color: #353636;
|
||||
font-weight: 900;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.drawer-basic-id {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-basic-function, .drawer-basic-description {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.detection-drawer-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.basic-description-value {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.basic-function-value {
|
||||
color: #046ECA;
|
||||
}
|
||||
|
||||
.detection-drawer-collapse {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(226, 229, 236, 1);
|
||||
border-radius: 4px;
|
||||
//box-shadow: 0 1px 0 0 rgba(226, 229, 236, 1);
|
||||
|
||||
.el-collapse-item__header {
|
||||
height: 32px !important;
|
||||
background-color: #F7F7F7 !important;
|
||||
padding-left: 12px !important;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.drawer-collapse-content {
|
||||
padding: 20px 20px 0;
|
||||
|
||||
.detection__icon {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.drawer-collapse-trigger {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +1,165 @@
|
||||
.detection-filter-case {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 280px;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
background-color: white;
|
||||
overflow: auto;
|
||||
//.detection-filter-case {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// width: 280px;
|
||||
// padding: 10px;
|
||||
// margin-right: 10px;
|
||||
// background-color: white;
|
||||
// overflow: auto;
|
||||
//
|
||||
// .detection-filter {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// margin-bottom: 10px;
|
||||
//
|
||||
// .filter__header {
|
||||
// display: flex;
|
||||
// flex: 0 0 32px;
|
||||
// align-items: center;
|
||||
// padding-left: 10px;
|
||||
// color: #666;
|
||||
// //background-color: #F3F7FA;
|
||||
// cursor: pointer;
|
||||
//
|
||||
// span {
|
||||
// font-size: 14px;
|
||||
// padding-left: 6px;
|
||||
// }
|
||||
// i {
|
||||
// font-size: 12px;
|
||||
// transition: all linear .1s;
|
||||
// transform: rotate(0) translate(0, 2px);
|
||||
// }
|
||||
// i.arrow-rotate {
|
||||
// transform: rotate(90deg) translate(2px, 3px);
|
||||
// }
|
||||
// .new-detection-filter-header-title {
|
||||
// font-size: 14px;
|
||||
// color: #353636;
|
||||
// font-weight: 600;
|
||||
// }
|
||||
// .new-detection-filter-icon {
|
||||
// margin-left: 8px;
|
||||
// margin-bottom: 2px;
|
||||
// font-weight: bold !important;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .filter__body {
|
||||
// padding: 5px 0 0 15px;
|
||||
//
|
||||
// .el-checkbox-group {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
//
|
||||
// .el-checkbox {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// padding: 5px 0;
|
||||
// margin-right: 5px;
|
||||
// .el-checkbox__label {
|
||||
// width: 100%;
|
||||
// }
|
||||
//
|
||||
// .filter__checkbox-label {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// align-items: center;
|
||||
//
|
||||
// .severity-color-block {
|
||||
// width: 4px;
|
||||
// height: 15px;
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// &:last-of-type {
|
||||
// padding-bottom: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .new-detection-filter-title {
|
||||
// display: flex;
|
||||
// flex: 0 0 32px;
|
||||
// align-items: center;
|
||||
// padding-left: 27px;
|
||||
// background-color: #EFF2F5;
|
||||
// cursor: pointer;
|
||||
// font-size: 14px;
|
||||
// color: #353636;
|
||||
// font-weight: 600;
|
||||
// margin: -10px;
|
||||
// margin-bottom: 10px;
|
||||
// }
|
||||
//}
|
||||
.detection-filter-title {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background: #F7F7F7;
|
||||
padding: 0 20px;
|
||||
box-shadow: 0 1px 0 0 rgba(226, 229, 236, 1);
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.detection-filter-content {
|
||||
padding: 20px;
|
||||
|
||||
.detection-filter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.filter__header {
|
||||
display: flex;
|
||||
flex: 0 0 32px;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
color: #666;
|
||||
//background-color: #F3F7FA;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
.filter-content-title {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
padding-left: 6px;
|
||||
line-height: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
}
|
||||
i {
|
||||
font-size: 12px;
|
||||
transition: all linear .1s;
|
||||
transform: rotate(0) translate(0, 2px);
|
||||
}
|
||||
i.arrow-rotate {
|
||||
transform: rotate(90deg) translate(2px, 3px);
|
||||
}
|
||||
.new-detection-filter-header-title {
|
||||
.filter-content-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.filter-content-checkbox {
|
||||
line-height: 16px;
|
||||
margin-bottom: 10px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 600;
|
||||
}
|
||||
.new-detection-filter-icon {
|
||||
margin-left: 8px;
|
||||
margin-bottom: 2px;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
font-weight: 400;
|
||||
|
||||
.el-checkbox__inner {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
text-align: center !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
|
||||
.filter__body {
|
||||
padding: 5px 0 0 15px;
|
||||
|
||||
.el-checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
margin-right: 5px;
|
||||
.el-checkbox__label {
|
||||
width: 100%;
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
background: #38ACD2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.filter__checkbox-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.severity-color-block {
|
||||
width: 4px;
|
||||
height: 15px;
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
|
||||
background: #FFFFFF;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.el-checkbox__input.is-checked {
|
||||
.el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
background: #38ACD2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-focus {
|
||||
.el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.new-detection-filter-title {
|
||||
display: flex;
|
||||
flex: 0 0 32px;
|
||||
align-items: center;
|
||||
padding-left: 27px;
|
||||
background-color: #EFF2F5;
|
||||
cursor: pointer;
|
||||
.el-checkbox__label {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 600;
|
||||
margin: -10px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
.detection-table {
|
||||
.el-table th > .cell, .el-table .cell {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
line-height: 16px;
|
||||
}
|
||||
.el-table--enable-row-transition .el-table__body td, .el-table--border th {
|
||||
border-left: 0 !important;
|
||||
border-right: 0 !important;
|
||||
font-size: 12px;
|
||||
color: #353636;
|
||||
}
|
||||
.el-table--border th {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-weight: 500;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-table__header-wrapper {
|
||||
height: 32px !important;
|
||||
line-height: 32px !important;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.el-table__body td {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
height: 32px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.detection-tag-blue, .detection-tag-red, .detection-tag-gray, .detection-tag-status0, .detection-tag-status1 {
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
padding: 1px 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.detection-tag-blue {
|
||||
background: rgba(56, 172, 210, 0.10);
|
||||
box-shadow: 0 2px 4px 0 rgba(51, 51, 51, 0.02);
|
||||
color: #046ECA;
|
||||
}
|
||||
|
||||
.detection-tag-red {
|
||||
background: rgba(226, 97, 84, 0.12);
|
||||
color: #E26154;
|
||||
}
|
||||
|
||||
.detection-tag-gray {
|
||||
background: rgba(113, 113, 113, 0.12);
|
||||
color: #717171;
|
||||
}
|
||||
|
||||
.detection-tag-status0 {
|
||||
font-weight: 500;
|
||||
font-family: NotoSansHans-Medium;
|
||||
background: rgba(113, 113, 113, 0.12);
|
||||
color: #717171;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.detection-tag-status1 {
|
||||
font-weight: 500;
|
||||
font-family: NotoSansHans-Medium;
|
||||
background: rgba(126, 159, 84, 0.12);
|
||||
color: #7E9F54;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.detection-table-library {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
color: #046ECA;
|
||||
font-weight: 400;
|
||||
}
|
||||
154
src/assets/css/components/views/detections/detection-tools.scss
Normal file
154
src/assets/css/components/views/detections/detection-tools.scss
Normal file
@@ -0,0 +1,154 @@
|
||||
.top-tools__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
.top-tool-btn {
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
border: 1px solid #DEDEDE;
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
background-color: #F9F9F9;
|
||||
transition: background-color linear .1s;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
//font-family: $fontFamily !important;
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #575757;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.66;
|
||||
|
||||
i {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn:hover:not(.cn-btn-disabled) {
|
||||
border: 1px solid #DEDEDE;
|
||||
background-color: #EBF1F4;
|
||||
}
|
||||
|
||||
.top-tool-btn:focus:not(.cn-btn-disabled), .top-tool-btn.is-focus {
|
||||
background-color: #E0E7EA;
|
||||
border: 1px solid #DEDEDE;
|
||||
|
||||
i {
|
||||
color: #575757;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn--delete.top-tool-btn:focus:not(.cn-btn-disabled) {
|
||||
background-color: #EBF1F4;
|
||||
border-color: #FFC4B9;
|
||||
|
||||
i {
|
||||
color: #F0745A;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn--create {
|
||||
background-color: #38ACD2 !important;
|
||||
border-color: #2E88A6 !important;
|
||||
color: #FFFFFF;
|
||||
|
||||
i {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn--create:hover {
|
||||
background-color: #57B8D9 !important;
|
||||
border-color: #2E88A6 !important;
|
||||
color: #FFFFFF;
|
||||
|
||||
i {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn--create:focus {
|
||||
background-color: #31A5CD !important;
|
||||
border-color: #2E88A6 !important;
|
||||
color: #FFFFFF !important;
|
||||
|
||||
i {
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn--create:disabled {
|
||||
opacity: 0.66;
|
||||
background-color: #38ACD2 !important;
|
||||
border-color: #2E88A6 !important;
|
||||
color: #FFFFFF;
|
||||
|
||||
i {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-search {
|
||||
display: flex;
|
||||
width: 242px; //calc(100% - 256px);
|
||||
.el-input--small {
|
||||
line-height: 27px;
|
||||
|
||||
.el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn {
|
||||
border-left: none;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
|
||||
.top-tool-btn--search:hover {
|
||||
border-left: none !important;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
|
||||
.top-tool-btn--search:focus {
|
||||
border-left: none !important;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.el-input--small {
|
||||
line-height: 27px;
|
||||
|
||||
.el-input__inner {
|
||||
height: 28px;
|
||||
border-radius: 2px 0 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-tool-btn {
|
||||
border-left: none;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
|
||||
.top-tool-btn--search:hover {
|
||||
border-left: none !important;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
|
||||
.top-tool-btn--search:focus {
|
||||
border-left: none !important;
|
||||
border-radius: 0 2px 2px 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
194
src/components/table/detection/GeneralSettings.vue
Normal file
194
src/components/table/detection/GeneralSettings.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="form-setting__block margin-b-20" style="display: flex">
|
||||
<div class="block-mode">
|
||||
<!--todo 图标没有,后期换-->
|
||||
<div class="block-mode-left">
|
||||
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="block-mode-right">
|
||||
<div class="block-mode-title">Indicator Match</div>
|
||||
<div class="block-mode-content">
|
||||
Use indicators from intelligencesources to detect matchingevents and alerts.
|
||||
</div>
|
||||
<div :class="settingObj.ruleType===detectionRuleType.indicator?'block-mode-btn-active':'block-mode-btn'"
|
||||
@click="selectMode(detectionRuleType.indicator)">select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-mode">
|
||||
<!--todo 图标没有,后期换-->
|
||||
<div class="block-mode-left">
|
||||
<i class="cn-icon cn-icon-setting2 block-mode-icon"></i>
|
||||
</div>
|
||||
|
||||
<div class="block-mode-right">
|
||||
<div class="block-mode-title">Threshold</div>
|
||||
<div class="block-mode-content">
|
||||
Aggregate query results to detect when number of matches exceeds threshold.
|
||||
</div>
|
||||
<div :class="settingObj.ruleType===detectionRuleType.threshold?'block-mode-btn-active':'block-mode-btn'"
|
||||
@click="selectMode(detectionRuleType.threshold)">select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--category-->
|
||||
<el-form ref="form" :model="settingObj" label-position="top" :rules="rules">
|
||||
<el-form-item label="Category" prop="category" class="form-setting__block margin-b-20">
|
||||
<el-select v-model="settingObj.category" class="form-setting__select" placeholder=" " size="mini" @change="changeEditFlag">
|
||||
<el-option
|
||||
v-for="item in categoryList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!--type-->
|
||||
<el-form-item label="Type" prop="eventType" class="form-setting__block margin-b-20">
|
||||
<el-select v-model="settingObj.eventType" placeholder=" " size="mini" class="form-setting__select" @change="changeEditFlag">
|
||||
<el-option
|
||||
v-for="item in typeList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!--name-->
|
||||
<el-form-item label="Name" prop="name" class="form-setting__block margin-b-20">
|
||||
<el-input
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
placeholder=""
|
||||
v-model="settingObj.name"
|
||||
@input="changeEditFlag"
|
||||
class="form-setting__input" />
|
||||
</el-form-item>
|
||||
|
||||
<!--Description-->
|
||||
<div class="form-setting__block margin-b-20">
|
||||
<div class="block-title">Description</div>
|
||||
<el-input
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
v-model="settingObj.description"
|
||||
type="textarea"
|
||||
resize='none'
|
||||
@input="changeEditFlag"
|
||||
class="form-setting__textarea" />
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div class="form-setting__block margin-b-20">
|
||||
<div class="block-title">Policy Status</div>
|
||||
<el-switch
|
||||
v-model="settingObj.status"
|
||||
@change="changeEditFlag"
|
||||
active-color="#38ACD2"
|
||||
inactive-color="#C0CEDB"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
:active-text="switchStatus(settingObj.status)"/>
|
||||
</div>
|
||||
|
||||
<div class="form-setting__btn">
|
||||
<el-button @click="onContinue">Continue</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { detectionRuleType } from '@/utils/constants'
|
||||
import { switchStatus } from '@/utils/tools'
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'GeneralSettings',
|
||||
data () {
|
||||
return {
|
||||
detectionRuleType,
|
||||
categoryList: [],
|
||||
typeList: [],
|
||||
settingObj: {
|
||||
ruleType: detectionRuleType.threshold,
|
||||
category: '',
|
||||
eventType: '',
|
||||
name: '',
|
||||
description: '',
|
||||
status: 1,
|
||||
editFlag: false, // 编辑标识,如果保存之后继续编辑且不再点击保存,置为true,则不会将编辑内容提交到最终form
|
||||
saveFlag: false // 是否点击保存标识
|
||||
},
|
||||
rules: {
|
||||
category: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
eventType: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
switchStatus,
|
||||
initData () {
|
||||
get(api.detection.statistics, { pageSize: -1 }).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.categoryList = response.data.categoryList || []
|
||||
this.typeList = response.data.typeList || []
|
||||
} else {
|
||||
console.error(response)
|
||||
}
|
||||
}).finally(() => {
|
||||
})
|
||||
},
|
||||
selectMode (ruleType) {
|
||||
this.settingObj.ruleType = ruleType
|
||||
this.changeEditFlag()
|
||||
},
|
||||
changeEditFlag () {
|
||||
this.settingObj.editFlag = true
|
||||
},
|
||||
/** 点击继续,进行第二步 */
|
||||
onContinue () {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
this.settingObj.editFlag = false
|
||||
this.settingObj.saveFlag = true
|
||||
this.$emit('setSettingForm', this.settingObj)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
234
src/components/table/detection/HistoryTopKeys.vue
Normal file
234
src/components/table/detection/HistoryTopKeys.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div class="history-top-key" v-if="myDrawer">
|
||||
<el-drawer v-model="myDrawer" :with-header="false">
|
||||
<div class="key-header">
|
||||
<div>History Top Keys</div>
|
||||
<i class="cn-icon cn-icon-close" @click="closeDrawer"></i>
|
||||
</div>
|
||||
|
||||
<div class="key-search">
|
||||
<el-input v-model="searchKey" @keyup.enter="onSearch" size="mini" placeholder="Search for">
|
||||
<template #prefix>
|
||||
<!--todo 该图标名称错误,已在iconfont修改,后续记得改过来-->
|
||||
<i class="cn-icon cn-icon-serach key-search-icon"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<i class="cn-icon cn-icon-refresh1 key-refresh" @click="onRefresh"></i>
|
||||
</div>
|
||||
|
||||
<div class="key-total">
|
||||
Total: <span class="key-total-number">{{ tableTotal }}</span>
|
||||
</div>
|
||||
|
||||
<div class="key-table">
|
||||
<loading :loading="loading"></loading>
|
||||
|
||||
<el-table :data="tableData" style="width: 100%" @row-click="rowClick">
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableTitle"
|
||||
:key="`col-${index}`"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
:min-width="`${item.minWidth}`"
|
||||
:prop="item.prop"
|
||||
:sort-orders="['ascending', 'descending']"
|
||||
:width="`${item.width}`"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ item.label }}</span>
|
||||
</template>
|
||||
<template #default="scope" :column="item">
|
||||
<template v-if="item.prop === 'metric'">
|
||||
<span>{{unitConvert(scope.row.metric, unitTypes.byte).join(' ')}}</span>
|
||||
</template>
|
||||
<template v-else-if="item.prop === 'last'">
|
||||
<span>{{dateFormatByAppearance(scope.row[item.prop])}}</span>
|
||||
</template>
|
||||
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<template v-slot:empty >
|
||||
<chart-error v-if="showError" :content="errorMsg" style="line-height: 0" />
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import unitConvert from '@/utils/unit-convert'
|
||||
import { unitTypes } from '@/utils/constants'
|
||||
import { dateFormatByAppearance } from '@/utils/date-util'
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
import Loading from '@/components/common/Loading'
|
||||
import ChartError from '@/components/common/Error'
|
||||
export default {
|
||||
name: 'HistoryTopKeys',
|
||||
props: {
|
||||
showDrawer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ChartError,
|
||||
Loading
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
myDrawer: false,
|
||||
searchKey: '',
|
||||
unitTypes,
|
||||
loading: false,
|
||||
tableTotal: 0,
|
||||
tableData: [],
|
||||
tableTitle: [
|
||||
{
|
||||
// label: this.$t('knowledge.status'),
|
||||
label: 'Keys',
|
||||
prop: 'keys',
|
||||
show: true,
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
label: 'Last Seen',
|
||||
prop: 'last',
|
||||
show: true,
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: 'Primary metric',
|
||||
prop: 'metric',
|
||||
show: true,
|
||||
minWidth: 120
|
||||
}
|
||||
],
|
||||
showError: false,
|
||||
errorMsg: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.myDrawer = this.showDrawer
|
||||
this.getTopKeysData()
|
||||
},
|
||||
methods: {
|
||||
unitConvert,
|
||||
dateFormatByAppearance,
|
||||
getTopKeysData (data) {
|
||||
this.loading = true
|
||||
const params = {}
|
||||
if (data) {
|
||||
// todo 具体入参按文档要求
|
||||
params.param = data
|
||||
}
|
||||
|
||||
get(api.detection.create.topKeys, params).then(res => {
|
||||
this.tableTotal = 0
|
||||
this.tableData = []
|
||||
|
||||
if (res.code === 200) {
|
||||
this.tableTotal = res.data.total
|
||||
this.tableData = res.data.list
|
||||
} else {
|
||||
this.httpError(res)
|
||||
}
|
||||
}).catch(err => {
|
||||
this.httpError(err)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 关闭topKeys弹窗 */
|
||||
closeDrawer () {
|
||||
this.myDrawer = false
|
||||
this.$emit('closeDrawer', false)
|
||||
},
|
||||
/** 单击topKeys弹窗某一项 */
|
||||
rowClick (data) {
|
||||
this.$emit('keyRowClick', data)
|
||||
},
|
||||
onRefresh () {
|
||||
this.getTopKeysData()
|
||||
},
|
||||
onSearch () {
|
||||
this.getTopKeysData(this.searchKey)
|
||||
},
|
||||
httpError (e) {
|
||||
this.showError = true
|
||||
this.errorMsg = this.errorMsgHandler(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.history-top-key {
|
||||
width: 396px;
|
||||
height: 520px;
|
||||
|
||||
.el-drawer__body {
|
||||
border: 1px #E2E5EC solid;
|
||||
}
|
||||
}
|
||||
.key-header {
|
||||
height: 41px;
|
||||
background: #F7F7F7;
|
||||
box-shadow: 0 1px 0 0 rgba(226,229,236,1);
|
||||
border-radius: 2px 2px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
color: #575757;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.key-search {
|
||||
margin-left: 12px;
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.key-search-icon {
|
||||
font-size: 13px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.key-refresh {
|
||||
margin: 0 10px;
|
||||
color: #38ACD2;
|
||||
font-size: 14px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.key-total {
|
||||
margin: 10px 12px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
line-height: 21px;
|
||||
font-weight: 400;
|
||||
|
||||
.key-total-number {
|
||||
color: #666666;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
488
src/components/table/detection/RuleDefinition.vue
Normal file
488
src/components/table/detection/RuleDefinition.vue
Normal file
@@ -0,0 +1,488 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="mySettingObj.ruleType===detectionRuleType.threshold" style="display: flex;justify-content: space-between;">
|
||||
<div>
|
||||
<el-form ref="form" :model="thresholdRuleObj" label-position="top" :rules="rules">
|
||||
<!--source-->
|
||||
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20">
|
||||
<el-select v-model="thresholdRuleObj.dataSource" placeholder=" " size="mini" class="form-setting__select">
|
||||
<el-option
|
||||
v-for="item in sourceList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!--Dimensions-->
|
||||
<div class="form-setting__block margin-b-20">
|
||||
<div class="block-title">Dimensions</div>
|
||||
<div class="block-dimension">
|
||||
<div class="block-dimension-tag" v-for="(ite, ind) in dimensionList" :key="ind">{{ ite.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-setting__block form-setting__block-key">
|
||||
<div>Key Selection</div>
|
||||
<div class="block-key">
|
||||
<!--todo 图标暂无,需要更换-->
|
||||
<i class="cn-icon cn-icon-shijianjihua"></i>
|
||||
<span @click="showDrawer=true">History Top Keys</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Filters模块-->
|
||||
<div class="form-setting__block margin-b-20">
|
||||
<div class="block-title1">{{ $t('detections.filters') }}</div>
|
||||
<div class="definition-filter-block" v-if="showFilter">
|
||||
<div class="definition-filter-item" v-for="(item, index) in thresholdRuleObj.filterList" :key="index">
|
||||
<el-select class="filter-item__select margin-r-8" v-model="item.filter" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in selectList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input class="filter-item__input margin-r-8" size="mini" disabled placeholder="equal"></el-input>
|
||||
<el-input
|
||||
class="filter-item__input margin-r-8"
|
||||
size="mini"
|
||||
oninput="value=value.replace(/[^\d]/g,'')"
|
||||
v-model="item.value"></el-input>
|
||||
<i class="cn-icon cn-icon-close" @click="delFilterItem(index)"></i>
|
||||
</div>
|
||||
|
||||
<div style="height: 10px;"></div>
|
||||
<div class="filter-block-footer">
|
||||
<i class="cn-icon cn-icon-add" @click="addFilter"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="block-filter-add" @click="addFilter">+</div>
|
||||
</div>
|
||||
|
||||
<!--Condition模块-->
|
||||
<div class="form-setting__block margin-b-20">
|
||||
<div class="block-title">Condition</div>
|
||||
|
||||
<el-form ref="form2" :model="thresholdRuleObj" label-position="top">
|
||||
<div class="definition-condition-block" v-for="(item, index) in thresholdRuleObj.conditionData" :key="index">
|
||||
<el-form-item :label="$t('detection.level')" :prop="`conditionData.${index}.level`" :rules="rules.level">
|
||||
<el-select class="condition__select margin-b-20" v-model="item.level" placeholder=" " size="mini">
|
||||
<template #prefix>
|
||||
<div
|
||||
class="condition__select__icon"
|
||||
:style="{background: eventSeverityColor[item.level]}"></div>
|
||||
</template>
|
||||
|
||||
<el-option
|
||||
v-for="item in levelList"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div class="condition-metric" v-for="(data, i) in item.list" :key="i">
|
||||
<div class="condition-metric-item1">
|
||||
<div class="metric-item1__text">
|
||||
<span style="margin-right: 9px;">If</span>
|
||||
<el-form-item :prop="`conditionData.${index}.list.${i}.metric`" :rules="rules.metric">
|
||||
<el-select v-model="data.metric" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in metricList"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<span style="margin-left: 9px;">of keys</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class="cn-icon cn-icon-add" @click="addConditionItem(index)"></i>
|
||||
<i
|
||||
class="cn-icon cn-icon-close"
|
||||
:class="delConditionDisabled ? 'metric-item1-close-disable' : 'metric-item1-close'"
|
||||
@click="delConditionItem(index, i)">
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="condition-metric-item2">
|
||||
<div style="height: 24px;line-height: 24px;display: flex;">
|
||||
<el-form-item :prop="`conditionData.${index}.list.${i}.condition`" :rules="rules.condition">
|
||||
<el-select class="metric-item2__select" v-model="data.condition" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in conditionList"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :prop="`conditionData.${index}.list.${i}.value`" :rules="rules.value">
|
||||
<!--todo 应当添加长度限制-->
|
||||
<el-input
|
||||
class="metric-item2__input"
|
||||
v-model.number="data.value"
|
||||
oninput="value=value.replace(/[^\d]/g,'')"
|
||||
size="mini"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<span>{{ data.metric }}</span>
|
||||
</div>
|
||||
<el-divider v-if="item.list.length - 1 > i" class="condition-divider">and</el-divider>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div class="condition-add" @click="addCondition">
|
||||
<i class="cn-icon cn-icon-add"></i>Add Condition
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--History Top Keys弹窗-->
|
||||
<div class="key-drawer">
|
||||
<history-top-keys
|
||||
v-if="showDrawer"
|
||||
:showDrawer="showDrawer"
|
||||
@closeDrawer="onCloseDrawer"
|
||||
@keyRowClick="getRowClick"
|
||||
></history-top-keys>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="mySettingObj.ruleType===detectionRuleType.indicator">
|
||||
<el-form ref="form" :model="indicatorRuleObj" label-position="top" :rules="rules">
|
||||
<!--Source-->
|
||||
<el-form-item label="Source" prop="dataSource" class="form-setting__block margin-b-20">
|
||||
<el-select v-model="indicatorRuleObj.dataSource" class="form-setting__select" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in sourceList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!--Library-->
|
||||
<el-form-item label="Library" prop="knowledgeId" class="form-setting__block margin-b-20">
|
||||
<el-select v-model="indicatorRuleObj.knowledgeId" class="form-setting__select" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in libraryList"
|
||||
:key="item.knowledgeId"
|
||||
:label="item.label"
|
||||
:value="item.knowledgeId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!--Level-->
|
||||
<el-form-item :label="$t('detection.level')" prop="level" class="form-setting__block">
|
||||
<el-select v-model="indicatorRuleObj.level" class="condition__select form-setting__select" placeholder=" " size="mini">
|
||||
<template #prefix>
|
||||
<div
|
||||
class="condition__select__icon"
|
||||
:style="{background: eventSeverityColor[indicatorRuleObj.level]}"></div>
|
||||
</template>
|
||||
|
||||
<el-option
|
||||
v-for="item in levelList"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="form-setting__btn">
|
||||
<el-button @click="onContinue">Continue</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
import HistoryTopKeys from '@/components/table/detection/HistoryTopKeys'
|
||||
import { eventSeverityColor, detectionRuleType } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'RuleDefinition',
|
||||
props: {
|
||||
settingObj: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
HistoryTopKeys
|
||||
},
|
||||
watch: {
|
||||
settingObj: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (newVal, oldVal) {
|
||||
if (!newVal.editFlag && newVal.saveFlag) {
|
||||
this.mySettingObj = JSON.parse(JSON.stringify(newVal))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
eventSeverityColor,
|
||||
detectionRuleType,
|
||||
mySettingObj: {
|
||||
ruleType: detectionRuleType.threshold
|
||||
},
|
||||
dimensionList: [], // Dimensions数据
|
||||
// ruleType为Indicator时表单数据
|
||||
indicatorRuleObj: {
|
||||
dataSource: '',
|
||||
knowledgeId: '',
|
||||
level: ''
|
||||
},
|
||||
// ruleType为Threshold时表单数据
|
||||
thresholdRuleObj: {
|
||||
dataSource: '',
|
||||
dimensionKeys: '',
|
||||
filterList: [],
|
||||
filters: '', // filter提交到接口的数据,即filterList转化为字符串
|
||||
conditionData: [
|
||||
{
|
||||
level: '',
|
||||
list: [
|
||||
{ metric: '', condition: '', value: '' }
|
||||
]
|
||||
}
|
||||
],
|
||||
conditions: {} // filter提交到接口的数据,即filterList转化为字符串
|
||||
},
|
||||
rules: {
|
||||
dataSource: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
level: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
metric: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
condition: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
knowledgeId: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
sourceList: [], // source下拉列表数据
|
||||
// rule的policy创建信息
|
||||
showDrawer: false, // 显示History Top Keys抽屉弹框
|
||||
showFilter: false, // 显示filter筛选框,点击add显示
|
||||
selectList: [], // filter的第一个下拉列表
|
||||
levelList: [], // condition的level下拉列表
|
||||
metricList: [],
|
||||
conditionList: [],
|
||||
libraryList: [],
|
||||
delConditionDisabled: true, // condition删除标识,true表示禁止删除,即只有一个condition时
|
||||
unitObj: {
|
||||
than: '>',
|
||||
less: '<',
|
||||
equal: '='
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initData()
|
||||
|
||||
// todo 调用接口进行赋值
|
||||
this.dimensionList = [
|
||||
{ label: 'Destination IP/CIDR', value: 'Destination IP/CIDR' },
|
||||
{ label: 'Source Port Number', value: 'Source Port Number' }
|
||||
]
|
||||
},
|
||||
methods: {
|
||||
initData () {
|
||||
get(api.detection.statistics, { pageSize: -1 }).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.sourceList = response.data.sourceList || []
|
||||
this.levelList = response.data.levelList || []
|
||||
this.conditionList = response.data.conditionList || []
|
||||
this.metricList = response.data.metricList || []
|
||||
this.libraryList = response.data.libraryList || []
|
||||
} else {
|
||||
console.error(response)
|
||||
}
|
||||
}).finally(() => {
|
||||
})
|
||||
},
|
||||
/** 单击History Top Keys列表某行,filter添加数据 */
|
||||
getRowClick (data) {
|
||||
this.addFilter(data)
|
||||
},
|
||||
/** 关闭History Top Keys弹框 */
|
||||
onCloseDrawer () {
|
||||
this.showDrawer = false
|
||||
},
|
||||
/** filter模块点击add按钮 */
|
||||
addFilter (data) {
|
||||
this.showFilter = true
|
||||
if (this.selectList.length === 0) {
|
||||
this.getFilterList()
|
||||
}
|
||||
|
||||
// 添加数据前,先删除空白项
|
||||
const delIndex = this.thresholdRuleObj.filterList.findIndex(t => t.filter === '')
|
||||
if (delIndex > -1) {
|
||||
this.thresholdRuleObj.filterList.splice(delIndex, 1)
|
||||
}
|
||||
|
||||
if (data.metric) {
|
||||
// 从key弹框添加数据
|
||||
const obj = this.thresholdRuleObj.filterList.find(t => t.keyId === data.keyId)
|
||||
if (!obj) {
|
||||
this.thresholdRuleObj.filterList.push({ keyId: data.keyId, filter: this.selectList[0].label, value: data.metric })
|
||||
}
|
||||
} else {
|
||||
// 手动添加
|
||||
this.thresholdRuleObj.filterList.push({ filter: '', value: '' })
|
||||
}
|
||||
},
|
||||
/** 添加condition大模块 */
|
||||
addCondition () {
|
||||
this.$refs.form2.validate(valid => {
|
||||
if (valid) {
|
||||
// 如果选择了level,则禁用这一条level,不允许再选择,除非删除才能选择
|
||||
this.levelList.forEach(item => {
|
||||
const obj = this.thresholdRuleObj.conditionData.find(t => t.level === item.value)
|
||||
if (obj) {
|
||||
item.disabled = true
|
||||
}
|
||||
})
|
||||
this.thresholdRuleObj.conditionData.push({ level: '', list: [{ metric: '', condition: '', value: '' }] })
|
||||
this.delConditionDisabled = false
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 添加condition小模块 */
|
||||
addConditionItem (index) {
|
||||
this.$refs.form2.validate(valid => {
|
||||
if (valid) {
|
||||
this.thresholdRuleObj.conditionData[index].list.push({ metric: '', condition: '', value: '' })
|
||||
this.delConditionDisabled = false
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 删除condition,小模块只剩一个时点击删除,则删除大模块
|
||||
* 只有一个大模块时,只剩一个小模块则不可删除
|
||||
* */
|
||||
delConditionItem (index, i) {
|
||||
if (!this.delConditionDisabled) {
|
||||
const dataLen = this.thresholdRuleObj.conditionData.length
|
||||
const listLen = this.thresholdRuleObj.conditionData[index].list.length
|
||||
if (dataLen === 1) {
|
||||
if (listLen >= 2) {
|
||||
this.thresholdRuleObj.conditionData[index].list.splice(i, 1)
|
||||
}
|
||||
|
||||
if (this.thresholdRuleObj.conditionData[index].list.length === 1) {
|
||||
this.delConditionDisabled = true
|
||||
}
|
||||
} else {
|
||||
if (listLen === 1) {
|
||||
this.thresholdRuleObj.conditionData.splice(index, 1)
|
||||
} else {
|
||||
this.thresholdRuleObj.conditionData[index].list.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/** 获取filter模块下拉列表 */
|
||||
getFilterList () {
|
||||
// todo 请求接口
|
||||
this.selectList = [
|
||||
{ value: 'Destination As Number', label: 'Destination As Number' },
|
||||
{ value: 'Destination As String', label: 'Destination As String' }
|
||||
]
|
||||
},
|
||||
/** 删除filter某一项 */
|
||||
delFilterItem (i) {
|
||||
this.thresholdRuleObj.filterList.splice(i, 1)
|
||||
},
|
||||
/** 点击继续,展开第三步 */
|
||||
onContinue () {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) {
|
||||
if (this.mySettingObj.ruleType === detectionRuleType.indicator) {
|
||||
// 第一步模式选择Indicator Match
|
||||
this.$emit('setRuleObj', this.indicatorRuleObj)
|
||||
} else {
|
||||
// 第一步模式选择Threshold
|
||||
this.$refs.form2.validate(valid2 => {
|
||||
if (valid2) {
|
||||
this.getConditions()
|
||||
this.$emit('setRuleObj', this.thresholdRuleObj)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 将condition的数组转换为入参需要的字符串 */
|
||||
getConditions () {
|
||||
const conditionData = this.thresholdRuleObj.conditionData
|
||||
const obj = {}
|
||||
|
||||
conditionData.forEach(item => {
|
||||
obj[item.level] = ''
|
||||
let str = ''
|
||||
item.list.forEach(t => {
|
||||
str = str + t.metric + ' ' + this.unitObj[t.condition] + ' ' + t.value + ' && '
|
||||
})
|
||||
str = str.substring(0, str.length - 4)
|
||||
obj[item.level] = str
|
||||
})
|
||||
this.thresholdRuleObj.conditions = obj
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -118,6 +118,43 @@ export default {
|
||||
if (this.listUrl) {
|
||||
listUrl = this.listUrl
|
||||
}
|
||||
// todo 此段是为了避免mock没开启,打开detection界面报错提示,后续再开发detection时删除
|
||||
if (listUrl === api.detection.list) {
|
||||
const list = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const obj = {
|
||||
ruleId: 100000 + i,
|
||||
ruleType: 'indicator_match',
|
||||
status: 1,
|
||||
name: 'name123',
|
||||
category: 'Security Event',
|
||||
eventType: 'C&C',
|
||||
description: 'Built-in darkweb IoC',
|
||||
ruleConfig: {
|
||||
knowledge: {
|
||||
name: 'VPN Server IP',
|
||||
category: 'user_defined'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i % 2 === 0) {
|
||||
obj.ruleType = 'threshold'
|
||||
obj.ruleConfig = {
|
||||
dimensions: 'Destination IP/CIDR'
|
||||
}
|
||||
obj.description = 'abuse.ch is providing community driven threat intelligence on \n' +
|
||||
'cyber threats. It is the home of a couple of projects that are \n' +
|
||||
'helping internet service providers and network operators protect …'
|
||||
} else {
|
||||
obj.status = 0
|
||||
}
|
||||
list.push(obj)
|
||||
}
|
||||
|
||||
this.tableData = list
|
||||
this.pageObj.total = list.length
|
||||
this.loading = false
|
||||
} else {
|
||||
get(listUrl, this.searchLabel).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.tableData = response.data.list
|
||||
@@ -140,7 +177,9 @@ export default {
|
||||
this.isNoData = true
|
||||
}).finally(() => {
|
||||
this.toggleLoading(false)
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
del (row) {
|
||||
this.$confirm(this.$t('tip.confirmDelete'), {
|
||||
@@ -396,7 +435,7 @@ export default {
|
||||
tableData: {
|
||||
deep: true,
|
||||
handler (n) {
|
||||
if (n.length === 0 && this.pageObj.pageNo > 1) {
|
||||
if (n && n.length === 0 && this.pageObj.pageNo > 1) {
|
||||
this.pageNo(this.pageObj.pageNo - 1)
|
||||
}
|
||||
// TODO 不是删除时回到顶部
|
||||
|
||||
167
src/mock/detection.js
Normal file
167
src/mock/detection.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import Mock from 'mockjs'
|
||||
|
||||
const urlAndVersion = BASE_CONFIG.baseUrl + BASE_CONFIG.apiVersion
|
||||
const openMock = true
|
||||
if (openMock) {
|
||||
Mock.mock(new RegExp(urlAndVersion + '/rule/detection/list.*'), 'get', function (requestObj) {
|
||||
const list = []
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const obj = {
|
||||
ruleId: 100000 + i,
|
||||
ruleType: 'indicator_match',
|
||||
status: 1,
|
||||
name: 'name123',
|
||||
category: 'Security Event',
|
||||
eventType: 'C&C',
|
||||
description: 'Built-in darkweb IoC',
|
||||
ruleConfig: {
|
||||
knowledge: {
|
||||
name: 'VPN Server IP',
|
||||
category: 'user_defined'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i % 2 === 0) {
|
||||
obj.ruleType = 'threshold'
|
||||
obj.ruleConfig = {
|
||||
dimensions: 'Destination IP/CIDR'
|
||||
}
|
||||
obj.description = 'abuse.ch is providing community driven threat intelligence on \n' +
|
||||
'cyber threats. It is the home of a couple of projects that are \n' +
|
||||
'helping internet service providers and network operators protect …'
|
||||
} else {
|
||||
obj.status = 0
|
||||
}
|
||||
list.push(obj)
|
||||
}
|
||||
const data = {
|
||||
total: list.length,
|
||||
pageSize: 20,
|
||||
pageNo: 1,
|
||||
list: list
|
||||
}
|
||||
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: data
|
||||
}
|
||||
})
|
||||
Mock.mock(new RegExp(urlAndVersion + '/detection/statistics.*'), 'get', function (requestObj) {
|
||||
const data = {
|
||||
statusList: [
|
||||
{ status: 1 },
|
||||
{ status: 0 }
|
||||
],
|
||||
categoryList: [
|
||||
{ value: 'security', label: 'Security Event' },
|
||||
{ value: 'performance', label: 'Performance Event' },
|
||||
{ value: 'regulatory_risk', label: 'Regulatory Risk Event' }
|
||||
],
|
||||
typeList: [
|
||||
{ value: 'c&c', label: 'C&C' },
|
||||
{ value: 'ddos', label: 'DDos' },
|
||||
{ value: 'lateral_movement', label: 'Lateral movement' },
|
||||
{ value: 'brute_force', label: 'Brute force' }
|
||||
],
|
||||
sourceList: [
|
||||
{ value: 'ip_metric', label: 'IP metric' },
|
||||
{ value: 'performance_event', label: 'performance event' }
|
||||
],
|
||||
levelList: [
|
||||
{ value: 'critical', label: 'Critical' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'info', label: 'Info' }
|
||||
],
|
||||
metricList: [
|
||||
{ value: 'tcp_lostlen_ratio', label: 'Bits/second' },
|
||||
{ value: 's2c_byte_retrans_ratio', label: 'Packets/second' },
|
||||
{ value: 's2c_byte_retrans_ratio1', label: 'Sessions/second' }
|
||||
],
|
||||
conditionList: [
|
||||
{ value: 'than', label: 'Greater Than' },
|
||||
{ value: 'less', label: 'Greater Less' },
|
||||
{ value: 'equal', label: 'Greater Equal' }
|
||||
],
|
||||
libraryList: [
|
||||
{ value: 'library name2', knowledgeId: '101', label: 'Library name' },
|
||||
{ value: 'library name1', knowledgeId: '102', label: 'Library name1' },
|
||||
{ value: 'library name2', knowledgeId: '103', label: 'Library name2' }
|
||||
],
|
||||
intervalList: [
|
||||
{ value: 'minutes', label: 'minutes' },
|
||||
{ value: 'hours', label: 'hours' },
|
||||
{ value: 'days', label: 'days' },
|
||||
{ value: 'weeks', label: 'weeks' }
|
||||
]
|
||||
}
|
||||
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: data
|
||||
}
|
||||
})
|
||||
Mock.mock(new RegExp(urlAndVersion + '/detection/topKeys.*'), 'get', function (requestObj) {
|
||||
const list = [
|
||||
{ keyId: '10000001', keys: '192.168.40.54', last: 1690266188, metric: 181440000000 },
|
||||
{ keyId: '10000002', keys: '192.168.40.55', last: 1690266188, metric: 161440000000 },
|
||||
{ keyId: '10000003', keys: '192.168.40.56', last: 1690266188, metric: 181440000000 },
|
||||
{ keyId: '10000004', keys: '192.168.40.57', last: 1690266188, metric: 171440000000 },
|
||||
{ keyId: '10000005', keys: '192.168.40.58', last: 1690266188, metric: 171440000000 },
|
||||
{ keyId: '10000006', keys: '192.168.40.59', last: 1690266188, metric: 187440000000 },
|
||||
{ keyId: '10000007', keys: '192.168.40.60', last: 1690266188, metric: 181440000000 }
|
||||
]
|
||||
const data = {
|
||||
list: list,
|
||||
total: list.length
|
||||
}
|
||||
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: data
|
||||
}
|
||||
})
|
||||
Mock.mock(new RegExp(urlAndVersion + '/rule/detection.*'), 'get', function (requestObj) {
|
||||
const ruleId = getLastValue(requestObj.url)
|
||||
const data = {
|
||||
name: 'name123',
|
||||
category: 'Security Event',
|
||||
ruleType: 'indicator_match',
|
||||
eventType: 'C&C',
|
||||
description: 'Built-in darkweb IoC',
|
||||
status: 1,
|
||||
ruleConfig: {
|
||||
dataSource: 'VPN Server IP',
|
||||
knowledgeId: 10,
|
||||
level: 10
|
||||
},
|
||||
trigger: {
|
||||
atLeast: 1,
|
||||
interval: 'PT5M',
|
||||
resetInterval: 'PT10M'
|
||||
}
|
||||
}
|
||||
|
||||
if (ruleId % 2 === 0) {
|
||||
data.ruleType = 'threshold'
|
||||
data.status = 1
|
||||
} else {
|
||||
data.status = 0
|
||||
}
|
||||
|
||||
return {
|
||||
msg: 'success',
|
||||
code: 200,
|
||||
data: data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getLastValue = (url) => {
|
||||
const index = url.lastIndexOf('/')
|
||||
return url.substring(index + 1)
|
||||
}
|
||||
@@ -2,3 +2,4 @@ import './npm'
|
||||
import './linkMonitor'
|
||||
import './dns'
|
||||
import './entity'
|
||||
import './detection'
|
||||
|
||||
@@ -96,6 +96,18 @@ const routes = [
|
||||
name: 'Chart',
|
||||
path: '/chart',
|
||||
component: () => import('@/views/administration/Chart')
|
||||
},
|
||||
{
|
||||
path: '/detectionsNew',
|
||||
component: () => import('@/views/detectionsNew/Index')
|
||||
},
|
||||
{
|
||||
path: '/detection/policies',
|
||||
component: () => import('@/views/detectionsNew/Index')
|
||||
},
|
||||
{
|
||||
path: '/detection/policies/create',
|
||||
component: () => import('@/views/detectionsNew/DetectionForm')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -136,6 +136,16 @@ export const api = {
|
||||
dnsErrorMetric: '/interface/detection/performance/detail/overview/metric/dnsError',
|
||||
httpErrorMetric: '/interface/detection/performance/detail/overview/metric/httpError',
|
||||
highDnsResponseTimeMetric: '/interface/detection/performance/detail/overview/metric/highDnsResponseTime'
|
||||
},
|
||||
list: apiVersion + '/rule/detection/list', // 检测规则列表
|
||||
detail: apiVersion + '/rule/detection', // 检测规则详情
|
||||
delete: apiVersion + '/rule', // 检测规则删除
|
||||
// 获取单位列表,如source、type、metric等
|
||||
statistics: apiVersion + '/detection/statistics',
|
||||
// 规则新建模块
|
||||
create: {
|
||||
topKeys: apiVersion + '/detection/topKeys', // topKeys列表
|
||||
create: apiVersion + '/rule/detection/create' // todo 规则新建编辑,此api为模拟,后续需要修改
|
||||
}
|
||||
},
|
||||
// Dashboard
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1300,3 +1300,17 @@ export function numberWithCommas (num) {
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态编码0,1转化为Disabled,Enabled
|
||||
* @param status
|
||||
* @returns {string}
|
||||
*/
|
||||
export function switchStatus (status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'Disabled'
|
||||
case 1:
|
||||
return 'Enabled'
|
||||
}
|
||||
}
|
||||
|
||||
173
src/views/detectionsNew/DetectionDrawer.vue
Normal file
173
src/views/detectionsNew/DetectionDrawer.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div class="detection-drawer" style="height: 100vh;overflow: scroll;padding-bottom: 90px">
|
||||
<div class="drawer-basic">
|
||||
<div class="drawer-basic-header">
|
||||
<div class="drawer-basic-id">ID: {{ drawerInfo.ruleId }}</div>
|
||||
<div :class="`detection-tag-status${detailData.status}`">
|
||||
{{ switchStatus(detailData.status) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Name</div>
|
||||
<div class="basic-function-value">{{ detailData.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Type</div>
|
||||
<div class="basic-function-value">{{ detailData.eventType }}</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-description">
|
||||
<div class="detection-drawer-title">Description</div>
|
||||
<div class="basic-description-value">{{ detailData.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detection-drawer-collapse">
|
||||
<el-collapse v-model="activeRule">
|
||||
<el-collapse-item title="Rule Definitcm" name="rule">
|
||||
<div class="drawer-collapse-content">
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Source</div>
|
||||
<div class="basic-function-value">{{ detailData.category }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detailData.ruleType==='indicator_match'">
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Library</div>
|
||||
<span class="basic-function-value">{{ detailData.library }}</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Level</div>
|
||||
<div class="detection-drawer-title">
|
||||
<div class="detection__icon" :style="`background-color: ${eventSeverityColor['critical']}`"></div>
|
||||
<div class="basic-function-value">Critical</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Dimensions</div>
|
||||
<span class="detection-tag-blue">{{ detailData.dimensions }}</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Filters</div>
|
||||
<span class="detection-tag-blue">source</span>
|
||||
<span style="margin: 0 6px;">equal</span><span>19890</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function" v-for="item in severityList" :key="item.severity"
|
||||
style="padding-bottom: 0">
|
||||
<div class="detection-drawer-title">
|
||||
<div class="detection__icon" :style="`background-color: ${eventSeverityColor[item.severity]}`"></div>
|
||||
<div>{{ toUpperCaseByString(item.severity) }}</div>
|
||||
</div>
|
||||
<div class="detection-drawer-title">Conditions</div>
|
||||
<div>
|
||||
<div class="detection-tag-gray margin-r-10">> 60 Kpackets/s</div>
|
||||
<div class="detection-tag-gray">> 50 Unique Src IPs</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<div class="detection-drawer-collapse" style="margin: 20px 0">
|
||||
<el-collapse v-model="activeTrigger">
|
||||
<el-collapse-item title="Trigger" name="trigger">
|
||||
<div class="drawer-collapse-content">
|
||||
<div class="drawer-collapse-trigger">
|
||||
Triggered when conditions occur at least
|
||||
<span style="color: #046ECA" v-if="detailData.trigger">
|
||||
{{ detailData.trigger.atLeast }} time
|
||||
</span> in
|
||||
<span style="color: #046ECA" v-if="detailData.trigger">
|
||||
<!--todo 此处返回的是PT5M,具体时间处理根据后续字段来看-->
|
||||
{{ getNumberFromStr(detailData.trigger.interval) }} minutes
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="drawer-basic-function">
|
||||
<div class="detection-drawer-title">Evaluation Frequency</div>
|
||||
<div class="basic-function-value" v-if="detailData.trigger">{{ getNumberFromStr(detailData.trigger.resetInterval) }} minutes</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { switchStatus, toUpperCaseByString } from '@/utils/tools'
|
||||
import { eventSeverityColor } from '@/utils/constants'
|
||||
import axios from '_axios@0.21.4@axios'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'DetectionDrawer',
|
||||
props: {
|
||||
drawerInfo: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
activeRule: 'rule',
|
||||
activeTrigger: 'trigger',
|
||||
detailData: {},
|
||||
eventSeverityColor,
|
||||
severityList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
drawerInfo: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (n) {
|
||||
if (n) {
|
||||
this.getDetailData()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchStatus,
|
||||
toUpperCaseByString,
|
||||
getDetailData () {
|
||||
this.severityList = [
|
||||
{
|
||||
severity: 'critical',
|
||||
list: ['> 60 Kpackets/s', '> 50 Unique Src IPs']
|
||||
},
|
||||
{
|
||||
severity: 'high',
|
||||
list: ['> 20 Kpackets/s', '> 50 Unique Src IPs']
|
||||
}
|
||||
]
|
||||
|
||||
axios.get(`${api.detection.detail}/${this.drawerInfo.ruleId}`).then(res => {
|
||||
if (res.data.code === 200) {
|
||||
this.detailData = res.data.data
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
getNumberFromStr (str) {
|
||||
return str.match(/\d+(\.\d+)?/g)[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
215
src/views/detectionsNew/DetectionFilter.vue
Normal file
215
src/views/detectionsNew/DetectionFilter.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="detection-filter-title">
|
||||
{{ $t('detections.filters') }}
|
||||
</div>
|
||||
<div class="detection-filter-content">
|
||||
<div>
|
||||
<div class="filter-content-title">{{ $t('overall.status') }}</div>
|
||||
<div class="filter-content-content">
|
||||
<el-checkbox-group v-model="checkStatus" @change="onChangeCategory" style="display: flex;flex-direction: column">
|
||||
<el-checkbox v-for="item in statusList" :key="item.label" class="filter-content-checkbox" :label="item.status">
|
||||
<div>{{ item.label }}</div>
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="filter-content-title">{{ $t('overall.category') }}</div>
|
||||
<div class="filter-content-content">
|
||||
<el-checkbox-group v-model="checkCategory" @change="onChangeCategory">
|
||||
<el-checkbox v-for="item in categoryList" :key="item.value" class="filter-content-checkbox" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="filter-content-title">{{ $t('overall.type') }}</div>
|
||||
<div class="filter-content-content">
|
||||
<el-checkbox-group v-model="checkType" @change="onChangeCategory" style="display: flex;flex-direction: column">
|
||||
<el-checkbox v-for="item in typeList" :key="item.value" class="filter-content-checkbox" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'DetectionFilter',
|
||||
data () {
|
||||
return {
|
||||
statusList: [],
|
||||
categoryList: [],
|
||||
typeList: [],
|
||||
checkStatus: [],
|
||||
checkCategory: [],
|
||||
checkType: [],
|
||||
url: api.detection.statistics
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 开发时删除---start
|
||||
this.statusList = [
|
||||
{ status: 1, label: 'Enabled' },
|
||||
{ status: 0, label: 'Disabled' }
|
||||
]
|
||||
this.checkStatus = [0, 1]
|
||||
this.categoryList = [
|
||||
{ value: 'security', label: 'Security Event' },
|
||||
{ value: 'performance', label: 'Performance Event' },
|
||||
{ value: 'regulatory_risk', label: 'Regulatory Risk Event' }
|
||||
]
|
||||
this.checkCategory = ['security', 'performance', 'regulatory_risk']
|
||||
this.typeList = [
|
||||
{ value: 'c&c', label: 'C&C' },
|
||||
{ value: 'ddos', label: 'DDos' },
|
||||
{ value: 'lateral_movement', label: 'Lateral movement' },
|
||||
{ value: 'brute_force', label: 'Brute force' }
|
||||
]
|
||||
this.checkType = ['c&c', 'ddos', 'lateral_movement', 'brute_force']
|
||||
// 开发时删除---end
|
||||
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// this.getFilterData()
|
||||
},
|
||||
methods: {
|
||||
getFilterData (params) {
|
||||
let searchParams = { pageSize: -1 }
|
||||
if (params) {
|
||||
searchParams = { ...searchParams, ...params }
|
||||
}
|
||||
get(this.url, searchParams).then(response => {
|
||||
if (response.code === 200) {
|
||||
if (response.data.statusList) {
|
||||
this.statusList = []
|
||||
response.data.statusList.forEach(item => {
|
||||
this.statusList.push({ status: item.status, label: this.switchStatus(item.status) })
|
||||
this.checkStatus.push(item.status)
|
||||
})
|
||||
}
|
||||
|
||||
this.categoryList = response.data.categoryList
|
||||
if (response.data.categoryList) {
|
||||
response.data.categoryList.forEach(item => {
|
||||
this.checkCategory.push(item.value)
|
||||
})
|
||||
}
|
||||
|
||||
this.typeList = response.data.typeList
|
||||
if (response.data.typeList) {
|
||||
response.data.typeList.forEach(item => {
|
||||
this.checkType.push(item.value)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.error(response)
|
||||
}
|
||||
}).finally(() => {
|
||||
// this.initTypeData()
|
||||
// this.initStatusData()
|
||||
// const self = this
|
||||
// this.$nextTick(() => {
|
||||
// if (self.$refs.knowledgeTreeTypeFilter) {
|
||||
// self.$refs.knowledgeTreeTypeFilter.setCheckedKeys(this.defaultCheckedCategory)
|
||||
// }
|
||||
// if (self.$refs.knowledgeTreeStatusFilter) {
|
||||
// self.$refs.knowledgeTreeStatusFilter.setCheckedKeys(this.defaultCheckedStatus)
|
||||
// }
|
||||
// })
|
||||
})
|
||||
},
|
||||
onChangeCategory (data) {
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// 根据选择的值,构造不同入参,传给列表页,调用查询列表接口
|
||||
},
|
||||
switchStatus (status) {
|
||||
switch (status) {
|
||||
case 0: return 'Disabled'
|
||||
case 1: return 'Enabled'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detection-filter-title {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background: #F7F7F7;
|
||||
padding: 0 20px;
|
||||
box-shadow: 0 1px 0 0 rgba(226, 229, 236, 1);
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.detection-filter-content {
|
||||
padding: 20px;
|
||||
|
||||
.filter-content-title {
|
||||
font-family: NotoSansHans-Medium;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: #353636;
|
||||
font-weight: 500;
|
||||
}
|
||||
.filter-content-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.filter-content-checkbox {
|
||||
line-height: 16px;
|
||||
margin-bottom: 10px;
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
|
||||
.el-checkbox__inner {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
text-align: center !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
background: #38ACD2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
|
||||
background: #FFFFFF;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.el-checkbox__input.is-checked {
|
||||
.el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
background: #38ACD2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.el-checkbox__input.is-focus {
|
||||
.el-checkbox__inner {
|
||||
border-color: #38ACD2;
|
||||
}
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 14px;
|
||||
color: #353636;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
251
src/views/detectionsNew/DetectionForm.vue
Normal file
251
src/views/detectionsNew/DetectionForm.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="detection-form">
|
||||
|
||||
<div class="detection-form-header">
|
||||
Create Alert Policies
|
||||
</div>
|
||||
|
||||
<!--第一步:General Settings-->
|
||||
<div class="detection-form-content">
|
||||
<div class="detection-form-collapse">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="1">
|
||||
<template #title>
|
||||
<div class="form-collapse-header">
|
||||
<div :class="activeNames[0]==='1' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">1</div>
|
||||
<div class="form-collapse-header-title">General Settings</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="form-collapse-content">
|
||||
<general-settings ref="form" @setSettingForm="getFormSetting" />
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!--第二步:Rule Definition-->
|
||||
<div class="detection-form-collapse">
|
||||
<el-collapse v-model="activeNames" style="position: relative;">
|
||||
<el-collapse-item name="2">
|
||||
<template #title>
|
||||
<div class="form-collapse-header">
|
||||
<div :class="activeNames[0]==='2' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">2</div>
|
||||
<div class="form-collapse-header-title">Rule Definition</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="form-collapse-content">
|
||||
<rule-definition :settingObj="settingObj" @setRuleObj="getRuleObj" />
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!--第三步:Trigger-->
|
||||
<div class="detection-form-collapse">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="3">
|
||||
<template #title>
|
||||
<div class="form-collapse-header">
|
||||
<div :class="activeNames[0]==='3' ? 'form-collapse-header-no-active' : 'form-collapse-header-no'">3</div>
|
||||
<div class="form-collapse-header-title">Trigger</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="form-collapse-content margin-t-18">
|
||||
<el-form class="trigger-block margin-b-20" ref="form3" :model="triggerObj" :rules="rules">
|
||||
<div class="trigger-block-item margin-b-10">
|
||||
<div>At least</div>
|
||||
<el-form-item prop="atLeast">
|
||||
<el-input size="mini" v-model="triggerObj.atLeast" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
|
||||
</el-form-item>
|
||||
<div>times within</div>
|
||||
|
||||
<el-form-item prop="interval">
|
||||
<el-input size="mini" v-model="triggerObj.interval" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="intervalVal">
|
||||
<el-select v-model="triggerObj.intervalVal" class="form-trigger__select" placeholder=" " size="mini">
|
||||
<el-option
|
||||
v-for="item in intervalList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="trigger-block-item">
|
||||
<div>With the counter resetting after no activity for</div>
|
||||
<el-form-item prop="minute">
|
||||
<el-input size="mini" v-model="triggerObj.minute" oninput="value=value.replace(/[^\d]/g,'')"></el-input>
|
||||
</el-form-item>
|
||||
<div>minutes</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div class="form-setting__btn1">
|
||||
<el-button @click="createPolicy">Create & enable rule</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GeneralSettings from '@/components/table/detection/GeneralSettings'
|
||||
import RuleDefinition from '@/components/table/detection/RuleDefinition'
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'DetectionForm',
|
||||
data () {
|
||||
return {
|
||||
activeNames: ['1'],
|
||||
settingObj: {}, // General Settings,即第一步的form表单信息
|
||||
ruleObj: {}, // 第二步的form表单信息
|
||||
// 第三步的form表单信息
|
||||
triggerObj: {
|
||||
atLeast: '',
|
||||
interval: '',
|
||||
intervalVal: '',
|
||||
minute: '',
|
||||
finishFlag: false
|
||||
},
|
||||
rules: {
|
||||
atLeast: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
interval: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
intervalVal: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
minute: [
|
||||
{
|
||||
required: true,
|
||||
message: this.$t('validate.required'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
intervalList: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
GeneralSettings,
|
||||
RuleDefinition
|
||||
},
|
||||
mounted () {
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
/** 获取下拉列表数据 */
|
||||
getStatistics () {
|
||||
get(api.detection.statistics, { pageSize: -1 }).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.intervalList = response.data.intervalList || []
|
||||
} else {
|
||||
console.error(response)
|
||||
}
|
||||
}).finally(() => {
|
||||
})
|
||||
},
|
||||
/** 获取General Settings折叠板form数据 */
|
||||
getFormSetting (data) {
|
||||
this.handleActiveNames('1', this.activeNames)
|
||||
this.settingObj = JSON.parse(JSON.stringify(data))
|
||||
},
|
||||
/** 获取Rule Definition折叠板form数据 */
|
||||
getRuleObj (data) {
|
||||
this.handleActiveNames('2', this.activeNames)
|
||||
this.ruleObj = JSON.parse(JSON.stringify(data))
|
||||
},
|
||||
/** 自动展开收起折叠板 */
|
||||
handleActiveNames (name, arr) {
|
||||
const list = arr
|
||||
list.splice(list.indexOf(name), 1)
|
||||
this.activeNames = []
|
||||
list.forEach(t => {
|
||||
this.activeNames.push(t)
|
||||
})
|
||||
},
|
||||
/** 创建policy */
|
||||
createPolicy () {
|
||||
const self = this
|
||||
const settingLen = Object.keys(this.settingObj).length
|
||||
const ruleLen = Object.keys(this.ruleObj).length
|
||||
|
||||
if (settingLen > 0 && ruleLen > 0) {
|
||||
this.$refs.form3.validate(valid => {
|
||||
if (valid) {
|
||||
// 最终提交form
|
||||
// const formObj = { ...this.settingObj, ...this.ruleObj, ...this.triggerObj }
|
||||
this.$message({
|
||||
duration: 2000,
|
||||
type: 'success',
|
||||
message: this.$t('tip.saveSuccess')
|
||||
})
|
||||
|
||||
// axios.post('api', formObj).then(response => {
|
||||
// if (response.data.code === 200) {
|
||||
// this.$message({
|
||||
// duration: 2000,
|
||||
// type: 'success',
|
||||
// message: this.$t('tip.saveSuccess')
|
||||
// })
|
||||
//
|
||||
// this.$router.push({
|
||||
// path: '/detectionNew',
|
||||
// query: {
|
||||
// t: +new Date()
|
||||
// }
|
||||
// })
|
||||
// } else {
|
||||
// this.$message.error(this.errorMsgHandler(response))
|
||||
// }
|
||||
// }).catch(e => {
|
||||
// console.error(e)
|
||||
// this.$message.error(this.errorMsgHandler(e))
|
||||
// }).finally(() => {
|
||||
// //
|
||||
// })
|
||||
}
|
||||
})
|
||||
} else if (settingLen === 0) {
|
||||
this.handleFormError('1')
|
||||
} else if (ruleLen === 0) {
|
||||
this.handleFormError('2')
|
||||
}
|
||||
},
|
||||
handleFormError (name) {
|
||||
const list = this.activeNames
|
||||
list.push(name)
|
||||
this.activeNames = []
|
||||
list.forEach(t => {
|
||||
this.activeNames.push(t)
|
||||
})
|
||||
this.$message.error('请确保信息填写完整')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
175
src/views/detectionsNew/DetectionTable.vue
Normal file
175
src/views/detectionsNew/DetectionTable.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<el-table
|
||||
id="detectionTable"
|
||||
class="detection-table"
|
||||
ref="dataTable"
|
||||
:data="tableData"
|
||||
height="100%"
|
||||
border
|
||||
empty-text=" "
|
||||
@header-dragend="dragend"
|
||||
@sort-change="tableDataSort"
|
||||
@selection-change="selectionChange"
|
||||
@row-dblclick="rowDoubleClick"
|
||||
>
|
||||
<el-table-column
|
||||
:resizable="false"
|
||||
align="center"
|
||||
type="selection"
|
||||
:selectable="selectable"
|
||||
width="55">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-for="(item, index) in customTableTitles"
|
||||
:key="`col-${index}`"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
:min-width="`${item.minWidth}`"
|
||||
:prop="item.prop"
|
||||
:resizable="true"
|
||||
:sort-orders="['ascending', 'descending']"
|
||||
:sortable="item.sortable"
|
||||
:width="`${item.width}`"
|
||||
class="data-column"
|
||||
>
|
||||
<template #header>
|
||||
<span class="data-column__span">{{ item.label }}</span>
|
||||
<div class="col-resize-area"></div>
|
||||
</template>
|
||||
<template #default="scope" :column="item">
|
||||
<template v-if="item.prop === 'name'">
|
||||
<template v-if="scope.row.i18n">
|
||||
<span :title="scope.row[item.prop]">{{ $t(scope.row.i18n) }}</span>
|
||||
</template>
|
||||
<template v-else-if="scope.row.name">
|
||||
<span :title="scope.row[item.prop]">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>-</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="item.prop === 'status'">
|
||||
<div :class="`detection-tag-status${scope.row[item.prop]}`">
|
||||
{{ switchStatus(scope.row[item.prop]) }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="item.prop === 'description'">
|
||||
<div style="padding-right: 20px">{{ scope.row[item.prop] }}</div>
|
||||
</template>
|
||||
<template v-else-if="item.prop === 'dimensions' && scope.row[item.prop]">
|
||||
<span class="detection-tag-blue">{{ scope.row[item.prop] }}</span>
|
||||
</template>
|
||||
<template v-else-if="item.prop === 'library' && scope.row[item.prop]">
|
||||
<span class="detection-table-library">{{ scope.row[item.prop] }}</span>
|
||||
</template>
|
||||
<span v-else>{{ scope.row[item.prop] || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-slot:empty>
|
||||
<div class="table-no-data" v-if="isNoData">
|
||||
<div class="table-no-data__title">{{ $t('npm.noData') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import table from '@/mixins/table'
|
||||
import { dateFormatByAppearance } from '@/utils/date-util'
|
||||
import { switchStatus } from '@/utils/tools'
|
||||
|
||||
export default {
|
||||
name: 'DetectionTable',
|
||||
props: {
|
||||
isNoData: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mixins: [table],
|
||||
data () {
|
||||
return {
|
||||
tableTitle: [
|
||||
{
|
||||
label: this.$t('knowledge.status'),
|
||||
prop: 'status',
|
||||
show: true,
|
||||
minWidth: 70
|
||||
},
|
||||
{
|
||||
label: 'ID',
|
||||
prop: 'ruleId',
|
||||
show: true,
|
||||
minWidth: 65
|
||||
},
|
||||
{
|
||||
label: this.$t('config.roles.name'),
|
||||
prop: 'name',
|
||||
minWidth: 90,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.category'),
|
||||
prop: 'category',
|
||||
minWidth: 140,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.type'),
|
||||
prop: 'eventType',
|
||||
minWidth: 145,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.remark'),
|
||||
prop: 'description',
|
||||
minWidth: 205,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
// label: this.$t('config.user.createTime'),
|
||||
label: 'Dimensions',
|
||||
prop: 'dimensions',
|
||||
minWidth: 204,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
// label: this.$t('config.user.createTime'),
|
||||
label: 'Library',
|
||||
prop: 'library',
|
||||
minWidth: 204,
|
||||
show: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
tableData: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (n) {
|
||||
if (n) {
|
||||
n.forEach(t => {
|
||||
if (t.ruleType === 'indicator_match') {
|
||||
t.library = t.ruleConfig.knowledge.name
|
||||
} else if (t.ruleType === 'threshold') {
|
||||
t.dimensions = t.ruleConfig.dimensions
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dateFormatByAppearance,
|
||||
switchStatus,
|
||||
rowDoubleClick (data) {
|
||||
this.$emit('rowDoubleClick', data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
77
src/views/detectionsNew/DetectionTools.vue
Normal file
77
src/views/detectionsNew/DetectionTools.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top-tools__left">
|
||||
<button
|
||||
id="knowledge-base-add"
|
||||
:title="$t('knowledgeBase.createKnowledgeBase')"
|
||||
class="top-tool-btn margin-r-10 top-tool-btn--create"
|
||||
@click="onCreate"
|
||||
style="width:72px;">
|
||||
<i class="cn-icon-xinjian cn-icon"></i>
|
||||
<span>{{ $t('overall.create') }}</span>
|
||||
</button>
|
||||
|
||||
<!-- <button-->
|
||||
<!-- id="knowledge-base-edit"-->
|
||||
<!-- :title="$t('knowledgeBase.editKnowledgeBase')"-->
|
||||
<!-- class="top-tool-btn margin-r-10"-->
|
||||
<!-- style="width:72px;">-->
|
||||
<!-- <i class="cn-icon-edit cn-icon" ></i>-->
|
||||
<!-- <span>{{$t('overall.edit')}}</span>-->
|
||||
<!-- </button>-->
|
||||
|
||||
<button
|
||||
:disabled="disableDelete"
|
||||
id="knowledge-base-delete"
|
||||
:title="$t('knowledgeBase.deleteKnowledgeBase')"
|
||||
class="top-tool-btn margin-r-10"
|
||||
@click="onDelete"
|
||||
style="width:72px;">
|
||||
<i class="cn-icon-delete cn-icon"></i>
|
||||
<span>{{ $t('overall.delete') }}</span>
|
||||
</button>
|
||||
|
||||
<div class="top-tool-search margin-l-10">
|
||||
<el-input v-model="keyWord" size="small" style="height: 28px;" @keyup.enter="onSearch"></el-input>
|
||||
<button
|
||||
class="top-tool-btn top-tool-btn--search"
|
||||
style="border-radius: 0 2px 2px 0 !important;"
|
||||
@click="onSearch">
|
||||
<i class="el-icon-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DetectionTools',
|
||||
props: {
|
||||
disableDelete: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
keyWord: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSearch () {
|
||||
this.$emit('search', this.keyWord)
|
||||
},
|
||||
onCreate () {
|
||||
this.$emit('create')
|
||||
},
|
||||
onDelete (data) {
|
||||
this.$emit('delete', data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
301
src/views/detectionsNew/Index.vue
Normal file
301
src/views/detectionsNew/Index.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="detection">
|
||||
<div class="detection-title">
|
||||
<span>{{ $t('overall.detections') }}</span>
|
||||
<span class="detection-title-label">
|
||||
60 Polices created(200 max) | 32 polices enabled(100 max)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="detection-content">
|
||||
<div class="detection-filter">
|
||||
<detection-filter></detection-filter>
|
||||
</div>
|
||||
|
||||
<div class="detection-block">
|
||||
<detection-tools
|
||||
@delete="toDelete"
|
||||
@create="onCreate"
|
||||
@search="onSearch"
|
||||
:disableDelete="disableDelete"/>
|
||||
|
||||
<div class="detection-table" style="position: relative">
|
||||
<loading :loading="loading"></loading>
|
||||
<detection-table
|
||||
ref="dataTable"
|
||||
height="100%"
|
||||
:api="url"
|
||||
:isNoData="isNoData"
|
||||
:custom-table-title="tools.customTableTitle"
|
||||
:table-data="tableData"
|
||||
:is-selected-status="isSelectedStatus"
|
||||
:all-count="18"
|
||||
@selectionChange="selectionChange"
|
||||
@reload="reloadRowList"
|
||||
@toggleLoading="toggleLoading"
|
||||
@rowDoubleClick="onRowDoubleClick"
|
||||
></detection-table>
|
||||
</div>
|
||||
|
||||
<div class="knowledge-pagination">
|
||||
<pagination ref="pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="showConfirmDialog"
|
||||
:title="$t('overall.hint')"
|
||||
width="480px"
|
||||
custom-class="del-model-hint"
|
||||
:before-close="handleClose">
|
||||
<div class="dialog-message">{{ $t('knowledge.deleteDataHint') }}</div>
|
||||
<el-table v-model="delItemList"
|
||||
ref="delDataTable"
|
||||
:data="batchDeleteObjs"
|
||||
@selection-change="secondSelectionChange"
|
||||
height="156px"
|
||||
width="100%"
|
||||
class="dialog-table"
|
||||
cell-style="padding:4px 0px;font-size: 12px;color: #353636;font-weight: 400;"
|
||||
header-cell-style="padding:4px 0px;background: #F5F8FA;font-size: 12px;color: #353636;font-weight: 500;">
|
||||
<el-table-column :resizable="false" align="center" type="selection" width="50"></el-table-column>
|
||||
<el-table-column property="ruleId" label="ID" width="70"></el-table-column>
|
||||
<el-table-column property="name" label="Name"></el-table-column>
|
||||
<el-table-column property="category" label="Category" width="100" :formatter="categoryFormat"></el-table-column>
|
||||
<el-table-column property="function" label="Function" width="110" :formatter="sourceFormat"></el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showConfirmDialog = false">{{ $t('overall.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submit">{{ $t('tip.confirm') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<div class="detection-drawer">
|
||||
<el-drawer v-model="showDrawer" :with-header="false">
|
||||
<detection-drawer :drawer-info="drawerInfo"></detection-drawer>
|
||||
</el-drawer>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DetectionFilter from '@/views/detectionsNew/DetectionFilter'
|
||||
import DetectionTools from '@/views/detectionsNew/DetectionTools'
|
||||
import DetectionTable from '@/views/detectionsNew/DetectionTable'
|
||||
import { api } from '@/utils/api'
|
||||
import dataListMixin from '@/mixins/data-list'
|
||||
import DetectionDrawer from '@/views/detectionsNew/DetectionDrawer'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
DetectionFilter,
|
||||
DetectionTools,
|
||||
DetectionTable,
|
||||
DetectionDrawer
|
||||
},
|
||||
mixins: [dataListMixin],
|
||||
data () {
|
||||
return {
|
||||
// url: api.detection.list,
|
||||
url: api.knowledgeBase,
|
||||
listUrl: api.detection.list,
|
||||
tableId: 'detectionTable',
|
||||
isNoData: false,
|
||||
tableData: [],
|
||||
isSelectedStatus: false,
|
||||
batchDeleteObjs: [], // 待删除列表
|
||||
secondBatchDeleteObjs: [],
|
||||
disableDelete: true,
|
||||
showConfirmDialog: false,
|
||||
delItemList: [],
|
||||
showDrawer: false,
|
||||
drawerInfo: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSearch () {
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// const params = {
|
||||
// ...this.filterParams,
|
||||
// name: this.keyWord
|
||||
// }
|
||||
// this.clearList()
|
||||
// this.search(params)
|
||||
// this.$refs.knowledgeFilter.reloadFilter(this.checkedCategoryIds, this.checkedStatusIds)
|
||||
},
|
||||
toDelete (data) {
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// if (data && data.ruleId) {
|
||||
// this.secondBatchDeleteObjs = []
|
||||
// this.batchDeleteObjs = []
|
||||
// this.secondBatchDeleteObjs.push(data)
|
||||
// this.batchDeleteObjs.push(data)
|
||||
// }
|
||||
// this.showDelDialog()
|
||||
},
|
||||
onCreate () {
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// this.$router.push({
|
||||
// path: '/detection/policies/create',
|
||||
// query: {
|
||||
// t: +new Date()
|
||||
// }
|
||||
// })
|
||||
},
|
||||
selectionChange (objs) {
|
||||
this.batchDeleteObjs = []
|
||||
objs.forEach(obj => {
|
||||
const delObj = this.batchDeleteObjs.find(item => item.ruleId === obj.ruleId)
|
||||
if (delObj === undefined) {
|
||||
this.batchDeleteObjs.push(obj)
|
||||
}
|
||||
})
|
||||
this.disableEdit = this.batchDeleteObjs.length !== 1
|
||||
this.disableDelete = this.batchDeleteObjs.length < 1
|
||||
},
|
||||
reloadRowList () {
|
||||
this.getTableData()
|
||||
},
|
||||
toggleLoading () {
|
||||
},
|
||||
showDelDialog () {
|
||||
this.showConfirmDialog = true
|
||||
this.$nextTick(() => {
|
||||
this.batchDeleteObjs.forEach((item) => {
|
||||
this.$refs.delDataTable.toggleRowSelection(item, true)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleClose () {
|
||||
this.showConfirmDialog = false
|
||||
},
|
||||
secondSelectionChange (objs) {
|
||||
this.secondBatchDeleteObjs = objs
|
||||
},
|
||||
categoryFormat (row, column) {
|
||||
// const category = row.category
|
||||
// const t = knowledgeBaseCategory.find(t => t.value === category)
|
||||
// return t ? t.name : category
|
||||
},
|
||||
sourceFormat (row, column) {
|
||||
// const source = row.source
|
||||
// const t = knowledgeBaseSource.find(t => t.value === source)
|
||||
// return t ? t.name : source
|
||||
},
|
||||
submit () {
|
||||
this.delBatchDetection()
|
||||
this.showConfirmDialog = false
|
||||
},
|
||||
delBatchDetection () {
|
||||
const ids = []
|
||||
if (this.secondBatchDeleteObjs && this.secondBatchDeleteObjs.length > 0) {
|
||||
this.secondBatchDeleteObjs.forEach(item => {
|
||||
ids.push(item.ruleId)
|
||||
})
|
||||
}
|
||||
if (ids.length === 0) {
|
||||
this.$alert(this.$t('tip.pleaseSelect'), {
|
||||
confirmButtonText: this.$t('tip.yes'),
|
||||
type: 'warning'
|
||||
}).catch(() => {
|
||||
})
|
||||
} else {
|
||||
// todo 调用接口删除
|
||||
// this.toggleLoading(true)
|
||||
// axios.delete(api.detection.delete + '?ruleIds=' + ids).then(response => {
|
||||
// if (response.data.code === 200) {
|
||||
// this.delFlag = true
|
||||
// this.$message({ duration: 2000, type: 'success', message: this.$t('tip.deleteSuccess') })
|
||||
// this.secondBatchDeleteObjs.forEach((item) => {
|
||||
// this.$refs.delDataTable.toggleRowSelection(item, false)
|
||||
// })
|
||||
// this.$refs.knowledgeFilter.reloadFilter()
|
||||
// this.secondBatchDeleteObjs = []
|
||||
// this.batchDeleteObjs = []
|
||||
// delete this.searchLabel.category
|
||||
// delete this.searchLabel.source
|
||||
// this.getTableData()
|
||||
// } else {
|
||||
// this.$message.error(response.data.message)
|
||||
// }
|
||||
// }).finally(() => {
|
||||
// this.toggleLoading(false)
|
||||
// if (this.isSelectedStatus != undefined) {
|
||||
// this.isSelectedStatus = false
|
||||
// this.disableDelete = true
|
||||
// this.secondBatchDeleteObjs = []
|
||||
// this.batchDeleteObjs = []
|
||||
// this.showConfirmDialog = false
|
||||
// }
|
||||
// })
|
||||
}
|
||||
},
|
||||
onRowDoubleClick (data) {
|
||||
// todo 暂时禁用,后续再开发时解禁
|
||||
// this.showDrawer = true
|
||||
// this.drawerInfo = data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detection {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.detection-title {
|
||||
font-family: NotoSansHans-Black;
|
||||
line-height: 24px;
|
||||
font-size: 24px;
|
||||
color: #353636;
|
||||
font-weight: 900;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.detection-title-label {
|
||||
font-family: NotoSansSChineseRegular;
|
||||
font-size: 12px;
|
||||
color: #717171;
|
||||
letter-spacing: 0;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.detection-content {
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
height: calc(100% - 96px);
|
||||
display: flex;
|
||||
|
||||
.detection-filter {
|
||||
width: 320px;
|
||||
height: calc(100% + 34px);
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(226, 229, 236, 1);
|
||||
border-radius: 4px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.detection-block {
|
||||
width: calc(100% - 340px);
|
||||
|
||||
.detection-table {
|
||||
width: 100%;
|
||||
height: calc(100% - 44px);
|
||||
border-radius: 4px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
src/views/entityExplorer/EntityFilterOld.vue
Normal file
150
src/views/entityExplorer/EntityFilterOld.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template >
|
||||
<div class="entity-filter-case">
|
||||
<div class="filter-case__header">{{$t('entities.filter')}}</div>
|
||||
<div
|
||||
class="entity-filter"
|
||||
v-for="(filters, index) in filterData"
|
||||
:key="index"
|
||||
>
|
||||
<div class="filter__header">{{filters.title}}</div>
|
||||
<div class="filter__body">
|
||||
|
||||
<div class="filter__row" v-for="(item, i) in filters.data" :key="i">
|
||||
<el-popover popper-class="filter__row-popover" placement="right-start" :width="440" v-model:visible="item.showTopTen">
|
||||
<template #reference>
|
||||
<div class="filter__row-popover" @click="showTopDialog(i, item, filters)">
|
||||
<div class="row__label">
|
||||
<i :class="item.icon"></i>
|
||||
<span>{{item.label}}</span>
|
||||
</div>
|
||||
<div class="row__value">
|
||||
<loading :loading="loadingLeft" size="small"></loading>
|
||||
<span>{{item.value}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<entity-top
|
||||
ref="entityTopTenPop"
|
||||
:loading="loading"
|
||||
:popover-data="popoverData"
|
||||
:item-data="itemData"
|
||||
:total-count="totalCount"
|
||||
:top-column="item.topColumn"
|
||||
@filter="filter"
|
||||
></entity-top>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EntityTop from '@/views/entityExplorer/EntityTop'
|
||||
import { get } from '@/utils/http'
|
||||
import { api } from '@/utils/api'
|
||||
import Loading from '@/components/common/Loading'
|
||||
import { getSecond } from '@/utils/date-util'
|
||||
export default {
|
||||
name: 'EntityFilter',
|
||||
components: {
|
||||
Loading,
|
||||
EntityTop
|
||||
},
|
||||
props: {
|
||||
filterData: Array,
|
||||
q: String,
|
||||
timeFilter: Object,
|
||||
loadingLeft: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
topList: 'list',
|
||||
topData: [],
|
||||
entityTopTenData: [],
|
||||
currentColumn: {},
|
||||
totalCount: 0,
|
||||
loading: false,
|
||||
popoverData: [],
|
||||
itemData: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentColumn (n, o) {
|
||||
if (n.column === 'dnsServerOrgCount') {
|
||||
this.totalCount = this.filterData[3].orgTotalCount
|
||||
} else if (n.column === 'dnsServerSoftwareCount') {
|
||||
this.totalCount = this.filterData[3].softwareTotalCount
|
||||
} else if (n.column === 'dnsServerOsCount') {
|
||||
this.totalCount = this.filterData[3].osTotalCount
|
||||
} else if (n.column === 'categoryDistinctCount' && n.type === 'app') {
|
||||
this.totalCount = this.filterData[1].totalCount
|
||||
} else {
|
||||
let count = 0
|
||||
this.filterData.forEach(f => {
|
||||
const filter = f.data.some(d => d.column === n.column)
|
||||
if (filter) {
|
||||
count = f.totalCount
|
||||
}
|
||||
})
|
||||
this.totalCount = count
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showTopDialog (i, item, filter) {
|
||||
if (this.currentColumn.column === item.column && item.showTopTen) {
|
||||
item.showTopTen = false
|
||||
return
|
||||
}
|
||||
this.filterData.forEach(f => {
|
||||
f.data.forEach(ff => {
|
||||
ff.showTopTen = false
|
||||
})
|
||||
})
|
||||
item.showTopTen = true
|
||||
this.currentColumn = {
|
||||
column: item.column,
|
||||
type: filter.type
|
||||
}
|
||||
const queryParams = {
|
||||
q: this.q,
|
||||
entityType: filter.type,
|
||||
column: item.topColumn,
|
||||
top: 10,
|
||||
startTime: getSecond(this.timeFilter.startTime),
|
||||
endTime: getSecond(this.timeFilter.endTime)
|
||||
}
|
||||
this.loading = true
|
||||
this.popoverData = []
|
||||
this.itemData = {}
|
||||
get(api.filterTop, queryParams).then(response => {
|
||||
if (response.code === 200) {
|
||||
if (this.currentColumn.column === item.column) {
|
||||
if (filter.type === 'dns') {
|
||||
this.popoverData = response.data.result.filter(f => {
|
||||
return f.count > 0
|
||||
})
|
||||
} else {
|
||||
this.popoverData = response.data.result
|
||||
}
|
||||
this.itemData = item
|
||||
}
|
||||
} else {
|
||||
this.popoverData = []
|
||||
this.itemData = item
|
||||
}
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
this.popoverData = []
|
||||
this.itemData = item
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
filter (name, topData) {
|
||||
this.showTopDialog('', topData)
|
||||
this.$emit('filter', name, topData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user