This commit is contained in:
hyx
2023-08-07 09:23:17 +08:00
59 changed files with 3860 additions and 228 deletions

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="12px" viewBox="0 0 18 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>未知</title>
<g id="🥇Entity" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Entity_Explore【domain展开】" transform="translate(-180.000000, -216.000000)">
<g id="未知" transform="translate(180.000000, 216.000000)">
<rect id="矩形" fill="#EFF2F5" x="0" y="0" width="18" height="12"></rect>
<g transform="translate(2.000000, 3.000000)" fill="#96A2B0" fill-rule="nonzero">
<polygon id="路径" points="1.513 2.957 1.035 2 0.991 2 1.13 2.957 1.13 6.131 0 6.131 0 0 0.869 0 3.174 3.243 3.635 4.174 3.679 4.174 3.539 3.243 3.539 0.043 4.67 0.043 4.67 6.174 3.8 6.174"></polygon>
<path d="M12.261,4.783 L10.391,4.783 L9.93,6.131 L8.739,6.131 L10.93,0 L11.808,0 L14,6.131 L12.748,6.131 L12.261,4.783 Z M10.696,3.827 L12,3.827 L11.548,2.47 L11.365,1.513 L11.321,1.513 L11.138,2.478 L10.696,3.827 Z" id="形状"></path>
<polygon id="路径" points="8.101 0 5.367 6.174 6.396 6.174 9.123 0"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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 {
.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;
margin-bottom: 10px;
.filter__header {
display: flex;
flex: 0 0 32px;
align-items: center;
padding-left: 10px;
color: #666;
//background-color: #F3F7FA;
cursor: pointer;
.filter-content-checkbox {
line-height: 16px;
margin-bottom: 10px;
font-family: NotoSansSChineseRegular;
font-size: 14px;
color: #353636;
font-weight: 400;
span {
font-size: 14px;
padding-left: 6px;
.el-checkbox__inner {
width: 16px !important;
height: 16px !important;
text-align: center !important;
line-height: 16px !important;
}
i {
font-size: 12px;
transition: all linear .1s;
transform: rotate(0) translate(0, 2px);
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
border-color: #38ACD2;
background: #38ACD2;
border-radius: 2px;
}
i.arrow-rotate {
transform: rotate(90deg) translate(2px, 3px);
.el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
background: #FFFFFF;
border-radius: 1px;
}
.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;
}
.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;
}
}
}
.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;
}
}

View File

@@ -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;
}

View 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;
}
}
}

View File

@@ -88,12 +88,17 @@
color: #353636;
font-weight: 400;
}
.explorer-container {
.explorer-container, .explorer-container-new {
display: flex;
overflow: visible; /*overflow: hidden;*/
height: calc(100% - 120px);
position: relative;
}
.explorer-container-new {
height: calc(100% - 62px);
flex-direction: column;
width: calc(100% - 340px);
}
.explorer-foot {
display: flex;
justify-content: center;

View File

@@ -56,11 +56,6 @@
color: #353636;
font-weight: 400;
.filter-country-flag {
width: 18px;
height: 12px;
}
.filter__body-item-left-index {
width: 16px;
height: 16px;
@@ -99,3 +94,9 @@
}
}
}
.filter-country-flag {
width: 18px;
height: 12px;
margin-right: 6px;
}

View File

@@ -98,7 +98,7 @@
}
.row__content {
display: flex;
color: #3976CB;
color: #046ECA;
word-wrap: break-word;
max-width: 30%;

View File

@@ -107,8 +107,8 @@ $font-size: 12px;
}
.graph-list-country-flag {
width: 16px;
height: 14px;
width: 14px;
height: 10px;
margin-right: 5px;
}

View 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>

View 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>

View 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>

View File

@@ -118,29 +118,68 @@ export default {
if (this.listUrl) {
listUrl = this.listUrl
}
get(listUrl, this.searchLabel).then(response => {
if (response.code === 200) {
this.tableData = response.data.list
this.pageObj.total = response.data.total
if (!this.tableData || this.tableData.length === 0) {
this.isNoData = true
} else {
this.isNoData = false
// 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'
}
}
}
} else {
console.error(response)
this.isNoData = true
if (response.message) {
this.$message.error(response.message)
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 {
this.$message.error(this.$t('tip.somethingWentWrong'))
obj.status = 0
}
list.push(obj)
}
}).catch(() => {
this.isNoData = true
}).finally(() => {
this.toggleLoading(false)
})
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
this.pageObj.total = response.data.total
if (!this.tableData || this.tableData.length === 0) {
this.isNoData = true
} else {
this.isNoData = false
}
} else {
console.error(response)
this.isNoData = true
if (response.message) {
this.$message.error(response.message)
} else {
this.$message.error(this.$t('tip.somethingWentWrong'))
}
}
}).catch(() => {
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
View 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)
}

View File

@@ -2,3 +2,4 @@ import './npm'
import './linkMonitor'
import './dns'
import './entity'
import './detection'

View File

@@ -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')
}
]
}

View File

@@ -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

View File

@@ -23,6 +23,7 @@ axios.interceptors.request.use(config => {
}
}
}
config.params.q = q
}
config.cancelToken = cancelToken

View File

@@ -1300,3 +1300,17 @@ export function numberWithCommas (num) {
return num
}
}
/**
* 根据状态编码01转化为DisabledEnabled
* @param status
* @returns {string}
*/
export function switchStatus (status) {
switch (status) {
case 0:
return 'Disabled'
case 1:
return 'Enabled'
}
}

View File

@@ -141,7 +141,10 @@ export function getUnitType (column) {
/* 单位转换,返回转换后的[value, unit]type=time时若value<1ms返回<1mstype=percent时若value<0.01%,返回<0.01% */
export function valueToRangeValue (value, unitType) {
const values = unitConvert(Number(value), unitType)
const values = unitConvert(value, unitType)
if (values[0] === '-') {
return values
}
if (values[0] || values[0] === 0) {
switch (unitType) {
case unitTypes.time: {
@@ -168,6 +171,12 @@ export function valueToRangeValue (value, unitType) {
}
break
}
case unitTypes.number: {
if (values[0] < 0.01) {
return ['<0.01', '']
}
break
}
default: break
}
}

View File

@@ -485,7 +485,7 @@ export function stackedLineTooltipFormatter (params) {
str += '<div class="cn-chart-tooltip">'
params.forEach((item, i) => {
str += `<span class="cn-chart-tooltip-value cn-chart-tooltip__color">
${unitConvert(item.data[1], item.value[2]).join(' ')}
${valueToRangeValue(item.data[1], item.value[2]).join(' ')}
</span>`
})
str += '</div>'
@@ -510,7 +510,7 @@ export function appStackedLineTooltipFormatter (params) {
</span>`
str += '</span>'
str += `<span class="cn-chart-tooltip-value cn-chart-tooltip__color">
${unitConvert(item.data[1], item.value[2]).join(' ')}
${valueToRangeValue(item.data[1], item.value[2]).join(' ')}
</span>`
str += '</div>'
})

View File

@@ -364,6 +364,8 @@ export default {
let url = ''
if (this.queryCondition.indexOf(' OR ') > -1) {
condition = this.queryCondition.split(/["|'](.*?)["|']/)
} else if (this.queryCondition.indexOf('+OR+') > -1) {
condition = this.queryCondition.replace(/\+/g, ' ').split(/["|'](.*?)["|']/)
} else {
condition = this.queryCondition
}

View File

@@ -22,10 +22,10 @@
</div>
<div class="line-value-unit" :test-id="`tabContent${index}`">
<span class="line-value-unit-number">
{{ getTabUnit(item.analysis.avg) }}
{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[0] }}
</span>
<span class="line-value-unit-number2">
<span>{{ unitConvert(item.analysis.avg, unitTypes.number)[1] }}</span>
<span>{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[1] }}</span>
<span v-if="item.unitType">{{ item.unitType }}</span>
</span>
</div>
@@ -88,7 +88,7 @@
import chartMixin from '@/views/charts2/chart-mixin'
import * as echarts from 'echarts'
import { stackedLineChartOption } from '@/views/charts2/charts/options/echartOption'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { unitTypes, chartColor3, chartColor4 } from '@/utils/constants.js'
import { ref, shallowRef } from 'vue'
import { stackedLineTooltipFormatter } from '@/views/charts/charts/tools'
@@ -153,6 +153,7 @@ export default {
tabsTemplate: dataForNetworkOverviewLine.tabsTemplate,
tabs: [],
unitConvert,
valueToRangeValue,
unitTypes,
chartDateObject: [],
timer: null,
@@ -627,14 +628,6 @@ export default {
} else {
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
}
},
// 对lineTab进行转换
getTabUnit (avg) {
let number = unitConvert(avg, unitTypes.number)[0]
if (number !== '-' && JSON.parse(number) > 0 && JSON.parse(number) < 1) {
number = '<1'
}
return number
}
},
mounted () {
@@ -664,6 +657,7 @@ export default {
}
// 检测时发现该方法占用较大内存,且未被释放
this.unitConvert = null
this.valueToRangeValue = null
myChart = null
}
}

View File

@@ -26,9 +26,9 @@
<div class="tabs-name">{{$t(item.name)}}</div>
</div>
<div class="line-value-unit" :test-id="`tabContent${index}`">
<span class="line-value-unit-number">{{unitConvert(item.analysis.avg, unitTypes.number)[0]}}</span>
<span class="line-value-unit-number">{{valueToRangeValue(item.analysis.avg, unitTypes.number)[0]}}</span>
<span class="line-value-unit-number2">
<span>{{unitConvert(item.analysis.avg, unitTypes.number)[1]}}</span>
<span>{{valueToRangeValue(item.analysis.avg, unitTypes.number)[1]}}</span>
<span v-if="item.unitType">{{item.unitType}}</span>
</span>
</div>
@@ -67,7 +67,7 @@ import ChartNoData from '@/views/charts/charts/ChartNoData'
import Loading from '@/components/common/Loading'
import { useRoute } from 'vue-router'
import { ref, shallowRef } from 'vue'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import { chartColor3, chartColor4, unitTypes } from '@/utils/constants'
import { getLineType, overwriteUrl, urlParamsHandler } from '@/utils/tools'
import { getSecond } from '@/utils/date-util'
@@ -107,7 +107,7 @@ export default {
return {
options: dataForLinkTrafficLine.options,
tabs: dataForLinkTrafficLine.tabs,
unitConvert,
valueToRangeValue,
unitTypes,
chartDateObject: [],
timer: null,
@@ -457,7 +457,7 @@ export default {
echarts.dispose(this.myChart)
}
this.chartOption = null
this.unitConvert = null
this.valueToRangeValue = null
}
}
</script>

View File

@@ -21,7 +21,7 @@
<div class="app-card__body">
<div class="app-card__body-content">
<div class="app-card__body-content-value">
<div class="app-card__body-content-number" :test-id="`rate${index}`">{{unitConvert(app.rate, unitTypes.number).join(' ')}}</div>
<div class="app-card__body-content-number" :test-id="`rate${index}`">{{valueToRangeValue(app.rate, unitTypes.number).join(' ')}}</div>
<div class="app-card__body-content-percent red" v-if="app.value > 0" :test-id="`percent${index}`">
<span v-if="app.value <= 5">
+{{unitConvert(app.value, unitTypes.percent).join('')}}
@@ -39,8 +39,8 @@
</div>
<div class="app-card__body-previous">
<div>Total</div>
<div v-if="metric === 'Bits/s'" :test-id="`total${index}`">{{unitConvert(app.total, unitTypes.byte).join(' ')}}</div>
<div v-else :test-id="`total${index}`">{{unitConvert(app.total, unitTypes.number).join(' ')}}</div>
<div v-if="metric === 'Bits/s'" :test-id="`total${index}`">{{valueToRangeValue(app.total, unitTypes.byte).join(' ')}}</div>
<div v-else :test-id="`total${index}`">{{valueToRangeValue(app.total, unitTypes.number).join(' ')}}</div>
</div>
</div>
<div class="chart__drawing" v-show="!isNoData" :id="`chart-${app.name}-${app.type}`"></div>
@@ -120,7 +120,7 @@
</template>
<script>
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { storageKey, unitTypes, networkTable, operationType, curTabState } from '@/utils/constants'
import * as echarts from 'echarts'
import { appListChartOption } from '@/views/charts2/charts/options/echartOption'
@@ -149,6 +149,8 @@ export default {
appData: [],
// 假数据
appTempData: [],
valueToRangeValue,
unitConvert,
unitTypes,
timer: null,
showAddApp: false,
@@ -207,7 +209,6 @@ export default {
}
},
methods: {
unitConvert,
clickOutSide () {
this.appData.forEach(t => {
t.moreOptions = false
@@ -732,6 +733,7 @@ export default {
echarts.dispose(this.myChart)
}
this.unitConvert = null
this.valueToRangeValue = null
}
}
</script>

View File

@@ -20,9 +20,9 @@
<div class="tabs-name" :test-id="`tabTitle${index}`">{{ $t(item.name) }}</div>
</div>
<div class="line-value-unit" :test-id="`tabContent${index}`">
<span class="line-value-unit-number">{{ unitConvert(item.analysis.avg, unitTypes.number)[0] }}</span>
<span class="line-value-unit-number">{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[0] }}</span>
<span class="line-value-unit-number2">
<span>{{ unitConvert(item.analysis.avg, unitTypes.number)[1] }}</span>
<span>{{ valueToRangeValue(item.analysis.avg, unitTypes.number)[1] }}</span>
<span v-if="item.unitType">{{ item.unitType }}</span>
</span>
</div>
@@ -58,7 +58,7 @@
<script>
import * as echarts from 'echarts'
import { stackedLineChartOption } from '@/views/charts2/charts/options/echartOption'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import { unitTypes, chartColor3, chartColor4 } from '@/utils/constants.js'
import { ref, shallowRef } from 'vue'
import { stackedLineTooltipFormatter } from '@/views/charts/charts/tools'
@@ -107,7 +107,7 @@ export default {
options2: dataForNetworkOverviewLine.options2,
tabsTemplate: dataForNetworkOverviewLine.tabsTemplate,
tabs: [],
unitConvert,
valueToRangeValue,
unitTypes,
chartDateObject: [],
timer: null,
@@ -254,7 +254,7 @@ export default {
symbol: 'none',
label: {
formatter (params) {
const arr = unitConvert(params.value, unitTypes.number).join('')
const arr = valueToRangeValue(params.value, unitTypes.number).join('')
return _this.lineRefer + '(' + arr + echartsData[0].unitType + ')'
},
position: 'insideStartTop',
@@ -576,7 +576,7 @@ export default {
echarts.dispose(this.myChart)
}
// 检测时发现该方法占用较大内存,且未被释放
this.unitConvert = null
this.valueToRangeValue = null
myChart = null
}
}

View File

@@ -50,10 +50,10 @@
<div class="data-total" >
<div class="data-value" :test-id="`data-value-${item.prop}${scope.row.index}`">
<template v-if="showUnit && item.unit">
{{scope.row[item.prop]?((scope.row[item.prop][0]||scope.row[item.prop][0]===0)? unitConvert(scope.row[item.prop][0], item.unit).join(' ') : '-'):'' }}
{{scope.row[item.prop]?((scope.row[item.prop][0]||scope.row[item.prop][0]===0)? valueToRangeValue(scope.row[item.prop][0], item.unit).join(' ') : '-'):'' }}
</template>
<template v-else>
{{scope.row[item.prop]?((scope.row[item.prop][0]||scope.row[item.prop][0]===0)? unitConvert(scope.row[item.prop][0], unitTypes.number).join(' ') : '-'):'' }}
{{scope.row[item.prop]?((scope.row[item.prop][0]||scope.row[item.prop][0]===0)? valueToRangeValue(scope.row[item.prop][0], unitTypes.number).join(' ') : '-'):'' }}
</template>
</div>
<div class="data-trend" :test-id="`data-trend-all-${item.prop}${scope.row.index}`">
@@ -116,10 +116,10 @@
</template>
<template v-else>
<template v-if="showUnit && item.unit">
{{(scope.row[item.prop] || scope.row[item.prop]===0) ? unitConvert(scope.row[item.prop], item.unit).join(' ') : '-'}}
{{(scope.row[item.prop] || scope.row[item.prop]===0) ? valueToRangeValue(scope.row[item.prop], item.unit).join(' ') : '-'}}
</template>
<template v-else>
{{(scope.row[item.prop] || scope.row[item.prop]===0)? unitConvert(scope.row[item.prop], unitTypes.number).join(' ') : '-'}}
{{(scope.row[item.prop] || scope.row[item.prop]===0)? valueToRangeValue(scope.row[item.prop], unitTypes.number).join(' ') : '-'}}
</template>
</template>
</template>
@@ -213,7 +213,7 @@ import {
commonErrorTip
} from '@/utils/constants'
import { get } from '@/utils/http'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { getChainRatio, computeScore, urlParamsHandler, overwriteUrl, readDrilldownTableConfigByUser, combineDrilldownTableWithUserConfig, getDnsMapData, handleSpecialValue, getConfigVersion } from '@/utils/tools'
import { getSecond } from '@/utils/date-util'
import chartMixin from '@/views/charts2/chart-mixin'
@@ -231,6 +231,7 @@ export default {
orderBy: 'totalBytes',
tab: 'ip',
unitConvert,
valueToRangeValue,
unitTypes,
networkTable,
isNoData: false,
@@ -1993,7 +1994,8 @@ export default {
},
beforeUnmount () {
// 以下元素,检测到内存并未释放
this.unitConvert = null
this.valueToRangeValue = null
},
unmounted () {
this.isNoData = false

View File

@@ -36,7 +36,7 @@
<span class="data-total-category-value" @click="drillDownData(scope.row.i18n)">{{$t(scope.row.i18n)}}</span>
</template>
<template v-else-if="item.prop === 'total'">
<div class="data-total-value">{{unitConvert(scope.row.totalPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-total-value">{{valueToRangeValue(scope.row.totalPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-trend">
<div v-if="scope.row.bytesRateChainRatio > 0" class="data-total-trend data-total-trend-red">
<i class="cn-icon-rise1 cn-icon"></i>&nbsp;
@@ -59,7 +59,7 @@
</div>
</template>
<template v-else-if="item.prop === 'outbound'">
<div class="data-total-value">{{unitConvert(scope.row.outboundPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-total-value">{{valueToRangeValue(scope.row.outboundPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-trend">
<div v-if="scope.row.outboundBytesRateChainRatio > 0" class="data-total-trend data-total-trend-red">
<i class="cn-icon-rise1 cn-icon"></i>&nbsp;
@@ -82,7 +82,7 @@
</div>
</template>
<template v-else-if="item.prop === 'inbound'">
<div class="data-total-value" :test-id="`inbound-packet-${scope.row.appSubcategory}`">{{unitConvert(scope.row.inboundPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-total-value" :test-id="`inbound-packet-${scope.row.appSubcategory}`">{{valueToRangeValue(scope.row.inboundPacketsRate, unitTypes.bps).join(' ')}}</div>
<div class="data-trend">
<div v-if="scope.row.inboundBytesRateChainRatio > 0" class="data-total-trend data-total-trend-red">
<i class="cn-icon-rise1 cn-icon"></i>&nbsp;
@@ -135,7 +135,7 @@
<script>
import { unitTypes, npmCategoryInfoMapping, networkTable, operationType, npmCategoryToAppCategoryMap, curTabState } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { api } from '@/utils/api'
import { getSecond } from '@/utils/date-util'
import { computeScore, getChainRatio, getUserDrilldownTableConfig, overwriteUrl, urlParamsHandler } from '@/utils/tools'
@@ -149,6 +149,7 @@ export default {
data () {
return {
unitConvert,
valueToRangeValue,
unitTypes,
colorPatchData: [
{ letter: '' },
@@ -379,6 +380,7 @@ export default {
},
beforeUnmount () {
this.unitConvert = null
this.valueToRangeValue = null
}
}
</script>

View File

@@ -118,7 +118,7 @@ export default {
res2.forEach((r, i) => {
if (r.code === 200) {
mapData.forEach(t => {
t[keyPre[i] + 'Score'] = r.data.result.find(d => d.country === t.country)
t[keyPre[i] + 'Score'] = r.data.result.find(d => d.country === t.country && d.province === t.province)
})
} else {
this.showError = true

View File

@@ -50,7 +50,7 @@ import { shallowRef } from 'vue'
import * as am4Core from '@amcharts/amcharts4/core'
import * as am4Maps from '@amcharts/amcharts4/maps'
import { computeScore, getGeoData } from '@/utils/tools'
import { storageKey, unitTypes, countryNameIdMapping } from '@/utils/constants'
import { storageKey, unitTypes } from '@/utils/constants'
import locationOptions from '@/views/charts2/charts/locationOptions'
import { valueToRangeValue } from '@/utils/unit-convert'
import { getSecond } from '@/utils/date-util'
@@ -58,7 +58,6 @@ import { api } from '@/utils/api'
import axios from 'axios'
import { get } from '@/utils/http'
import chartMixin from '@/views/charts2/chart-mixin'
// import { Rectangle3D } from '@amcharts/amcharts4/.internal/core/elements/3d/Rectangle3D'
import ChartError from '@/components/common/Error'
export default {
name: 'NpmMap',
@@ -141,7 +140,7 @@ export default {
res2.forEach((r, i) => {
if (r.code === 200) {
mapData.forEach(t => {
const find = r.data.result.find(d => d.country === t.country)
const find = r.data.result.find(d => d.country === t.country && t.province === d.province)
t[keyPre[i] + 'Score'] = find
})
} else {

View File

@@ -8,10 +8,10 @@
<div class="single-value__content" >
<div class="single-value__content-number" v-if="index ===0 || index ===1 || index ===2" :test-id="`singleValueContent${index}`">
{{ unitConvert(npm.Avg, unitTypes.time).join(' ') }}
{{ valueToRangeValue(npm.Avg, unitTypes.time).join(' ') }}
</div>
<div class="single-value__content-number" v-else :test-id="`singleValueContent${index}`">
{{unitConvert(npm.Avg, unitTypes.percent).join(' ')}}
{{valueToRangeValue(npm.Avg, unitTypes.percent).join(' ')}}
</div>
<div v-if="npm.value > 0" class="single-value__content-trend single-value__content-trend-red" >
<i class="cn-icon-rise1 cn-icon" :test-id="`singleValueTrendIcon${index}`"></i>&nbsp;
@@ -36,17 +36,17 @@
<div class="single-value__circle">
<div class="single-value__circle-p95" :test-id="`singleValueP95${index}`">
<span v-if="index ===0 || index ===1 || index ===2">
P95:{{ unitConvert(npm.P95, unitTypes.time).join(' ') }}</span>
P95:{{ valueToRangeValue(npm.P95, unitTypes.time).join(' ') }}</span>
<span v-else>
P95:{{ unitConvert(npm.P95, unitTypes.percent).join(' ') }}
P95:{{ valueToRangeValue(npm.P95, unitTypes.percent).join(' ') }}
</span>
</div>
<div class="single-value__circle-p99" :test-id="`singleValueP99${index}`">
<span v-if="index ===0 || index ===1 || index ===2">
P99:{{ unitConvert(npm.P99, unitTypes.time).join(' ') }}
P99:{{ valueToRangeValue(npm.P99, unitTypes.time).join(' ') }}
</span>
<span v-else>
P99:{{ unitConvert(npm.P99, unitTypes.percent).join(' ') }}
P99:{{ valueToRangeValue(npm.P99, unitTypes.percent).join(' ') }}
</span>
</div>
</div>
@@ -63,7 +63,7 @@ import { getChainRatio, getQueryByFlag2, getQueryByType } from '@/utils/tools'
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import axios from 'axios'
import { dataForNpmNetworkQuantity } from '@/utils/static-data'
export default {
@@ -88,6 +88,7 @@ export default {
return {
unitTypes,
unitConvert,
valueToRangeValue,
npmNetworkName: dataForNpmNetworkQuantity.npmNetworkName,
npmNetworkCycleData: [],
npmNetworkLastCycleData: [],
@@ -114,6 +115,8 @@ export default {
let url = ''
if (this.queryCondition && this.queryCondition.indexOf(' OR ') > -1) {
condition = this.queryCondition.split(/["|'](.*?)["|']/)
} else if (this.queryCondition.indexOf('+OR+') > -1) {
condition = this.queryCondition.replace(/\+/g, ' ').split(/["|'](.*?)["|']/)
} else {
condition = this.queryCondition
}
@@ -347,6 +350,7 @@ export default {
clearTimeout(this.timer1)
clearTimeout(this.timer2)
this.unitConvert = null
this.valueToRangeValue = null
}
}
</script>

View File

@@ -116,6 +116,8 @@ export default {
if (this.queryCondition.indexOf(' OR ') > -1) {
condition = this.queryCondition.split(/["|'](.*?)["|']/)
} else if (this.queryCondition.indexOf('+OR+') > -1) {
condition = this.queryCondition.replace(/\+/g, ' ').split(/["|'](.*?)["|']/)
} else {
condition = this.queryCondition
}

View File

@@ -6,7 +6,7 @@ import {
chartColor6,
unitTypes
} from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import { axisFormatter } from '@/views/charts/charts/tools'
import { xAxisTimeFormatter, xAxisTimeRich } from '@/utils/date-util'

View 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'
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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -25,7 +25,7 @@
:time-filter="timeFilter"
@filter="filter"
></entity-filter>
<div class="explorer-container" style="height: calc(100% - 62px);flex-direction: column;width: 100%;">
<div class="explorer-container explorer-container-new">
<explorer-search
ref="search"
:class="{'explorer-search--show-list': showList}"
@@ -662,9 +662,9 @@ export default {
if (item.code === 200 && item.data.list) {
this.newFilterData[index].data = []
item.data.list.forEach(item => {
const obj = { label: item.value, flag: '011-china', topColumn: 'Country', value: item.uniqueEntities }
const obj = { label: item.value, topColumn: 'Country', value: item.uniqueEntities }
if (index === 0) {
obj.flag = item.uniqueEntities // 接口字段名称为'China'目前svg名称为'011-china',后续再指定方案调整
obj.flag = item.value // 接口字段名称为'China'svg名称为'CN'通过countryNameIdMapping进行转换
}
if (index === 1) {
obj.topColumn = 'City'

View File

@@ -15,11 +15,11 @@
<div class="filter__body-item" v-for="(data, i) in item.data" :key="i" @click="filter(data.label, data)">
<div class="filter__body-item-left">
<!--当前无更好方案匹配国旗后续解决-->
<!--<div v-if="data.flag">-->
<!-- <img :src="require(`../../../public/images/flag/${data.flag}.svg`)" class="filter-country-flag"/>-->
<!--</div>-->
<div class="filter__body-item-left-index">{{ i+1 }}</div>
<div v-if="data.flag">
<img v-if="data.flag===countryNameIdMapping.Unknown || !countryNameIdMapping[data.flag]" src="../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../public/images/flag/${countryNameIdMapping[data.flag]}.png`)" class="filter-country-flag"/>
</div>
<div v-else class="filter__body-item-left-index">{{ i+1 }}</div>
<div class="filter__body-item-left-label">
<el-tooltip :content="data.label" placement="top" effect="light" :disabled="disabledLabel">
<span @mouseenter="handleMouse(`filter${index}${i}`)" :id="`filter${index}${i}`">{{ data.label }}</span>
@@ -42,6 +42,7 @@
<script>
import Loading from '@/components/common/Loading'
import ChartNoData from '@/views/charts/charts/ChartNoData'
import { countryNameIdMapping } from '@/utils/constants'
export default {
name: 'EntityFilter',
components: { ChartNoData, Loading },
@@ -65,7 +66,8 @@ export default {
},
data () {
return {
disabledLabel: true
disabledLabel: true,
countryNameIdMapping
}
},
methods: {

View 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>

View File

@@ -39,8 +39,11 @@
<div class="graph-list-item-block">
<div class="graph-list-item padding-b-4">
<div class="graph-list-item-label">{{ $t('overall.location') }}:</div>
<div class="graph-list-item-value graph-list-item-value1" style="display: flex;align-items: center;">
<!-- <img :src="require(`../../../../public/images/flag/${item.flag}.svg`)" class="graph-list-country-flag"/>-->
<div class="graph-list-item-value graph-list-item-value1" style="display: flex;align-items: center;line-height: 14px;">
<div style="line-height: 10px">
<img v-if="getCountry(item.detail)===countryNameIdMapping.Unknown || !countryNameIdMapping[getCountry(item.detail)]" src="../../../../public/images/flag/Unknown.svg" class="graph-list-country-flag">
<img v-else :src="require(`../../../../public/images/flag/${countryNameIdMapping[getCountry(item.detail)]}.png`)" class="graph-list-country-flag" >
</div>
<span>{{ $_.get(item.detail, 'location.city', '-') }}</span>
</div>
</div>
@@ -105,7 +108,7 @@
<script>
import Loading from '@/components/common/Loading'
import { riskLevelMapping } from '@/utils/constants'
import { riskLevelMapping, countryNameIdMapping } from '@/utils/constants'
import { scrollToTop } from '@/utils/tools'
import _ from 'lodash'
@@ -119,6 +122,11 @@ export default {
type: Boolean
}
},
data () {
return {
countryNameIdMapping
}
},
components: {
Loading
},
@@ -181,6 +189,9 @@ export default {
expandList () {
// 继续拓展ip列表传递信息调用接口
this.$emit('expandList', _.get(this.node, 'id'))
},
getCountry (detail) {
return this.$_.get(detail, 'location.country') || 'Unknown'
}
},
mounted () {

View File

@@ -80,7 +80,7 @@
<span class="row-item-label">{{ $t('entities.sentThroughput') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">
{{
unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') !=='- ' ? unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'
valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') !=='- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'
}}
</span>
<!-- 曲线-->
@@ -110,7 +110,7 @@
<i class="cn-icon cn-icon-fall"></i>
<span class="row-item-label">{{ $t('entities.receivedThroughput') }}&nbsp;:&nbsp;&nbsp;</span>
<span class="row-item-value">
{{ unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-' }}
{{ valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-' }}
</span>
<div class="item-box-loading">
<loading :loading="loading" size="small"></loading>

View File

@@ -31,13 +31,13 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.peak')}}</div>
<div class="row__content">
{{unitConvert(entityData.max, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.max, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.mean')}}</div>
<div class="row__content">
{{unitConvert(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
@@ -45,7 +45,7 @@
<div class="row__contents">
<div class="row__content">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -54,7 +54,7 @@
</div>
<div class="row__content row__content-accept">
<div class="row__charts-msg">{{$t('overall.received')}}
{{unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
{{valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -212,7 +212,7 @@
import { api } from '@/utils/api'
import entityDetailMixin from './entityDetailMixin'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import Chart from '@/views/charts/Chart'
import _ from 'lodash'
import { get } from '@/utils/http'
@@ -372,7 +372,7 @@ export default {
entityCopy: {
..._.cloneDeep(props.entity)
},
unitConvert,
valueToRangeValue,
entityData
}
}

View File

@@ -39,13 +39,13 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.peak')}}</div>
<div class="row__content">
{{unitConvert(entityData.max, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.max, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.mean')}}</div>
<div class="row__label row__label--width130">{{$t('overall.average')}}</div>
<div class="row__content">
{{unitConvert(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
@@ -53,7 +53,7 @@
<div class="row__contents">
<div class="row__content">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -62,7 +62,7 @@
</div>
<div class="row__content row__content-accept">
<div class="row__charts-msg">{{$t('overall.received')}}
{{unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
{{valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -216,7 +216,7 @@
import { api } from '@/utils/api'
import entityDetailMixin from './entityDetailMixin'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import Chart from '@/views/charts/Chart'
import _ from 'lodash'
import { get } from '@/utils/http'
@@ -397,7 +397,7 @@ export default {
},
entityCopy,
unitTypes,
unitConvert,
valueToRangeValue,
entityData
}
}

View File

@@ -4,7 +4,16 @@
<div class="overview__content">
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.location')}}</div>
<div class="row__content">{{ipLocationRegion(entity.location)}}</div>
<div class="row__content">
<div v-if="entity.location" style="display: flex">
<div v-if="entity.location.country">
<img v-if="entity.location.country===countryNameIdMapping.Unknown || !countryNameIdMapping[entity.location.country]" src="../../../../../public/images/flag/Unknown.svg" class="filter-country-flag">
<img v-else :src="require(`../../../../../public/images/flag/${countryNameIdMapping[entity.location.country]}.png`)" class="filter-country-flag" >
</div>
{{ipLocationRegion(entity.location)}}
</div>
<div v-else>-</div>
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">ASN</div>
@@ -44,7 +53,7 @@
<div class="row__label row__label--width130">{{$t('overall.dnsServerInfo.queryRate')}}</div>
<div class="row__contents">
<div class="row__content">
<div class="row__charts-msg">{{unitConvert(entityData.queryRate, unitTypes.byte).join(' ')}}ps</div>
<div class="row__charts-msg">{{valueToRangeValue(entityData.queryRate, unitTypes.byte).join(' ')}}ps</div>
<!-- 曲线-->
<div class="row__content-loading">
<loading :loading="!loadingDns && loading" size="small"></loading>
@@ -62,21 +71,21 @@
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.peak')}}</div>
<div class="row__content">
{{unitConvert(entityData.max, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.max, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.max, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.mean')}}</div>
<div class="row__label row__label--width130">{{$t('overall.average')}}</div>
<div class="row__content">
{{unitConvert(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
{{valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.avg, unitTypes.byte).join(' ') + '/s' : '-'}}
</div>
</div>
<div class="overview__row">
<div class="row__label row__label--width130">{{$t('overall.throughput')}}</div>
<div class="row__contents">
<div class="row__content">
<div class="row__charts-msg">{{$t('overall.sent')}}
{{unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
<div class="row__charts-msg">{{$t('overall.sent')}}:
{{valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesSentRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -84,8 +93,8 @@
</div>
</div>
<div class="row__content row__content-accept">
<div class="row__charts-msg">{{$t('overall.received')}}
{{unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? unitConvert(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
<div class="row__charts-msg">{{$t('overall.received')}}:
{{valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') !== '- ' ? valueToRangeValue(entityData.bytesReceivedRate, unitTypes.byte).join(' ') + 'ps' : '-'}}
</div>
<!-- 曲线-->
<div class="row__content-loading">
@@ -101,7 +110,7 @@
<div class="circle-icon" v-if="score <= 2 || score === '-'" :class="{'data-score-red': score <= 2 || score === '-'}" ></div>
<div class="circle-icon" v-else-if="score <= 4" :class="{'data-score-yellow': score <= 4}" ></div>
<div class="circle-icon" v-else-if="score <= 6" :class="{'data-score-green': score <= 6}" ></div>
Score:{{score}}
Score:&nbsp;{{score}}
</div>
<loading :loading="loadingNetworkQuality" size="small" style="left: 1rem;width: 50%;"></loading>
@@ -240,7 +249,7 @@
import entityDetailMixin from './entityDetailMixin'
import { api } from '@/utils/api'
import { unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import Chart from '@/views/charts/Chart'
import _ from 'lodash'
import { get } from '@/utils/http'
@@ -248,6 +257,7 @@ import relatedServer from '@/mixins/relatedServer'
import { dateFormatByAppearance, getMillisecond, getSecond } from '@/utils/date-util'
import Loading from '@/components/common/Loading'
import axios from 'axios'
import { countryNameIdMapping } from '@/utils/constants'
export default {
name: 'Ip',
@@ -345,12 +355,16 @@ export default {
loadingAlert: false,
loadingSecurityEvents: false,
loadingMap: false,
openPort: '-'
openPort: '-',
countryNameIdMapping
}
},
computed: {
ipLocationRegion () {
return function (entityData) {
if (!entityData) {
return '-'
}
let result = ''
if (entityData.country) {
result += `${entityData.country},`
@@ -465,7 +479,7 @@ export default {
},
entityCopy,
unitTypes,
unitConvert
valueToRangeValue
}
}
}

View File

@@ -3,7 +3,6 @@ import { get } from '@/utils/http'
import * as echarts from 'echarts'
import { entityListLineOption } from '@/views/charts/charts/chart-options'
import { riskLevelMapping, unitTypes } from '@/utils/constants'
import unitConvert from '@/utils/unit-convert'
import { shallowRef, markRaw } from 'vue'
import { metricOption } from '@/views/detections/options/detectionOptions'
import { sortBy, reverseSortBy, computeScore } from '@/utils/tools'
@@ -498,8 +497,7 @@ export default {
},
setup () {
return {
unitTypes,
unitConvert
unitTypes
}
},
mounted () {

View File

@@ -5,7 +5,7 @@ import * as echarts from 'echarts'
import { entityListLineOption } from '@/views/charts/charts/chart-options'
import { riskLevelMapping, unitTypes } from '@/utils/constants'
import { getSecond } from '@/utils/date-util'
import unitConvert from '@/utils/unit-convert'
import { valueToRangeValue } from '@/utils/unit-convert'
import { shallowRef } from 'vue'
export default {
@@ -20,7 +20,7 @@ export default {
trafficUrl: '',
chartOption: null,
unitTypes,
unitConvert,
valueToRangeValue,
echartsArray: []
}
},

View File

@@ -88,9 +88,9 @@ describe('views/charts2/charts/entityDetail/EntityDetailLine.vue测试', () => {
expect(titleNode0.text()).toEqual('network.total')
expect(titleNode1.text()).toEqual('network.outbound')
expect(titleNode2.text()).toEqual('network.other')
expect(textNode0.text()).toEqual('0bps')
expect(textNode0.text()).toEqual('<0.01bps')
expect(textNode1.text()).toEqual('95.23Ebps')
expect(textNode2.text()).toEqual('<1bps')
expect(textNode2.text()).toEqual('0.01bps')
resolve()
}, 200))
})

View File

@@ -165,9 +165,9 @@ describe('views/charts2/charts/linkMonitor/LinkTrafficLine.vue测试', () => {
const textNode2 = await wrapper.get('[test-id="tabContent2"]')
await new Promise(resolve => setTimeout(() => {
expect(textNode0.text()).toEqual('0bps')
expect(textNode1.text()).toEqual('0bps')
expect(textNode2.text()).toEqual('0bps')
expect(textNode0.text()).toEqual('<0.01bps')
expect(textNode1.text()).toEqual('<0.01bps')
expect(textNode2.text()).toEqual('<0.01bps')
resolve()
}, 200))
})

View File

@@ -420,7 +420,7 @@ describe('views/charts2/charts/networkOverview/NetworkOverviewApps.vue测试', (
expect(testrate0.text()).toEqual('31.33 G')
expect(testrate1.text()).toEqual('84.59 T')
expect(testrate2.text()).toEqual('500.75 K')
expect(testrate3.text()).toEqual('0')
expect(testrate3.text()).toEqual('<0.01')
expect(testrate4.text()).toEqual('-')
const testPercent0 = wrapper.get('[test-id="percent0"]')

View File

@@ -62,7 +62,7 @@ describe('views/charts2/charts/networkOverview/NetworkOverviewLine.vue测试', (
expect(titleNode0.text()).toEqual('network.total')
expect(titleNode1.text()).toEqual('network.outbound')
expect(titleNode2.text()).toEqual('network.other')
expect(textNode0.text()).toEqual('0bps')
expect(textNode0.text()).toEqual('<0.01bps')
expect(textNode1.text()).toEqual('95.23Ebps')
expect(textNode2.text()).toEqual('0.01bps')
resolve()

View File

@@ -169,12 +169,12 @@ describe('views/charts2/charts/npm/NpmAppCategoryScore.vue测试', () => {
const textNode4 = wrapper.find('[test-id="inbound-packet-multimedia-streaming"]')
const textNode5 = wrapper.find('[test-id="inbound-packet-social-networking"]')
expect(textNode0.text()).toBe('0 bps')
expect(textNode1.text()).toBe('0 bps')
expect(textNode2.text()).toBe('0 bps')
expect(textNode3.text()).toBe('0 bps')
expect(textNode4.text()).toBe('0 bps')
expect(textNode5.text()).toBe('0 bps')
expect(textNode0.text()).toBe('<0.01 bps')
expect(textNode1.text()).toBe('<0.01 bps')
expect(textNode2.text()).toBe('<0.01 bps')
expect(textNode3.text()).toBe('<0.01 bps')
expect(textNode4.text()).toBe('<0.01 bps')
expect(textNode5.text()).toBe('<0.01 bps')
resolve()
}, 200))
})

View File

@@ -119,7 +119,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
expect(p95Node0.text()).toEqual('P95:445 ms')
expect(p95Node1.text()).toEqual('P95:2.95 s')
expect(p95Node2.text()).toEqual('P95:0 ms')
expect(p95Node2.text()).toEqual('P95:<1 ms')
expect(p95Node3.text()).toEqual('P95:3.46 %')
expect(p95Node4.text()).toEqual('P95:10.47 %')
@@ -131,7 +131,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
expect(p99Node0.text()).toEqual('P99:601 ms')
expect(p99Node1.text()).toEqual('P99:4.62 s')
expect(p99Node2.text()).toEqual('P99:0 ms')
expect(p99Node2.text()).toEqual('P99:<1 ms')
expect(p99Node3.text()).toEqual('P99:13.72 %')
expect(p99Node4.text()).toEqual('P99:18.30 %')
resolve()
@@ -195,7 +195,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
expect(p95Node0.text()).toEqual('P95:160 ms')
expect(p95Node1.text()).toEqual('P95:266 ms')
expect(p95Node2.text()).toEqual('P95:0 ms')
expect(p95Node2.text()).toEqual('P95:<1 ms')
expect(p95Node3.text()).toEqual('P95:2.23 %')
expect(p95Node4.text()).toEqual('P95:6.60 %')
@@ -207,7 +207,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
expect(p99Node0.text()).toEqual('P99:789 ms')
expect(p99Node1.text()).toEqual('P99:2.09 s')
expect(p99Node2.text()).toEqual('P99:0 ms')
expect(p99Node2.text()).toEqual('P99:<1 ms')
expect(p99Node3.text()).toEqual('P99:21.87 %')
expect(p99Node4.text()).toEqual('P99:9.26 %')
resolve()
@@ -341,7 +341,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
const p95Node3 = wrapper.get('[test-id="singleValueP953"]')
const p95Node4 = wrapper.get('[test-id="singleValueP954"]')
expect(p95Node0.text()).toEqual('P95:0 ms')
expect(p95Node0.text()).toEqual('P95:<1 ms')
expect(p95Node1.text()).toEqual('P95:4.92 m')
expect(p95Node2.text()).toEqual('P95:10.00 s')
expect(p95Node3.text()).toEqual('P95:3.46 %')
@@ -353,7 +353,7 @@ describe('views/charts2/charts/npm/NpmNetworkQuantity.vue测试', () => {
const p99Node3 = wrapper.get('[test-id="singleValueP993"]')
const p99Node4 = wrapper.get('[test-id="singleValueP994"]')
expect(p99Node0.text()).toEqual('P99:0 ms')
expect(p99Node0.text()).toEqual('P99:<1 ms')
expect(p99Node1.text()).toEqual('P99:7.70 m')
expect(p99Node2.text()).toEqual('P99:10.00 s')
expect(p99Node3.text()).toEqual('P99:13.72 %')