mirror of https://github.com/veops/cmdb.git
feat(ui): add ipam
This commit is contained in:
parent
cce88bb4b0
commit
c50133b3e4
|
@ -54,6 +54,108 @@
|
||||||
<div class="content unicode" style="display: block;">
|
<div class="content unicode" style="display: block;">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ops-setting-holiday_management-copy</div>
|
||||||
|
<div class="code-name">&#xe9fa;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-system_log</div>
|
||||||
|
<div class="code-name">&#xe9f8;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ops-setting-adjustday</div>
|
||||||
|
<div class="code-name">&#xe9f6;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ops-setting-holiday</div>
|
||||||
|
<div class="code-name">&#xe9f7;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ops-setting-festival</div>
|
||||||
|
<div class="code-name">&#xe9f5;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-count</div>
|
||||||
|
<div class="code-name">&#xe9f4;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">itsm-satisfaction</div>
|
||||||
|
<div class="code-name">&#xe9f3;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-folder</div>
|
||||||
|
<div class="code-name">&#xe9f2;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-entire_network_</div>
|
||||||
|
<div class="code-name">&#xe9f1;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-subnet</div>
|
||||||
|
<div class="code-name">&#xe9f0;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-map_view</div>
|
||||||
|
<div class="code-name">&#xe9ef;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-recycle</div>
|
||||||
|
<div class="code-name">&#xe9ee;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-catalog</div>
|
||||||
|
<div class="code-name">&#xe9ed;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">veops-ipam</div>
|
||||||
|
<div class="code-name">&#xe9ec;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">cmdb-calc</div>
|
||||||
|
<div class="code-name">&#xe9eb;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-users</div>
|
||||||
|
<div class="code-name">&#xe9ea;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont"></span>
|
||||||
|
<div class="name">ai-tokens</div>
|
||||||
|
<div class="code-name">&#xe9e9;</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont"></span>
|
<span class="icon iconfont"></span>
|
||||||
<div class="name">oneterm-mysql</div>
|
<div class="name">oneterm-mysql</div>
|
||||||
|
@ -6000,9 +6102,9 @@
|
||||||
<pre><code class="language-css"
|
<pre><code class="language-css"
|
||||||
>@font-face {
|
>@font-face {
|
||||||
font-family: 'iconfont';
|
font-family: 'iconfont';
|
||||||
src: url('iconfont.woff2?t=1729157759723') format('woff2'),
|
src: url('iconfont.woff2?t=1731312848138') format('woff2'),
|
||||||
url('iconfont.woff?t=1729157759723') format('woff'),
|
url('iconfont.woff?t=1731312848138') format('woff'),
|
||||||
url('iconfont.ttf?t=1729157759723') format('truetype');
|
url('iconfont.ttf?t=1731312848138') format('truetype');
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||||
|
@ -6028,6 +6130,159 @@
|
||||||
<div class="content font-class">
|
<div class="content font-class">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ops-setting-holidays"></span>
|
||||||
|
<div class="name">
|
||||||
|
ops-setting-holiday_management-copy
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ops-setting-holidays
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ops-itsm-logs"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-system_log
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ops-itsm-logs
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ops-setting-workday"></span>
|
||||||
|
<div class="name">
|
||||||
|
ops-setting-adjustday
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ops-setting-workday
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ops-setting-holiday"></span>
|
||||||
|
<div class="name">
|
||||||
|
ops-setting-holiday
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ops-setting-holiday
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ops-setting-festival"></span>
|
||||||
|
<div class="name">
|
||||||
|
ops-setting-festival
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ops-setting-festival
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-calc"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-count
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-calc
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont itsm-reports_4"></span>
|
||||||
|
<div class="name">
|
||||||
|
itsm-satisfaction
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.itsm-reports_4
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-folder"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-folder
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-folder
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-entire_network_"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-entire_network_
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-entire_network_
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-subnet"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-subnet
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-subnet
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-map_view"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-map_view
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-map_view
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-recycle"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-recycle
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-recycle
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-catalog"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-catalog
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-catalog
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont veops-ipam"></span>
|
||||||
|
<div class="name">
|
||||||
|
veops-ipam
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.veops-ipam
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont cmdb-calc"></span>
|
||||||
|
<div class="name">
|
||||||
|
cmdb-calc
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.cmdb-calc
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-users"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-users
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-users
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<span class="icon iconfont ai-tokens"></span>
|
||||||
|
<div class="name">
|
||||||
|
ai-tokens
|
||||||
|
</div>
|
||||||
|
<div class="code-name">.ai-tokens
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont oneterm-mysql"></span>
|
<span class="icon iconfont oneterm-mysql"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
|
@ -8162,20 +8417,20 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont itsm-duration"></span>
|
<span class="icon iconfont itsm-reports_3"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
itsm-duration
|
itsm-duration
|
||||||
</div>
|
</div>
|
||||||
<div class="code-name">.itsm-duration
|
<div class="code-name">.itsm-reports_3
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<span class="icon iconfont itsm-workload"></span>
|
<span class="icon iconfont itsm-reports_2"></span>
|
||||||
<div class="name">
|
<div class="name">
|
||||||
itsm-workload (1)
|
itsm-workload (1)
|
||||||
</div>
|
</div>
|
||||||
<div class="code-name">.itsm-workload
|
<div class="code-name">.itsm-reports_2
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -14947,6 +15202,142 @@
|
||||||
<div class="content symbol">
|
<div class="content symbol">
|
||||||
<ul class="icon_lists dib-box">
|
<ul class="icon_lists dib-box">
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ops-setting-holidays"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ops-setting-holiday_management-copy</div>
|
||||||
|
<div class="code-name">#ops-setting-holidays</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ops-itsm-logs"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-system_log</div>
|
||||||
|
<div class="code-name">#ops-itsm-logs</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ops-setting-workday"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ops-setting-adjustday</div>
|
||||||
|
<div class="code-name">#ops-setting-workday</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ops-setting-holiday"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ops-setting-holiday</div>
|
||||||
|
<div class="code-name">#ops-setting-holiday</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ops-setting-festival"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ops-setting-festival</div>
|
||||||
|
<div class="code-name">#ops-setting-festival</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-calc"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-count</div>
|
||||||
|
<div class="code-name">#itsm-calc</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#itsm-reports_4"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">itsm-satisfaction</div>
|
||||||
|
<div class="code-name">#itsm-reports_4</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-folder"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-folder</div>
|
||||||
|
<div class="code-name">#veops-folder</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-entire_network_"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-entire_network_</div>
|
||||||
|
<div class="code-name">#veops-entire_network_</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-subnet"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-subnet</div>
|
||||||
|
<div class="code-name">#veops-subnet</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-map_view"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-map_view</div>
|
||||||
|
<div class="code-name">#veops-map_view</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-recycle"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-recycle</div>
|
||||||
|
<div class="code-name">#veops-recycle</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-catalog"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-catalog</div>
|
||||||
|
<div class="code-name">#veops-catalog</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#veops-ipam"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">veops-ipam</div>
|
||||||
|
<div class="code-name">#veops-ipam</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#cmdb-calc"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">cmdb-calc</div>
|
||||||
|
<div class="code-name">#cmdb-calc</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-users"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-users</div>
|
||||||
|
<div class="code-name">#ai-users</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="dib">
|
||||||
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
|
<use xlink:href="#ai-tokens"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="name">ai-tokens</div>
|
||||||
|
<div class="code-name">#ai-tokens</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#oneterm-mysql"></use>
|
<use xlink:href="#oneterm-mysql"></use>
|
||||||
|
@ -16845,18 +17236,18 @@
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#itsm-duration"></use>
|
<use xlink:href="#itsm-reports_3"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="name">itsm-duration</div>
|
<div class="name">itsm-duration</div>
|
||||||
<div class="code-name">#itsm-duration</div>
|
<div class="code-name">#itsm-reports_3</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
<svg class="icon svg-icon" aria-hidden="true">
|
<svg class="icon svg-icon" aria-hidden="true">
|
||||||
<use xlink:href="#itsm-workload"></use>
|
<use xlink:href="#itsm-reports_2"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="name">itsm-workload (1)</div>
|
<div class="name">itsm-workload (1)</div>
|
||||||
<div class="code-name">#itsm-workload</div>
|
<div class="code-name">#itsm-reports_2</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dib">
|
<li class="dib">
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 3857903 */
|
font-family: "iconfont"; /* Project id 3857903 */
|
||||||
src: url('iconfont.woff2?t=1729157759723') format('woff2'),
|
src: url('iconfont.woff2?t=1731312848138') format('woff2'),
|
||||||
url('iconfont.woff?t=1729157759723') format('woff'),
|
url('iconfont.woff?t=1731312848138') format('woff'),
|
||||||
url('iconfont.ttf?t=1729157759723') format('truetype');
|
url('iconfont.ttf?t=1731312848138') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
|
@ -13,6 +13,74 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-setting-holidays:before {
|
||||||
|
content: "\e9fa";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-itsm-logs:before {
|
||||||
|
content: "\e9f8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-workday:before {
|
||||||
|
content: "\e9f6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-holiday:before {
|
||||||
|
content: "\e9f7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-setting-festival:before {
|
||||||
|
content: "\e9f5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-calc:before {
|
||||||
|
content: "\e9f4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.itsm-reports_4:before {
|
||||||
|
content: "\e9f3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-folder:before {
|
||||||
|
content: "\e9f2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-entire_network_:before {
|
||||||
|
content: "\e9f1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-subnet:before {
|
||||||
|
content: "\e9f0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-map_view:before {
|
||||||
|
content: "\e9ef";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-recycle:before {
|
||||||
|
content: "\e9ee";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-catalog:before {
|
||||||
|
content: "\e9ed";
|
||||||
|
}
|
||||||
|
|
||||||
|
.veops-ipam:before {
|
||||||
|
content: "\e9ec";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmdb-calc:before {
|
||||||
|
content: "\e9eb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-users:before {
|
||||||
|
content: "\e9ea";
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-tokens:before {
|
||||||
|
content: "\e9e9";
|
||||||
|
}
|
||||||
|
|
||||||
.oneterm-mysql:before {
|
.oneterm-mysql:before {
|
||||||
content: "\e9e8";
|
content: "\e9e8";
|
||||||
}
|
}
|
||||||
|
@ -961,11 +1029,11 @@
|
||||||
content: "\e914";
|
content: "\e914";
|
||||||
}
|
}
|
||||||
|
|
||||||
.itsm-duration:before {
|
.itsm-reports_3:before {
|
||||||
content: "\e913";
|
content: "\e913";
|
||||||
}
|
}
|
||||||
|
|
||||||
.itsm-workload:before {
|
.itsm-reports_2:before {
|
||||||
content: "\e912";
|
content: "\e912";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -5,6 +5,125 @@
|
||||||
"css_prefix_text": "",
|
"css_prefix_text": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "42337844",
|
||||||
|
"name": "ops-setting-holiday_management-copy",
|
||||||
|
"font_class": "ops-setting-holidays",
|
||||||
|
"unicode": "e9fa",
|
||||||
|
"unicode_decimal": 59898
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42335414",
|
||||||
|
"name": "itsm-system_log",
|
||||||
|
"font_class": "ops-itsm-logs",
|
||||||
|
"unicode": "e9f8",
|
||||||
|
"unicode_decimal": 59896
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334782",
|
||||||
|
"name": "ops-setting-adjustday",
|
||||||
|
"font_class": "ops-setting-workday",
|
||||||
|
"unicode": "e9f6",
|
||||||
|
"unicode_decimal": 59894
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334768",
|
||||||
|
"name": "ops-setting-holiday",
|
||||||
|
"font_class": "ops-setting-holiday",
|
||||||
|
"unicode": "e9f7",
|
||||||
|
"unicode_decimal": 59895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42334734",
|
||||||
|
"name": "ops-setting-festival",
|
||||||
|
"font_class": "ops-setting-festival",
|
||||||
|
"unicode": "e9f5",
|
||||||
|
"unicode_decimal": 59893
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42281202",
|
||||||
|
"name": "itsm-count",
|
||||||
|
"font_class": "itsm-calc",
|
||||||
|
"unicode": "e9f4",
|
||||||
|
"unicode_decimal": 59892
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42270632",
|
||||||
|
"name": "itsm-satisfaction",
|
||||||
|
"font_class": "itsm-reports_4",
|
||||||
|
"unicode": "e9f3",
|
||||||
|
"unicode_decimal": 59891
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205149",
|
||||||
|
"name": "veops-folder",
|
||||||
|
"font_class": "veops-folder",
|
||||||
|
"unicode": "e9f2",
|
||||||
|
"unicode_decimal": 59890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205128",
|
||||||
|
"name": "veops-entire_network_",
|
||||||
|
"font_class": "veops-entire_network_",
|
||||||
|
"unicode": "e9f1",
|
||||||
|
"unicode_decimal": 59889
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42205094",
|
||||||
|
"name": "veops-subnet",
|
||||||
|
"font_class": "veops-subnet",
|
||||||
|
"unicode": "e9f0",
|
||||||
|
"unicode_decimal": 59888
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201912",
|
||||||
|
"name": "veops-map_view",
|
||||||
|
"font_class": "veops-map_view",
|
||||||
|
"unicode": "e9ef",
|
||||||
|
"unicode_decimal": 59887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201676",
|
||||||
|
"name": "veops-recycle",
|
||||||
|
"font_class": "veops-recycle",
|
||||||
|
"unicode": "e9ee",
|
||||||
|
"unicode_decimal": 59886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201586",
|
||||||
|
"name": "veops-catalog",
|
||||||
|
"font_class": "veops-catalog",
|
||||||
|
"unicode": "e9ed",
|
||||||
|
"unicode_decimal": 59885
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42201534",
|
||||||
|
"name": "veops-ipam",
|
||||||
|
"font_class": "veops-ipam",
|
||||||
|
"unicode": "e9ec",
|
||||||
|
"unicode_decimal": 59884
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42179262",
|
||||||
|
"name": "cmdb-calc",
|
||||||
|
"font_class": "cmdb-calc",
|
||||||
|
"unicode": "e9eb",
|
||||||
|
"unicode_decimal": 59883
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42161413",
|
||||||
|
"name": "ai-users",
|
||||||
|
"font_class": "ai-users",
|
||||||
|
"unicode": "e9ea",
|
||||||
|
"unicode_decimal": 59882
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42161417",
|
||||||
|
"name": "ai-tokens",
|
||||||
|
"font_class": "ai-tokens",
|
||||||
|
"unicode": "e9e9",
|
||||||
|
"unicode_decimal": 59881
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "42155223",
|
"icon_id": "42155223",
|
||||||
"name": "oneterm-mysql",
|
"name": "oneterm-mysql",
|
||||||
|
@ -1667,14 +1786,14 @@
|
||||||
{
|
{
|
||||||
"icon_id": "39926816",
|
"icon_id": "39926816",
|
||||||
"name": "itsm-duration",
|
"name": "itsm-duration",
|
||||||
"font_class": "itsm-duration",
|
"font_class": "itsm-reports_3",
|
||||||
"unicode": "e913",
|
"unicode": "e913",
|
||||||
"unicode_decimal": 59667
|
"unicode_decimal": 59667
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "39926833",
|
"icon_id": "39926833",
|
||||||
"name": "itsm-workload (1)",
|
"name": "itsm-workload (1)",
|
||||||
"font_class": "itsm-workload",
|
"font_class": "itsm-reports_2",
|
||||||
"unicode": "e912",
|
"unicode": "e912",
|
||||||
"unicode_decimal": 59666
|
"unicode_decimal": 59666
|
||||||
},
|
},
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,109 @@
|
||||||
|
import { axios } from '@/utils/request'
|
||||||
|
|
||||||
|
export function getIPAMSubnet() {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMSubnet(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMSubnetById(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putIPAMSubnet(id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIPAMSubnet(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/subnet/${id}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMScope(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/scope',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function putIPAMScope(id, data) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/scope/${id}`,
|
||||||
|
method: 'PUT',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIPAMScope(id) {
|
||||||
|
return axios({
|
||||||
|
url: `/v0.1/ipam/scope/${id}`,
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMAddress(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/address',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHosts(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/subnet/hosts',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postIPAMAddress(data) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/address',
|
||||||
|
method: 'POST',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHistoryOperate(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/history/operate',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMHistoryScan(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/history/scan',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIPAMStats(params) {
|
||||||
|
return axios({
|
||||||
|
url: '/v0.1/ipam/stats',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import { axios } from '@/utils/request'
|
import { axios } from '@/utils/request'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
import i18n from '@/lang'
|
||||||
|
|
||||||
export function getPreference(instance = true, tree = null) {
|
export function getPreference(instance = true, tree = null) {
|
||||||
return axios({
|
return axios({
|
||||||
|
@ -16,11 +18,35 @@ export function getPreference2(instance = true, tree = null) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubscribeAttributes(ciTypeId) {
|
export function getSubscribeAttributes(ciTypeId, formatDefaultAttr = true) {
|
||||||
return axios({
|
return new Promise(async (resolve) => {
|
||||||
|
const res = await axios({
|
||||||
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
|
url: `/v0.1/preference/ci_types/${ciTypeId}/attributes`,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
formatDefaultAttr &&
|
||||||
|
res?.attributes?.length
|
||||||
|
) {
|
||||||
|
res.attributes.forEach((item) => {
|
||||||
|
switch (item.name) {
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_USER:
|
||||||
|
item.id = item.name
|
||||||
|
item.alias = i18n.t('cmdb.components.updater')
|
||||||
|
break
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_TIME:
|
||||||
|
item.id = item.name
|
||||||
|
item.alias = i18n.t('cmdb.components.updateTime')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubscribeTreeView() {
|
export function getSubscribeTreeView() {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -22,7 +22,7 @@
|
||||||
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
|
<draggable :value="targetKeys" animation="300" @end="dragEnd" :disabled="!isSortable">
|
||||||
<div
|
<div
|
||||||
@dblclick="changeSingleItem(item)"
|
@dblclick="changeSingleItem(item)"
|
||||||
v-for="item in filteredItems"
|
v-for="item in filterDefaultAttr(filteredItems)"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:style="{ height: '38px' }"
|
:style="{ height: '38px' }"
|
||||||
>
|
>
|
||||||
|
@ -54,11 +54,44 @@
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="rightDefaultAttrList.length"
|
||||||
|
class="default-attr"
|
||||||
|
>
|
||||||
|
<a-divider>
|
||||||
|
<span class="default-attr-divider">
|
||||||
|
{{ $t('cmdb.components.default') }}
|
||||||
|
</span>
|
||||||
|
</a-divider>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(item) in rightDefaultAttrList"
|
||||||
|
:key="item.key"
|
||||||
|
:class="['default-attr-item', selectedKeys.includes(item.key) ? 'default-attr-item-selected' : '']"
|
||||||
|
@click="setSelectedKeys(item)"
|
||||||
|
@dblclick="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="default-attr-arrow"
|
||||||
|
style="left: 17px"
|
||||||
|
@click.stop="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<a-icon type="left" />
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
<div v-if="direction === 'left'" class="ant-transfer-list-content">
|
||||||
<div
|
<div
|
||||||
@dblclick="changeSingleItem(item)"
|
@dblclick="changeSingleItem(item)"
|
||||||
v-for="item in filteredItems"
|
v-for="item in filterDefaultAttr(filteredItems)"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:style="{ height: '38px' }"
|
:style="{ height: '38px' }"
|
||||||
>
|
>
|
||||||
|
@ -82,6 +115,39 @@
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="leftDefaultAttrList.length"
|
||||||
|
class="default-attr"
|
||||||
|
>
|
||||||
|
<a-divider>
|
||||||
|
<span class="default-attr-divider">
|
||||||
|
{{ $t('cmdb.components.default') }}
|
||||||
|
</span>
|
||||||
|
</a-divider>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(item) in leftDefaultAttrList"
|
||||||
|
:key="item.key"
|
||||||
|
:class="['default-attr-item', selectedKeys.includes(item.key) ? 'default-attr-item-selected' : '']"
|
||||||
|
@click="setSelectedKeys(item)"
|
||||||
|
@dblclick="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="default-attr-arrow"
|
||||||
|
style="left: 2px"
|
||||||
|
@click.stop="changeSingleItem(item)"
|
||||||
|
>
|
||||||
|
<a-icon type="right" />
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-title">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</div>
|
||||||
|
<div class="default-attr-name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-transfer>
|
</a-transfer>
|
||||||
|
@ -95,6 +161,7 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AttributesTransfer',
|
name: 'AttributesTransfer',
|
||||||
|
@ -130,10 +197,41 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 400,
|
default: 400,
|
||||||
},
|
},
|
||||||
|
showDefaultAttr: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
|
defaultAttrList: [
|
||||||
|
{
|
||||||
|
title: 'cmdb.components.updater',
|
||||||
|
name: 'updater',
|
||||||
|
key: CI_DEFAULT_ATTR.UPDATE_USER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'cmdb.components.updateTime',
|
||||||
|
name: 'update time',
|
||||||
|
key: CI_DEFAULT_ATTR.UPDATE_TIME
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
rightDefaultAttrList() {
|
||||||
|
if (!this.showDefaultAttr) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.defaultAttrList.filter((item) => this.targetKeys.includes(item.key))
|
||||||
|
},
|
||||||
|
|
||||||
|
leftDefaultAttrList() {
|
||||||
|
if (!this.showDefaultAttr) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.defaultAttrList.filter((item) => !this.targetKeys.includes(item.key))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -216,6 +314,10 @@ export default {
|
||||||
}
|
}
|
||||||
this.$emit('setFixedList', _fixedList)
|
this.$emit('setFixedList', _fixedList)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
filterDefaultAttr(list) {
|
||||||
|
return this.showDefaultAttr ? list.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.key)) : list
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -296,5 +398,67 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-attr {
|
||||||
|
.ant-divider {
|
||||||
|
margin: 7px 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
|
||||||
|
.ant-divider-inner-text {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-divider {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 12px;
|
||||||
|
color: rgb(163, 163, 163);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
display: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: @primary-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
padding-left: 34px;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
position: relative;
|
||||||
|
border-left: solid 2px transparent;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
&-selected {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
border-color: #2f54eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
|
||||||
|
.default-attr-arrow {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -129,6 +129,8 @@ import Treeselect from '@riophae/vue-treeselect'
|
||||||
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
import MetadataDrawer from '../../views/ci/modules/MetadataDrawer.vue'
|
||||||
import FilterComp from '@/components/CMDBFilterComp'
|
import FilterComp from '@/components/CMDBFilterComp'
|
||||||
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
import { getCITypeGroups } from '../../api/ciTypeGroup'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchForm',
|
name: 'SearchForm',
|
||||||
components: { MetadataDrawer, FilterComp, Treeselect },
|
components: { MetadataDrawer, FilterComp, Treeselect },
|
||||||
|
@ -176,7 +178,9 @@ export default {
|
||||||
return '200px'
|
return '200px'
|
||||||
},
|
},
|
||||||
canSearchPreferenceAttrList() {
|
canSearchPreferenceAttrList() {
|
||||||
return this.preferenceAttrList.filter((item) => item.value_type !== '6')
|
return this.preferenceAttrList.filter((item) => {
|
||||||
|
return item.value_type !== '6' && ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.name)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
:fixedList="fixedList"
|
:fixedList="fixedList"
|
||||||
@setFixedList="setFixedList"
|
@setFixedList="setFixedList"
|
||||||
:height="windowHeight - 170"
|
:height="windowHeight - 170"
|
||||||
|
:showDefaultAttr="true"
|
||||||
/>
|
/>
|
||||||
<div class="custom-drawer-bottom-action">
|
<div class="custom-drawer-bottom-action">
|
||||||
<a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
|
<a-button @click="subInstanceSubmit" type="primary">{{ $t('cmdb.preference.sub') }}</a-button>
|
||||||
|
@ -64,7 +65,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cmdb-subscribe-drawer-tree-main" :style="{ maxHeight: `${((windowHeight - 170) * 2) / 3}px` }">
|
<div class="cmdb-subscribe-drawer-tree-main" :style="{ maxHeight: `${((windowHeight - 170) * 2) / 3}px` }">
|
||||||
<div @click="changeTreeViews(attr)" v-for="attr in attrList" :key="attr.name">
|
<div @click="changeTreeViews(attr)" v-for="attr in treeViewAttrList" :key="attr.name">
|
||||||
<a-checkbox :checked="treeViews.includes(attr.name)" />
|
<a-checkbox :checked="treeViews.includes(attr.name)" />
|
||||||
{{ attr.title }}
|
{{ attr.title }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,6 +91,8 @@ import {
|
||||||
} from '@/modules/cmdb/api/preference'
|
} from '@/modules/cmdb/api/preference'
|
||||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
import AttributesTransfer from '../attributesTransfer'
|
import AttributesTransfer from '../attributesTransfer'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SubscribeSetting',
|
name: 'SubscribeSetting',
|
||||||
components: { AttributesTransfer },
|
components: { AttributesTransfer },
|
||||||
|
@ -110,16 +113,32 @@ export default {
|
||||||
...mapState({
|
...mapState({
|
||||||
windowHeight: (state) => state.windowHeight,
|
windowHeight: (state) => state.windowHeight,
|
||||||
}),
|
}),
|
||||||
|
treeViewAttrList() {
|
||||||
|
return this.attrList.filter((item) => ![CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(item.name))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
open(ciType = {}, activeKey = '1') {
|
open(ciType = {}, activeKey = '1') {
|
||||||
this.ciType = ciType
|
this.ciType = ciType
|
||||||
this.activeKey = activeKey
|
this.activeKey = activeKey
|
||||||
|
const updatedByKey = CI_DEFAULT_ATTR.UPDATE_USER
|
||||||
|
const updatedAtKey = CI_DEFAULT_ATTR.UPDATE_TIME
|
||||||
|
|
||||||
getCITypeAttributesByName(ciType.type_id).then((res) => {
|
getCITypeAttributesByName(ciType.type_id).then((res) => {
|
||||||
const attributes = res.attributes
|
const attributes = res.attributes.filter((item) => ![updatedByKey, updatedAtKey].includes(item.name))
|
||||||
|
|
||||||
|
;[updatedByKey, updatedAtKey].map((key) => {
|
||||||
|
attributes.push({
|
||||||
|
alias: key,
|
||||||
|
name: key,
|
||||||
|
id: key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
getSubscribeAttributes(ciType.type_id).then((_res) => {
|
getSubscribeAttributes(ciType.type_id).then((_res) => {
|
||||||
this.instanceSubscribed = _res.is_subscribed
|
this.instanceSubscribed = _res.is_subscribed
|
||||||
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
||||||
|
|
||||||
const attrList = attributes.map((item) => {
|
const attrList = attributes.map((item) => {
|
||||||
return {
|
return {
|
||||||
key: item.id.toString(),
|
key: item.id.toString(),
|
||||||
|
@ -188,9 +207,20 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
subInstanceSubmit() {
|
subInstanceSubmit() {
|
||||||
|
const customAttr = []
|
||||||
|
const defaultAttr = []
|
||||||
|
this.selectedAttrList.map((attr) => {
|
||||||
|
if ([CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(attr)) {
|
||||||
|
defaultAttr.push(attr)
|
||||||
|
} else {
|
||||||
|
customAttr.push(attr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const selectedAttrList = [...customAttr, ...defaultAttr]
|
||||||
|
|
||||||
subscribeCIType(
|
subscribeCIType(
|
||||||
this.ciType.type_id,
|
this.ciType.type_id,
|
||||||
this.selectedAttrList.map((item) => {
|
selectedAttrList.map((item) => {
|
||||||
return [item, !!this.fixedList.includes(item)]
|
return [item, !!this.fixedList.includes(item)]
|
||||||
})
|
})
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
|
|
|
@ -24,7 +24,8 @@ const cmdb_en = {
|
||||||
operationHistory: 'Operation Audit',
|
operationHistory: 'Operation Audit',
|
||||||
relationType: 'Relation Type',
|
relationType: 'Relation Type',
|
||||||
ad: 'AutoDiscovery',
|
ad: 'AutoDiscovery',
|
||||||
cidetail: 'CI Detail'
|
cidetail: 'CI Detail',
|
||||||
|
scene: 'Scene'
|
||||||
},
|
},
|
||||||
ciType: {
|
ciType: {
|
||||||
ciType: 'CIType',
|
ciType: 'CIType',
|
||||||
|
@ -377,6 +378,9 @@ const cmdb_en = {
|
||||||
param: 'Parameter{param}',
|
param: 'Parameter{param}',
|
||||||
value: 'Value{value}',
|
value: 'Value{value}',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
|
updater: 'Update User',
|
||||||
|
updateTime: 'Update Time',
|
||||||
|
default: 'Default'
|
||||||
},
|
},
|
||||||
batch: {
|
batch: {
|
||||||
downloadFailed: 'Download failed',
|
downloadFailed: 'Download failed',
|
||||||
|
@ -757,6 +761,83 @@ if __name__ == "__main__":
|
||||||
conditionName: 'Condition Name',
|
conditionName: 'Condition Name',
|
||||||
path: 'Path',
|
path: 'Path',
|
||||||
expandCondition: 'Expand Condition',
|
expandCondition: 'Expand Condition',
|
||||||
|
},
|
||||||
|
ipam: {
|
||||||
|
overview: 'Overview',
|
||||||
|
addressAssign: 'Address Assign',
|
||||||
|
ipSearch: 'IP Search',
|
||||||
|
subnetList: 'Subnet List',
|
||||||
|
history: 'History Log',
|
||||||
|
ticket: 'Related Tickets',
|
||||||
|
addSubnet: 'Add Subnet',
|
||||||
|
editSubnet: 'Edit Subnet',
|
||||||
|
addCatalog: 'Add Catalog',
|
||||||
|
editCatalog: 'Edit Catalog',
|
||||||
|
catalogName: 'Catalog Name',
|
||||||
|
editName: 'Edit Name',
|
||||||
|
editNode: 'Edit Node',
|
||||||
|
deleteNode: 'Delete Node',
|
||||||
|
basicInfo: 'Basic Info',
|
||||||
|
scanRule: 'Scan Rule',
|
||||||
|
adExecTarget: 'Execute targets',
|
||||||
|
masterMachineTip: 'The machine where OneMaster is installed',
|
||||||
|
oneagentIdTips: 'Please enter the hexadecimal OneAgent ID starting with 0x ID',
|
||||||
|
selectFromCMDBTips: 'Select from CMDB',
|
||||||
|
adInterval: 'Collection frequency',
|
||||||
|
cronTips: 'The format is the same as crontab, for example: 0 15 * * 1-5',
|
||||||
|
masterMachine: 'Master machine',
|
||||||
|
specifyMachine: 'Specify machine',
|
||||||
|
specifyMachineTips: 'Please fill in the specify machine!',
|
||||||
|
cronRequiredTip: 'Acquisition frequency cannot be null',
|
||||||
|
addressNullTip: ' Please select the leaf node of the left subnet tree first',
|
||||||
|
addressNullTip2: 'Subnet prefix length must be >= 16',
|
||||||
|
assignedOnline: 'Assigned Online',
|
||||||
|
assignedOffline: 'Assigned Offline',
|
||||||
|
unassignedOnline: 'Unassigned Online',
|
||||||
|
unused: 'Unused',
|
||||||
|
allStatus: 'All Status',
|
||||||
|
editAssignAddress: 'Edit Assign Address',
|
||||||
|
assign: 'Assign',
|
||||||
|
recycle: 'Recycle',
|
||||||
|
assignStatus: 'Assign Status',
|
||||||
|
reserved: 'Reserved',
|
||||||
|
assigned: 'Assigned',
|
||||||
|
recycleTip: 'Confirmed for recycle? After recycling, the status of the segment will be changed to unassigned.',
|
||||||
|
recycleSuccess: '{ip} Recycled successfully, status changed to: unassigned.',
|
||||||
|
operationLog: 'Operation Log',
|
||||||
|
scanLog: 'Scan Log',
|
||||||
|
updateCatalog: 'Update Catalog',
|
||||||
|
deleteCatalog: 'Delete Catalog',
|
||||||
|
updateSubnet: 'Update Subnet',
|
||||||
|
deleteSubnet: 'Delete Subnet',
|
||||||
|
revokeAddress: 'Revoke Address',
|
||||||
|
operateTime: 'Operate Time',
|
||||||
|
operateUser: 'Operate User',
|
||||||
|
operateType: 'Operate Type',
|
||||||
|
subnet: 'Subnet',
|
||||||
|
description: 'Description',
|
||||||
|
ipNumber: 'Number of online IP',
|
||||||
|
startTime: 'Start Time',
|
||||||
|
endTime: 'End Time',
|
||||||
|
scanningTime: 'Scanning Time',
|
||||||
|
viewResult: 'View Result',
|
||||||
|
scannedIP: 'Scanned IP',
|
||||||
|
subnetStats: 'Subnet Stats',
|
||||||
|
addressStats: 'Address Stats',
|
||||||
|
onlineStats: 'Online Stats',
|
||||||
|
assignStats: 'Assign Stats',
|
||||||
|
total: 'Total',
|
||||||
|
free: 'Free',
|
||||||
|
unassigned: 'Unassigned',
|
||||||
|
online: 'Online',
|
||||||
|
offline: 'Offline',
|
||||||
|
onlineUsageStats: 'Subnet Online Stats',
|
||||||
|
subnetName: 'Subnet Name',
|
||||||
|
addressCount: 'Address Count',
|
||||||
|
onlineRatio: 'Online Ratio',
|
||||||
|
scanEnable: 'Scan Enable',
|
||||||
|
lastScanTime: 'Last Scan Time',
|
||||||
|
isSuccess: 'Is Success'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default cmdb_en
|
export default cmdb_en
|
||||||
|
|
|
@ -24,7 +24,8 @@ const cmdb_zh = {
|
||||||
operationHistory: '操作审计',
|
operationHistory: '操作审计',
|
||||||
relationType: '关系类型',
|
relationType: '关系类型',
|
||||||
ad: '自动发现',
|
ad: '自动发现',
|
||||||
cidetail: 'CI 详情'
|
cidetail: 'CI 详情',
|
||||||
|
scene: '场景'
|
||||||
},
|
},
|
||||||
ciType: {
|
ciType: {
|
||||||
ciType: '模型',
|
ciType: '模型',
|
||||||
|
@ -377,6 +378,9 @@ const cmdb_zh = {
|
||||||
param: '参数{param}',
|
param: '参数{param}',
|
||||||
value: '值{value}',
|
value: '值{value}',
|
||||||
clear: '清空',
|
clear: '清空',
|
||||||
|
updater: '更新人',
|
||||||
|
updateTime: '更新时间',
|
||||||
|
default: '默认'
|
||||||
},
|
},
|
||||||
batch: {
|
batch: {
|
||||||
downloadFailed: '失败下载',
|
downloadFailed: '失败下载',
|
||||||
|
@ -756,6 +760,83 @@ if __name__ == "__main__":
|
||||||
conditionName: '条件命名',
|
conditionName: '条件命名',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
expandCondition: '展开条件',
|
expandCondition: '展开条件',
|
||||||
|
},
|
||||||
|
ipam: {
|
||||||
|
overview: '概览',
|
||||||
|
addressAssign: '地址分配',
|
||||||
|
ipSearch: 'IP查询',
|
||||||
|
subnetList: '子网列表',
|
||||||
|
history: '历史记录',
|
||||||
|
ticket: '关联工单',
|
||||||
|
addSubnet: '新增子网',
|
||||||
|
editSubnet: '编辑子网',
|
||||||
|
addCatalog: '新增目录',
|
||||||
|
editCatalog: '修改目录',
|
||||||
|
catalogName: '目录名称',
|
||||||
|
editName: '修改名称',
|
||||||
|
editNode: '修改节点',
|
||||||
|
deleteNode: '删除节点',
|
||||||
|
basicInfo: '基本信息',
|
||||||
|
scanRule: '扫描规则',
|
||||||
|
adExecTarget: '执行机器',
|
||||||
|
masterMachineTip: '安装OneMaster的所在机器',
|
||||||
|
oneagentIdTips: '请输入以0x开头的16进制OneAgent ID',
|
||||||
|
selectFromCMDBTips: '从CMDB中选择',
|
||||||
|
adInterval: '采集频率',
|
||||||
|
cronTips: '格式同crontab, 例如:0 15 * * 1-5',
|
||||||
|
masterMachine: 'Master机器',
|
||||||
|
specifyMachine: '指定机器',
|
||||||
|
specifyMachineTips: '请填写指定机器!',
|
||||||
|
cronRequiredTip: '采集频率不能为空',
|
||||||
|
addressNullTip: '请先选择左侧子网树的叶子节点',
|
||||||
|
addressNullTip2: '子网前缀长度必须 >= 16',
|
||||||
|
assignedOnline: '已分配在线',
|
||||||
|
assignedOffline: '已分配离线',
|
||||||
|
unassignedOnline: '未分配在线',
|
||||||
|
unused: '空闲',
|
||||||
|
allStatus: '全部状态',
|
||||||
|
editAssignAddress: '编辑分配地址',
|
||||||
|
assign: '分配',
|
||||||
|
recycle: '回收',
|
||||||
|
assignStatus: '分配状态',
|
||||||
|
reserved: '预留',
|
||||||
|
assigned: '已分配',
|
||||||
|
recycleTip: '确认要回收吗?回收后该网段状态变更为:未分配',
|
||||||
|
recycleSuccess: '{ip} 回收成功,状态变更为: 未分配',
|
||||||
|
operationLog: '操作记录',
|
||||||
|
scanLog: '扫描记录',
|
||||||
|
updateCatalog: '更新目录',
|
||||||
|
deleteCatalog: '删除目录',
|
||||||
|
updateSubnet: '修改子网',
|
||||||
|
deleteSubnet: '删除子网',
|
||||||
|
revokeAddress: '地址回收',
|
||||||
|
operateTime: '操作时间',
|
||||||
|
operateUser: '操作人',
|
||||||
|
operateType: '操作类型',
|
||||||
|
subnet: '子网',
|
||||||
|
description: '描述',
|
||||||
|
ipNumber: '在线IP地址数',
|
||||||
|
startTime: '开始时间',
|
||||||
|
endTime: '结束时间',
|
||||||
|
scanningTime: '扫描耗时',
|
||||||
|
viewResult: '查看结果',
|
||||||
|
scannedIP: '已扫描的IP',
|
||||||
|
subnetStats: '子网统计',
|
||||||
|
addressStats: '地址数统计',
|
||||||
|
onlineStats: '在线统计',
|
||||||
|
assignStats: '分配统计',
|
||||||
|
total: '总数',
|
||||||
|
free: '空闲',
|
||||||
|
unassigned: '未分配',
|
||||||
|
online: '在线',
|
||||||
|
offline: '离线',
|
||||||
|
onlineUsageStats: '子网在线统计',
|
||||||
|
subnetName: '子网名称',
|
||||||
|
addressCount: '地址数',
|
||||||
|
onlineRatio: '在线率',
|
||||||
|
scanEnable: '是否扫描',
|
||||||
|
lastScanTime: '最后扫描时间',
|
||||||
|
isSuccess: '是否成功'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default cmdb_zh
|
export default cmdb_zh
|
||||||
|
|
|
@ -70,6 +70,17 @@ const genCmdbRoutes = async () => {
|
||||||
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
|
meta: { title: 'cmdb.menu.cidetail', keepAlive: false },
|
||||||
component: () => import('../views/ci/ciDetailPage.vue')
|
component: () => import('../views/ci/ciDetailPage.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/cmdb/disabled4',
|
||||||
|
name: 'cmdb_disabled4',
|
||||||
|
meta: { title: 'cmdb.menu.scene', appName: 'cmdb', disabled: true, permission: ['admin', 'cmdb_admin'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/cmdb/ipam',
|
||||||
|
component: () => import('../views/ipam'),
|
||||||
|
name: 'cmdb_ipam',
|
||||||
|
meta: { title: 'IPAM', appName: 'cmdb', icon: 'veops-ipam', selectedIcon: 'veops-ipam', keepAlive: false, permission: ['admin', 'cmdb_admin'] }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/cmdb/disabled2',
|
path: '/cmdb/disabled2',
|
||||||
name: 'cmdb_disabled2',
|
name: 'cmdb_disabled2',
|
||||||
|
|
|
@ -33,3 +33,8 @@ export const defautValueColor = [
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7']
|
export const defaultBGColors = ['#ffccc7', '#ffd8bf', '#ffe7ba', '#fff1b8', '#d9f7be', '#b5f5ec', '#bae7ff', '#d6e4ff', '#efdbff', '#ffd6e7']
|
||||||
|
|
||||||
|
export const CI_DEFAULT_ATTR = {
|
||||||
|
UPDATE_USER: '_updated_by',
|
||||||
|
UPDATE_TIME: '_updated_at'
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import XLSX from 'xlsx'
|
import XLSX from 'xlsx'
|
||||||
import XLSXS from 'xlsx-js-style'
|
import XLSXS from 'xlsx-js-style'
|
||||||
|
import { CI_DEFAULT_ATTR } from './const.js'
|
||||||
|
import i18n from '@/lang'
|
||||||
|
|
||||||
export function sum(arr) {
|
export function sum(arr) {
|
||||||
if (!arr.length) {
|
if (!arr.length) {
|
||||||
return 0
|
return 0
|
||||||
|
@ -49,7 +52,10 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
||||||
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
|
const _attrList = _.orderBy(attrList, ['is_fixed'], ['desc'])
|
||||||
const columns = []
|
const columns = []
|
||||||
for (let attr of _attrList) {
|
for (let attr of _attrList) {
|
||||||
const editRender = { name: 'input' }
|
const editRender = {
|
||||||
|
name: 'input',
|
||||||
|
enabled: !attr.is_computed && !attr.sys_computed
|
||||||
|
}
|
||||||
switch (attr.value_type) {
|
switch (attr.value_type) {
|
||||||
case '0':
|
case '0':
|
||||||
editRender['props'] = { 'type': 'float' }
|
editRender['props'] = { 'type': 'float' }
|
||||||
|
@ -83,13 +89,35 @@ export function getCITableColumns(data, attrList, width = 1600, height) {
|
||||||
delete editRender.props
|
delete editRender.props
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let title = attr.alias || attr.name
|
||||||
|
let sortable = !!attr.is_sortable
|
||||||
|
let attr_id = attr.id
|
||||||
|
|
||||||
|
if ([CI_DEFAULT_ATTR.UPDATE_TIME, CI_DEFAULT_ATTR.UPDATE_USER].includes(attr.name)) {
|
||||||
|
editRender.enabled = false
|
||||||
|
attr_id = attr.name
|
||||||
|
|
||||||
|
switch (attr.name) {
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_USER:
|
||||||
|
title = i18n.t('cmdb.components.updater')
|
||||||
|
break;
|
||||||
|
case CI_DEFAULT_ATTR.UPDATE_TIME:
|
||||||
|
title = i18n.t('cmdb.components.updateTime')
|
||||||
|
sortable = true
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
columns.push({
|
columns.push({
|
||||||
attr_id: attr.id,
|
attr_id,
|
||||||
editRender,
|
editRender,
|
||||||
title: attr.alias || attr.name,
|
title,
|
||||||
field: attr.name,
|
field: attr.name,
|
||||||
value_type: attr.value_type,
|
value_type: attr.value_type,
|
||||||
sortable: !!attr.is_sortable,
|
sortable,
|
||||||
filters: attr.is_choice ? attr.choice_value : null,
|
filters: attr.is_choice ? attr.choice_value : null,
|
||||||
choice_builtin: null,
|
choice_builtin: null,
|
||||||
width: Math.min(Math.max(100, ...data.map(item => strLength(item[attr.name]))), 350),
|
width: Math.min(Math.max(100, ...data.map(item => strLength(item[attr.name]))), 350),
|
||||||
|
|
|
@ -259,7 +259,7 @@ export default {
|
||||||
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
|
this.attributeList = _attrList.sort((x, y) => y.is_required - x.is_required)
|
||||||
await getCITypeGroupById(this.typeId).then((res1) => {
|
await getCITypeGroupById(this.typeId).then((res1) => {
|
||||||
const _attributesByGroup = res1.map((g) => {
|
const _attributesByGroup = res1.map((g) => {
|
||||||
g.attributes = g.attributes.filter((attr) => !attr.is_computed)
|
g.attributes = g.attributes.filter((attr) => !attr.is_computed && !attr.sys_computed)
|
||||||
return g
|
return g
|
||||||
})
|
})
|
||||||
const attrHasGroupIds = []
|
const attrHasGroupIds = []
|
||||||
|
@ -268,7 +268,7 @@ export default {
|
||||||
attrHasGroupIds.push(...id)
|
attrHasGroupIds.push(...id)
|
||||||
})
|
})
|
||||||
const otherGroupAttr = this.attributeList.filter(
|
const otherGroupAttr = this.attributeList.filter(
|
||||||
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed
|
(attr) => !attrHasGroupIds.includes(attr.id) && !attr.is_computed && !attr.sys_computed
|
||||||
)
|
)
|
||||||
if (otherGroupAttr.length) {
|
if (otherGroupAttr.length) {
|
||||||
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
_attributesByGroup.push({ id: -1, name: this.$t('other'), attributes: otherGroupAttr })
|
||||||
|
|
|
@ -178,7 +178,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
<a v-if="!isEdit && !attr.is_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a>
|
<a v-if="!isEdit && !attr.is_computed && !attr.sys_computed" @click="handleEdit" :style="{ opacity: 0 }"><a-icon type="edit"/></a>
|
||||||
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
<JsonEditor ref="jsonEditor" @jsonEditorOk="jsonEditorOk" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -267,7 +267,7 @@ export default {
|
||||||
async handleCloseEdit() {
|
async handleCloseEdit() {
|
||||||
const newData = this.form.getFieldValue(this.attr.name)
|
const newData = this.form.getFieldValue(this.attr.name)
|
||||||
if (!_.isEqual(this.ci[this.attr.name], newData)) {
|
if (!_.isEqual(this.ci[this.attr.name], newData)) {
|
||||||
await updateCI(this.ci._id, { [`${this.attr.name}`]: newData })
|
await updateCI(this.ci._id, { [`${this.attr.name}`]: newData ?? null })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$message.success(this.$t('updateSuccess'))
|
this.$message.success(this.$t('updateSuccess'))
|
||||||
this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name)
|
this.$emit('updateCIByself', { [`${this.attr.name}`]: newData }, this.attr.name)
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
:disabled="!canEdit[parent.id]"
|
:disabled="!canEdit[parent.id]"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent.id, 'parents')
|
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, parent, 'parents')
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
><a-icon
|
><a-icon
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
:disabled="!canEdit[child.id]"
|
:disabled="!canEdit[child.id]"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child.id, 'children')
|
$refs.addTableModal.openModal({ [`${ci.unique}`]: ci[ci.unique] }, ci._id, child, 'children')
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
><a-icon
|
><a-icon
|
||||||
|
|
|
@ -78,20 +78,24 @@ export default {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.canvas.setZoomable(true, true)
|
this.canvas.setZoomable(true, true)
|
||||||
|
|
||||||
this.canvas.on('events', ({ type, data }) => {
|
this.canvas.on('events', ({ type, data }) => {
|
||||||
const sourceNode = data?.id || null
|
const sourceNode = data?.id || null
|
||||||
if (type === 'custom:clickLeft') {
|
if (type === 'custom:clickLeft') {
|
||||||
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=1&count=10000`).then((res) => {
|
this.debounceClick(sourceNode, 1)
|
||||||
this.redrawData(res, sourceNode, 'left')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (type === 'custom:clickRight') {
|
if (type === 'custom:clickRight') {
|
||||||
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=0&count=10000`).then((res) => {
|
this.debounceClick(sourceNode, 0)
|
||||||
this.redrawData(res, sourceNode, 'right')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
debounceClick: _.debounce(function(sourceNode, reverse) {
|
||||||
|
searchCIRelation(`root_id=${Number(sourceNode)}&level=1&reverse=${reverse}&count=10000`).then((res) => {
|
||||||
|
this.redrawData(res, sourceNode, reverse === 1 ? 'left' : 'right')
|
||||||
|
})
|
||||||
|
}, 300),
|
||||||
|
|
||||||
setTopoData(data) {
|
setTopoData(data) {
|
||||||
const root = document.getElementById('ci-detail-relation-topo')
|
const root = document.getElementById('ci-detail-relation-topo')
|
||||||
if (root && root?.innerHTML) {
|
if (root && root?.innerHTML) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<AttributesTransfer
|
<AttributesTransfer
|
||||||
:dataSource="attrList"
|
:dataSource="attrList"
|
||||||
:targetKeys="selectedAttrList"
|
:targetKeys="selectedAttrList"
|
||||||
|
:showDefaultAttr="true"
|
||||||
@setTargetKeys="setTargetKeys"
|
@setTargetKeys="setTargetKeys"
|
||||||
@changeSingleItem="changeSingleItem"
|
@changeSingleItem="changeSingleItem"
|
||||||
@handleSubmit="handleSubmit"
|
@handleSubmit="handleSubmit"
|
||||||
|
@ -23,6 +24,8 @@
|
||||||
import AttributesTransfer from '../../../components/attributesTransfer'
|
import AttributesTransfer from '../../../components/attributesTransfer'
|
||||||
import { subscribeCIType, getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
import { subscribeCIType, getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||||
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesByName } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { CI_DEFAULT_ATTR } from '@/modules/cmdb/utils/const.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditAttrsPopover',
|
name: 'EditAttrsPopover',
|
||||||
components: { AttributesTransfer },
|
components: { AttributesTransfer },
|
||||||
|
@ -48,8 +51,19 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getAttrs() {
|
getAttrs() {
|
||||||
|
const updatedByKey = CI_DEFAULT_ATTR.UPDATE_USER
|
||||||
|
const updatedAtKey = CI_DEFAULT_ATTR.UPDATE_TIME
|
||||||
|
|
||||||
getCITypeAttributesByName(this.typeId).then((res) => {
|
getCITypeAttributesByName(this.typeId).then((res) => {
|
||||||
const attributes = res.attributes
|
const attributes = res.attributes.filter((item) => ![updatedByKey, updatedAtKey].includes(item.name))
|
||||||
|
;[updatedByKey, updatedAtKey].map((key) => {
|
||||||
|
attributes.push({
|
||||||
|
alias: key,
|
||||||
|
name: key,
|
||||||
|
id: key
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
getSubscribeAttributes(this.typeId).then((_res) => {
|
getSubscribeAttributes(this.typeId).then((_res) => {
|
||||||
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
const selectedAttrList = _res.attributes.map((item) => item.id.toString())
|
||||||
|
|
||||||
|
@ -71,12 +85,23 @@ export default {
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
if (this.selectedAttrList.length) {
|
if (this.selectedAttrList.length) {
|
||||||
|
const customAttr = []
|
||||||
|
const defaultAttr = []
|
||||||
|
this.selectedAttrList.map((attr) => {
|
||||||
|
if ([CI_DEFAULT_ATTR.UPDATE_USER, CI_DEFAULT_ATTR.UPDATE_TIME].includes(attr)) {
|
||||||
|
defaultAttr.push(attr)
|
||||||
|
} else {
|
||||||
|
customAttr.push(attr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const selectedAttrList = [...customAttr, ...defaultAttr]
|
||||||
|
|
||||||
subscribeCIType(
|
subscribeCIType(
|
||||||
this.typeId,
|
this.typeId,
|
||||||
this.selectedAttrList.map((item) => {
|
selectedAttrList.map((item) => {
|
||||||
return [item, !!this.fixedList.includes(item)]
|
return [item, !!this.fixedList.includes(item)]
|
||||||
})
|
})
|
||||||
).then((res) => {
|
).then(() => {
|
||||||
this.$message.success(this.$t('cmdb.components.subSuccess'))
|
this.$message.success(this.$t('cmdb.components.subSuccess'))
|
||||||
this.visible = false
|
this.visible = false
|
||||||
this.$emit('refresh')
|
this.$emit('refresh')
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:title="$t(actionType === 'edit' ? 'cmdb.ipam.editCatalog' : 'cmdb.ipam.addCatalog')"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form-model
|
||||||
|
ref="catelogFormRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
:label-col="{ span: 5 }"
|
||||||
|
:wrapper-col="{ span: 19 }"
|
||||||
|
>
|
||||||
|
<a-form-model-item
|
||||||
|
:label="$t('cmdb.ipam.catalogName')"
|
||||||
|
prop="name"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
:placeholder="$t('placeholder1')"
|
||||||
|
v-model="form.name"
|
||||||
|
/>
|
||||||
|
</a-form-model-item>
|
||||||
|
</a-form-model>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { postIPAMScope, putIPAMScope } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CatalogForm',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
nodeId: null,
|
||||||
|
actionType: 'create',
|
||||||
|
form: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
formRules: {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
required: true, message: this.$t('placeholder1')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open({
|
||||||
|
nodeId,
|
||||||
|
type,
|
||||||
|
name
|
||||||
|
}) {
|
||||||
|
this.nodeId = nodeId || null
|
||||||
|
this.actionType = type || 'create'
|
||||||
|
this.form.name = name || ''
|
||||||
|
this.visible = true
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
this.visible = false
|
||||||
|
this.form.name = ''
|
||||||
|
this.actionType = 'create'
|
||||||
|
this.nodeId = null
|
||||||
|
|
||||||
|
this.$refs.catelogFormRef.clearValidate()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOk() {
|
||||||
|
this.$refs.catelogFormRef.validate(async (valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.actionType === 'edit') {
|
||||||
|
await putIPAMScope(this.nodeId, {
|
||||||
|
name: this.form.name
|
||||||
|
})
|
||||||
|
this.$message.success(this.$t('editSuccess'))
|
||||||
|
} else {
|
||||||
|
await postIPAMScope({
|
||||||
|
parent_id: this.nodeId,
|
||||||
|
name: this.form.name
|
||||||
|
})
|
||||||
|
this.$message.success(this.$t('addSuccess'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('ok')
|
||||||
|
this.handleCancel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
</style>
|
|
@ -0,0 +1,317 @@
|
||||||
|
<template>
|
||||||
|
<div class="ipam-tree">
|
||||||
|
<a-input
|
||||||
|
v-model="searchValue"
|
||||||
|
class="ipam-tree-search"
|
||||||
|
:placeholder="$t('placeholder1')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="ipam-tree-main">
|
||||||
|
<a-tree
|
||||||
|
v-if="treeData.length"
|
||||||
|
:treeData="filterTreeData"
|
||||||
|
:selectedKeys="treeKey ? [treeKey] : []"
|
||||||
|
:defaultExpandedKeys="treeKey ? [treeKey] : []"
|
||||||
|
>
|
||||||
|
<template #title="treeNodeData">
|
||||||
|
<div
|
||||||
|
class="ipam-tree-node"
|
||||||
|
@click="clickTreeNode(treeNodeData)"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
:type="treeNodeData.icon"
|
||||||
|
class="ipam-tree-node-icon"
|
||||||
|
:style="{ color: treeNodeData.iconColor }"
|
||||||
|
/>
|
||||||
|
<a-tooltip :title="treeNodeData.title">
|
||||||
|
<span
|
||||||
|
class="ipam-tree-node-title"
|
||||||
|
:style="{
|
||||||
|
color: treeKey === treeNodeData.key ? '#2F54EB' : ''
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ treeNodeData.title }}
|
||||||
|
</span>
|
||||||
|
</a-tooltip>
|
||||||
|
<div class="ipam-tree-node-right">
|
||||||
|
<span
|
||||||
|
v-if="(treeNodeData.key === 'all' && treeNodeData.count) || (treeNodeData.key !== 'all' && treeNodeData.children && treeNodeData.children.length && treeNodeData.count)"
|
||||||
|
class="ipam-tree-node-count"
|
||||||
|
>
|
||||||
|
{{ treeNodeData.count }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a-dropdown :getPopupContainer="(trigger) => trigger">
|
||||||
|
<a class="ipam-tree-node-action">
|
||||||
|
<ops-icon type="veops-more" />
|
||||||
|
</a>
|
||||||
|
<a-menu slot="overlay">
|
||||||
|
<a-menu-item
|
||||||
|
v-if="treeNodeData.showCatalogBtn"
|
||||||
|
@click="openCatalogForm(treeNodeData, 'create')"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-catalog" />
|
||||||
|
{{ $t('cmdb.ipam.addCatalog') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
v-if="treeNodeData.showSubnetBtn"
|
||||||
|
@click="openSubnetForm(treeNodeData, 'create')"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-increase" />
|
||||||
|
{{ $t('cmdb.ipam.addSubnet') }}
|
||||||
|
</a-menu-item>
|
||||||
|
|
||||||
|
<template v-if="treeNodeData.key !== 'all'">
|
||||||
|
<a-menu-item
|
||||||
|
v-if="!treeNodeData.isSubnet"
|
||||||
|
@click="openCatalogForm(treeNodeData, 'edit')"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-edit" />
|
||||||
|
{{ $t('cmdb.ipam.editName') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
v-if="treeNodeData.isSubnet"
|
||||||
|
@click="openSubnetForm(treeNodeData, 'edit')"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-edit" />
|
||||||
|
{{ $t('cmdb.ipam.editNode') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteNode(treeNodeData)">
|
||||||
|
<ops-icon type="veops-delete" />
|
||||||
|
{{ $t('cmdb.ipam.deleteNode') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</template>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-tree>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SubnetForm
|
||||||
|
ref="subnetFormRef"
|
||||||
|
:subnetCIType="subnetCIType"
|
||||||
|
@ok="refreshData"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CatalogForm
|
||||||
|
ref="catalogFormRef"
|
||||||
|
@ok="refreshData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { deleteIPAMSubnet, deleteIPAMScope } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
|
||||||
|
import SubnetForm from './subnetForm.vue'
|
||||||
|
import CatalogForm from './catalogForm.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'IPAMTree',
|
||||||
|
components: {
|
||||||
|
SubnetForm,
|
||||||
|
CatalogForm
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
treeData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
treeKey: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => ''
|
||||||
|
},
|
||||||
|
subnetCIType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchValue: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filterTreeData() {
|
||||||
|
if (this.searchValue) {
|
||||||
|
let treeData = _.cloneDeep(this.treeData)
|
||||||
|
treeData = treeData.filter((data) => {
|
||||||
|
return this.handleTreeDataBySearch(data)
|
||||||
|
})
|
||||||
|
return treeData
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.treeData
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTreeDataBySearch(data) {
|
||||||
|
const isMatch = data?.title?.indexOf?.(this.searchValue) !== -1
|
||||||
|
if (!data?.children?.length) {
|
||||||
|
return isMatch ? data : null
|
||||||
|
}
|
||||||
|
|
||||||
|
data.children = data.children.filter((data) => {
|
||||||
|
return this.handleTreeDataBySearch(data)
|
||||||
|
})
|
||||||
|
return isMatch || data.children.length ? data : null
|
||||||
|
},
|
||||||
|
|
||||||
|
openCatalogForm(node, type) {
|
||||||
|
const nodeId = node?.key && node?.key !== 'all' ? node.key : null
|
||||||
|
const name = type === 'edit' ? (node?.title || '') : ''
|
||||||
|
|
||||||
|
this.$refs.catalogFormRef.open({
|
||||||
|
nodeId,
|
||||||
|
type,
|
||||||
|
name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
openSubnetForm(node, type) {
|
||||||
|
const nodeId = node?.key && node?.key !== 'all' ? node.key : null
|
||||||
|
const parentId = node?.parentId || null
|
||||||
|
|
||||||
|
this.$refs.subnetFormRef.open(nodeId, type, parentId)
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteNode(node) {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('warning'),
|
||||||
|
content: this.$t('confirmDelete'),
|
||||||
|
onOk: async () => {
|
||||||
|
if (node.isSubnet) {
|
||||||
|
await deleteIPAMSubnet(node.key)
|
||||||
|
} else {
|
||||||
|
await deleteIPAMScope(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.key === this.treeKey) {
|
||||||
|
this.$emit('updateTreeKey', 'all')
|
||||||
|
}
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.refreshData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshData() {
|
||||||
|
this.$emit('refreshData')
|
||||||
|
},
|
||||||
|
|
||||||
|
clickTreeNode(node) {
|
||||||
|
this.$emit('updateTreeKey', node.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ipam-tree {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
width: 100%;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/deep/ .ant-tree {
|
||||||
|
.ant-tree-node-content-wrapper {
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
padding: 0px;
|
||||||
|
display: inline-block;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
.ant-tree-title {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > li:first-child {
|
||||||
|
.ant-tree-switcher {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tree-node-content-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tree-switcher-icon {
|
||||||
|
color: #CACDD9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-node {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-count {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #A5A9BC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
display: none;
|
||||||
|
margin-left: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .ant-dropdown-menu {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/deep/ .ant-dropdown-menu-item {
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.ipam-tree-node-action {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,445 @@
|
||||||
|
<template>
|
||||||
|
<CustomDrawer
|
||||||
|
width="800px"
|
||||||
|
:title="$t(actionType === 'edit' ? 'cmdb.ipam.editSubnet' : 'cmdb.ipam.addSubnet')"
|
||||||
|
:visible="visible"
|
||||||
|
:bodyStyle="{ height: 'calc(-108px + 100vh)' }"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<a-form-model
|
||||||
|
ref="subnetFormRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 17 }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(group, groupIndex) in basicFormGroup"
|
||||||
|
:key="groupIndex"
|
||||||
|
>
|
||||||
|
<div class="subnet-form-title">
|
||||||
|
{{ group.name }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-form-model-item
|
||||||
|
v-for="(attr) in group.attributes"
|
||||||
|
:key="attr.name"
|
||||||
|
:label="attr.alias || attr.name"
|
||||||
|
:prop="attr.name"
|
||||||
|
>
|
||||||
|
<CIReferenceAttr
|
||||||
|
v-if="attr.is_reference"
|
||||||
|
:referenceTypeId="attr.reference_type_id"
|
||||||
|
:isList="attr.is_list"
|
||||||
|
:referenceShowAttrName="attr.showAttrName"
|
||||||
|
:initSelectOption="getInitReferenceSelectOption(attr)"
|
||||||
|
v-model="form[attr.name]"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-else-if="attr.is_choice"
|
||||||
|
v-model="form[attr.name]"
|
||||||
|
:mode="attr.is_list ? 'multiple' : 'default'"
|
||||||
|
showSearch
|
||||||
|
allowClear
|
||||||
|
>
|
||||||
|
<a-icon slot="suffixIcon" type="caret-down" />
|
||||||
|
<a-select-option
|
||||||
|
v-for="(choiceItem, choiceIndex) in attr.selectOption"
|
||||||
|
:key="choiceIndex"
|
||||||
|
:value="choiceItem.value"
|
||||||
|
>
|
||||||
|
{{ choiceItem.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-switch
|
||||||
|
v-else-if="attr.is_bool"
|
||||||
|
v-model="form[attr.name]"
|
||||||
|
/>
|
||||||
|
<a-input
|
||||||
|
v-else
|
||||||
|
:placeholder="$t('placeholder1')"
|
||||||
|
v-model="form[attr.name]"
|
||||||
|
/>
|
||||||
|
</a-form-model-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="subnet-form-title">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="4">
|
||||||
|
{{ $t('cmdb.ipam.scanRule') }}
|
||||||
|
</a-col>
|
||||||
|
<a-switch v-model="form.scan_enabled" />
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="form.scan_enabled">
|
||||||
|
<a-form-model-item
|
||||||
|
:label="$t('cmdb.ipam.adExecTarget')"
|
||||||
|
>
|
||||||
|
<CustomRadio
|
||||||
|
v-model="agentType"
|
||||||
|
:radioList="agentTypeRadioList"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-show="agentType === 'master'"
|
||||||
|
slot="extra_master"
|
||||||
|
class="subnet-form-agent-tip"
|
||||||
|
>
|
||||||
|
{{ $t('cmdb.ipam.masterMachineTip') }}
|
||||||
|
</span>
|
||||||
|
<a-input
|
||||||
|
v-show="agentType === 'agent_id'"
|
||||||
|
slot="extra_agent_id"
|
||||||
|
:style="{ width: '300px' }"
|
||||||
|
:placeholder="$t('cmdb.ipam.oneagentIdTips')"
|
||||||
|
v-model="agentId"
|
||||||
|
/>
|
||||||
|
</CustomRadio>
|
||||||
|
</a-form-model-item>
|
||||||
|
|
||||||
|
<a-form-model-item
|
||||||
|
:label="$t('cmdb.ipam.adInterval')"
|
||||||
|
>
|
||||||
|
<el-popover
|
||||||
|
v-model="cronVisible"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template slot>
|
||||||
|
<Vcrontab
|
||||||
|
ref="cronTab"
|
||||||
|
:hideComponent="['second', 'year']"
|
||||||
|
:expression="form.cron"
|
||||||
|
:hasFooter="true"
|
||||||
|
@fill="crontabFill"
|
||||||
|
@hide="hideCron"
|
||||||
|
></Vcrontab>
|
||||||
|
</template>
|
||||||
|
<a-input
|
||||||
|
v-model="form.cron"
|
||||||
|
slot="reference"
|
||||||
|
:placeholder="$t('cmdb.ipam.cronTips')"
|
||||||
|
/>
|
||||||
|
</el-popover>
|
||||||
|
</a-form-model-item>
|
||||||
|
</template>
|
||||||
|
</a-form-model>
|
||||||
|
|
||||||
|
<div class="custom-drawer-bottom-action">
|
||||||
|
<a-button @click="handleClose">{{ $t('cancel') }}</a-button>
|
||||||
|
<a-button @click="handleSubmit" type="primary">{{ $t('save') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</CustomDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
postIPAMSubnet,
|
||||||
|
getIPAMSubnetById,
|
||||||
|
putIPAMSubnet
|
||||||
|
} from '@/modules/cmdb/api/ipam.js'
|
||||||
|
import { getCITypeGroupById, getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
|
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||||
|
|
||||||
|
import Vcrontab from '@/components/Crontab'
|
||||||
|
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SubnetForm',
|
||||||
|
components: {
|
||||||
|
Vcrontab,
|
||||||
|
CIReferenceAttr
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
subnetCIType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nodeId: null,
|
||||||
|
parentId: null,
|
||||||
|
actionType: 'create',
|
||||||
|
visible: false,
|
||||||
|
form: {
|
||||||
|
scan_enabled: true,
|
||||||
|
cron: ''
|
||||||
|
},
|
||||||
|
basicFormGroup: [],
|
||||||
|
formRules: {},
|
||||||
|
|
||||||
|
agentTypeRadioList: [
|
||||||
|
{ value: 'master', label: this.$t('cmdb.ipam.masterMachine') },
|
||||||
|
{ value: 'agent_id', label: this.$t('cmdb.ipam.specifyMachine') }
|
||||||
|
],
|
||||||
|
agentType: 'master',
|
||||||
|
agentId: '',
|
||||||
|
|
||||||
|
cronVisible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async open(nodeId, type, parentId) {
|
||||||
|
this.visible = true
|
||||||
|
this.actionType = type
|
||||||
|
this.nodeId = nodeId
|
||||||
|
this.parentId = parentId || null
|
||||||
|
|
||||||
|
let nodeData = {}
|
||||||
|
if (type === 'edit') {
|
||||||
|
nodeData = await getIPAMSubnetById(nodeId)
|
||||||
|
this.form.scan_enabled = !!nodeData.scan_enabled
|
||||||
|
|
||||||
|
if (nodeData?.scan_enabled) {
|
||||||
|
this.form.cron = nodeData.cron
|
||||||
|
|
||||||
|
if (nodeData.agent_id) {
|
||||||
|
if (nodeData.agent_id === '0x0000') {
|
||||||
|
this.agentType = 'master'
|
||||||
|
} else {
|
||||||
|
this.agentType = 'agent_id'
|
||||||
|
this.agentId = nodeData.agent_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const res = await getCITypeAttributesById(SUB_NET_CITYPE_NAME)
|
||||||
|
// const attributes = res?.attributes || []
|
||||||
|
|
||||||
|
const groupAttr = await getCITypeGroupById(this.subnetCIType.id)
|
||||||
|
const form = {
|
||||||
|
...this.form
|
||||||
|
}
|
||||||
|
const formRules = {}
|
||||||
|
|
||||||
|
let basicFormGroup = []
|
||||||
|
groupAttr.map((group) => {
|
||||||
|
group.attributes = group?.attributes?.filter?.((attr) => !attr.sys_computed && !attr.is_computed) || []
|
||||||
|
if (group.attributes.length) {
|
||||||
|
group.attributes.forEach((attr) => {
|
||||||
|
form[attr.name] = nodeData?.[attr.name] ?? undefined
|
||||||
|
|
||||||
|
if (attr?.is_choice) {
|
||||||
|
let choice_value = attr?.choice_value || []
|
||||||
|
if (attr.name === 'assign_status') {
|
||||||
|
choice_value = choice_value.filter((item) => item?.[0] !== 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.selectOption = choice_value.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item?.[1]?.label || item?.[0] || '',
|
||||||
|
value: item?.[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.is_required) {
|
||||||
|
formRules[attr.name] = [
|
||||||
|
{
|
||||||
|
required: true, message: this.$t('placeholder1')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
basicFormGroup.push({
|
||||||
|
name: group.name,
|
||||||
|
attributes: group.attributes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
basicFormGroup = await this.handleReferenceAttr(basicFormGroup, form)
|
||||||
|
|
||||||
|
this.form = form
|
||||||
|
this.$set(this, 'basicFormGroup', basicFormGroup)
|
||||||
|
this.formRules = formRules
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleReferenceAttr(basicFormGroup, ci) {
|
||||||
|
const map = {}
|
||||||
|
basicFormGroup.forEach((group) => {
|
||||||
|
group.attributes.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id && ci[attr.name]) {
|
||||||
|
const ids = Array.isArray(ci[attr.name]) ? ci[attr.name] : ci[attr.name] ? [ci[attr.name]] : []
|
||||||
|
if (ids.length) {
|
||||||
|
if (!map?.[attr.reference_type_id]) {
|
||||||
|
map[attr.reference_type_id] = {}
|
||||||
|
}
|
||||||
|
ids.forEach((id) => {
|
||||||
|
map[attr.reference_type_id][id] = {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (!Object.keys(map).length) {
|
||||||
|
return basicFormGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciTypesRes = await getCITypes({
|
||||||
|
type_ids: Object.keys(map).join(',')
|
||||||
|
})
|
||||||
|
const showAttrNameMap = {}
|
||||||
|
ciTypesRes.ci_types.forEach((ciType) => {
|
||||||
|
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const allRes = await Promise.all(
|
||||||
|
Object.keys(map).map((key) => {
|
||||||
|
return searchCI({
|
||||||
|
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||||
|
count: 9999
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const ciNameMap = {}
|
||||||
|
allRes.forEach((res) => {
|
||||||
|
res.result.forEach((item) => {
|
||||||
|
ciNameMap[item._id] = item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
basicFormGroup.forEach((group) => {
|
||||||
|
group.attributes.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id) {
|
||||||
|
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
|
||||||
|
|
||||||
|
const referenceShowAttrNameMap = {}
|
||||||
|
const referenceCIIds = ci[attr.name];
|
||||||
|
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
|
||||||
|
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
|
||||||
|
})
|
||||||
|
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return basicFormGroup
|
||||||
|
},
|
||||||
|
|
||||||
|
handleClose() {
|
||||||
|
this.form = {
|
||||||
|
scan_enabled: true,
|
||||||
|
cron: ''
|
||||||
|
}
|
||||||
|
this.basicFormGroup = []
|
||||||
|
this.formRules = {}
|
||||||
|
this.agentType = 'master'
|
||||||
|
this.agentId = ''
|
||||||
|
this.cronVisible = false
|
||||||
|
this.nodeId = null
|
||||||
|
this.parentId = null
|
||||||
|
this.actionType = 'create'
|
||||||
|
|
||||||
|
this.$refs.subnetFormRef.clearValidate()
|
||||||
|
|
||||||
|
this.visible = false
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSubmit() {
|
||||||
|
this.$refs.subnetFormRef.validate(async (valid) => {
|
||||||
|
if (!valid || !this.validateScan()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { cron, ...otherParams } = this.form
|
||||||
|
const params = {
|
||||||
|
...otherParams
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.form.scan_enabled) {
|
||||||
|
params.cron = cron
|
||||||
|
|
||||||
|
switch (this.agentType) {
|
||||||
|
case 'master':
|
||||||
|
params.agent_id = '0x0000'
|
||||||
|
break
|
||||||
|
case 'agent_id':
|
||||||
|
params.agent_id = this.agentId
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.actionType === 'edit') {
|
||||||
|
if (this.parentId) {
|
||||||
|
params.parent_id = this.parentId
|
||||||
|
}
|
||||||
|
await putIPAMSubnet(this.nodeId, params)
|
||||||
|
this.$message.success(this.$t('editSuccess'))
|
||||||
|
} else {
|
||||||
|
params.parent_id = this.nodeId
|
||||||
|
await postIPAMSubnet(params)
|
||||||
|
this.$message.success(this.$t('addSuccess'))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('ok')
|
||||||
|
this.handleClose()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
validateScan() {
|
||||||
|
if (this.form.scan_enabled) {
|
||||||
|
switch (this.agentType) {
|
||||||
|
case 'agent_id':
|
||||||
|
if (!this.agentId) {
|
||||||
|
this.$message.error(this.$t('cmdb.ipam.specifyMachineTips'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.form.cron) {
|
||||||
|
this.$message.error(this.$t('cmdb.ipam.cronRequiredTip'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOpenCmdb() {
|
||||||
|
this.$refs.cmdbDrawer.open()
|
||||||
|
},
|
||||||
|
|
||||||
|
crontabFill(cron) {
|
||||||
|
this.form.cron = cron
|
||||||
|
},
|
||||||
|
|
||||||
|
hideCron() {
|
||||||
|
this.cronVisible = false
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitReferenceSelectOption(attr) {
|
||||||
|
const option = Object.keys(attr?.referenceShowAttrNameMap || {}).map((key) => {
|
||||||
|
return {
|
||||||
|
key: Number(key),
|
||||||
|
title: attr?.referenceShowAttrNameMap?.[Number(key)] ?? ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.subnet-form-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnet-form-agent-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909c;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const SUB_NET_CITYPE_NAME = 'ipam_subnet'
|
||||||
|
export const SCOPE_CITYPE_NAME = 'ipam_scope'
|
||||||
|
export const ADDRESS_CITYPE_NAME = 'ipam_address'
|
|
@ -0,0 +1,296 @@
|
||||||
|
<template>
|
||||||
|
<TwoColumnLayout
|
||||||
|
class="ipam"
|
||||||
|
appName="cmdb-ipam"
|
||||||
|
calcBasedParent
|
||||||
|
>
|
||||||
|
<template #one>
|
||||||
|
<IPAMTree
|
||||||
|
v-if="subnetCIType"
|
||||||
|
:treeData="treeData"
|
||||||
|
:treeKey="treeKey"
|
||||||
|
:subnetCIType="subnetCIType"
|
||||||
|
@refreshData="refreshData"
|
||||||
|
@updateTreeKey="updateTreeKey"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #two>
|
||||||
|
<a-tabs
|
||||||
|
:activeKey="tabKey"
|
||||||
|
@change="handleTabChange"
|
||||||
|
>
|
||||||
|
<a-tab-pane
|
||||||
|
v-for="(item) in tabs"
|
||||||
|
:key="item.key"
|
||||||
|
:tab="$t(item.title)"
|
||||||
|
>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<Overview
|
||||||
|
v-if="tabKey === 'overview'"
|
||||||
|
ref="overviewRef"
|
||||||
|
:nodeId="treeKey"
|
||||||
|
/>
|
||||||
|
<template v-if="addressCIType">
|
||||||
|
<Address
|
||||||
|
v-if="tabKey === 'address'"
|
||||||
|
:nodeData="nodeData"
|
||||||
|
:addressCIType="addressCIType"
|
||||||
|
/>
|
||||||
|
<IPSearch
|
||||||
|
v-if="tabKey === 'ipSearch'"
|
||||||
|
:addressCIType="addressCIType"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-if="subnetCIType">
|
||||||
|
<SubnetList
|
||||||
|
v-if="tabKey === 'subnet'"
|
||||||
|
ref="subnetListRef"
|
||||||
|
:subnetCIType="subnetCIType"
|
||||||
|
@delete="getTreeData"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<HistoryLog
|
||||||
|
v-if="tabKey === 'history'"
|
||||||
|
ref="historyRef"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</TwoColumnLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getIPAMSubnet } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
import { getCIType } from '@/modules/cmdb/api/CIType.js'
|
||||||
|
import { SUB_NET_CITYPE_NAME, ADDRESS_CITYPE_NAME, SCOPE_CITYPE_NAME } from './constants.js'
|
||||||
|
|
||||||
|
import TwoColumnLayout from '@/components/TwoColumnLayout'
|
||||||
|
import IPAMTree from './components/ipamTree.vue'
|
||||||
|
import Overview from './modules/overview/index.vue'
|
||||||
|
import Address from './modules/address/index.vue'
|
||||||
|
import IPSearch from './modules/ipSearch/index.vue'
|
||||||
|
import SubnetList from './modules/subnetList/index.vue'
|
||||||
|
import HistoryLog from './modules/history/index.vue'
|
||||||
|
|
||||||
|
const TAB_STORAGE_KEY = 'ops_ipam_tab_active'
|
||||||
|
const TREE_STORAGE_KEY = 'ops_ipam_tree_active'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'IPAM',
|
||||||
|
components: {
|
||||||
|
TwoColumnLayout,
|
||||||
|
IPAMTree,
|
||||||
|
IPSearch,
|
||||||
|
SubnetList,
|
||||||
|
Address,
|
||||||
|
HistoryLog,
|
||||||
|
Overview
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabKey: localStorage.getItem(TAB_STORAGE_KEY) || 'overview',
|
||||||
|
treeKey: localStorage.getItem(TREE_STORAGE_KEY) || 'all',
|
||||||
|
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
key: 'overview',
|
||||||
|
title: 'cmdb.ipam.overview'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'address',
|
||||||
|
title: 'cmdb.ipam.addressAssign'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'ipSearch',
|
||||||
|
title: 'cmdb.ipam.ipSearch'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'subnet',
|
||||||
|
title: 'cmdb.ipam.subnetList'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'history',
|
||||||
|
title: 'cmdb.ipam.history'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
treeData: [],
|
||||||
|
subnetCIType: null,
|
||||||
|
addressCIType: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
nodeData() {
|
||||||
|
return this.findNodeById(this.treeData, this.treeKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tabKey: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'subnet':
|
||||||
|
if (!this.subnetCITYpe) {
|
||||||
|
this.getSubnetCIType()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'address':
|
||||||
|
case 'ipSearch':
|
||||||
|
if (!this.addressCIType) {
|
||||||
|
this.getAddressCIType()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getSubnetCIType()
|
||||||
|
this.getTreeData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getSubnetCIType() {
|
||||||
|
const res = await getCIType(SUB_NET_CITYPE_NAME)
|
||||||
|
this.subnetCIType = res?.ci_types?.[0] || {}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getAddressCIType() {
|
||||||
|
const res = await getCIType(ADDRESS_CITYPE_NAME)
|
||||||
|
this.addressCIType = res?.ci_types?.[0] || {}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTreeData() {
|
||||||
|
const res = await getIPAMSubnet()
|
||||||
|
let treeData = []
|
||||||
|
|
||||||
|
if (res?.result?.length) {
|
||||||
|
treeData = res.result.map((data) => {
|
||||||
|
return this.handleTreeData(data, res.type2name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const allCount = treeData.reduce((acc, cur) => acc + cur.count, 0)
|
||||||
|
const rootShowSubnetBtn = treeData.every((item) => item.ci_type === SUB_NET_CITYPE_NAME)
|
||||||
|
const rootShowCatalogBtn = treeData.every((item) => item.ci_type === SCOPE_CITYPE_NAME)
|
||||||
|
|
||||||
|
treeData.unshift({
|
||||||
|
key: 'all',
|
||||||
|
title: this.$t('all'),
|
||||||
|
count: allCount,
|
||||||
|
icon: 'veops-entire_network_',
|
||||||
|
iconColor: '#2F54EB',
|
||||||
|
showCatalogBtn: rootShowCatalogBtn,
|
||||||
|
showSubnetBtn: rootShowSubnetBtn,
|
||||||
|
parentId: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
this.treeData = treeData
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTreeData(data, type2name, parentId = '') {
|
||||||
|
const title = data?.[type2name?.[data?._type]] || ''
|
||||||
|
const isSubnet = data?.ci_type === SUB_NET_CITYPE_NAME
|
||||||
|
const icon = isSubnet ? 'veops-subnet' : 'veops-folder'
|
||||||
|
const iconColor = isSubnet ? '#CACDD9' : ''
|
||||||
|
const key = String(data._id)
|
||||||
|
|
||||||
|
if (!data?.children?.length) {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
title,
|
||||||
|
count: isSubnet ? 1 : 0,
|
||||||
|
icon,
|
||||||
|
iconColor,
|
||||||
|
showCatalogBtn: !isSubnet,
|
||||||
|
showSubnetBtn: true,
|
||||||
|
isSubnet,
|
||||||
|
parentId,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = data.children.map((item) => {
|
||||||
|
return this.handleTreeData(item, type2name, key)
|
||||||
|
})
|
||||||
|
|
||||||
|
const showSubnetBtn = children.every((item) => item.ci_type === SUB_NET_CITYPE_NAME)
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
iconColor,
|
||||||
|
showCatalogBtn: !isSubnet && !showSubnetBtn,
|
||||||
|
showSubnetBtn: showSubnetBtn,
|
||||||
|
isSubnet,
|
||||||
|
parentId,
|
||||||
|
...data,
|
||||||
|
children,
|
||||||
|
count: children.reduce((acc, item) => {
|
||||||
|
return acc + item.count
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleTabChange(key) {
|
||||||
|
if (key !== this.tabKey) {
|
||||||
|
this.tabKey = key
|
||||||
|
localStorage.setItem(TAB_STORAGE_KEY, key)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTreeKey(key) {
|
||||||
|
this.treeKey = key
|
||||||
|
localStorage.setItem(TREE_STORAGE_KEY, key)
|
||||||
|
},
|
||||||
|
|
||||||
|
findNodeById(nodes, key) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.key === key) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
const foundNode = this.findNodeById(node.children, key)
|
||||||
|
if (foundNode) {
|
||||||
|
return foundNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshData() {
|
||||||
|
this.getTreeData()
|
||||||
|
switch (this.tabKey) {
|
||||||
|
case 'overview':
|
||||||
|
if (this.$refs.overviewRef) {
|
||||||
|
this.$refs.overviewRef.initData()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'subnet':
|
||||||
|
if (this.$refs.subnetListRef) {
|
||||||
|
this.$refs.subnetListRef.getTableData()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'history':
|
||||||
|
if (this.$refs.historyRef) {
|
||||||
|
this.$refs.historyRef.refreshData()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ipam {
|
||||||
|
/deep/ .ant-tabs {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,284 @@
|
||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
:visible="visible"
|
||||||
|
:width="700"
|
||||||
|
:title="$t('cmdb.ipam.addressAssign')"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<a-form-model
|
||||||
|
ref="assignFormRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="formRules"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 18 }"
|
||||||
|
class="assign-form"
|
||||||
|
>
|
||||||
|
<a-form-model-item
|
||||||
|
label="IP"
|
||||||
|
>
|
||||||
|
{{ ipData.ip }}
|
||||||
|
</a-form-model-item>
|
||||||
|
<a-form-model-item
|
||||||
|
v-for="(item) in formList"
|
||||||
|
:key="item.name"
|
||||||
|
:label="item.alias || item.name"
|
||||||
|
:prop="item.name"
|
||||||
|
>
|
||||||
|
<CIReferenceAttr
|
||||||
|
v-if="item.is_reference"
|
||||||
|
:referenceTypeId="item.reference_type_id"
|
||||||
|
:isList="item.is_list"
|
||||||
|
:referenceShowAttrName="item.showAttrName"
|
||||||
|
:initSelectOption="getInitReferenceSelectOption(item)"
|
||||||
|
v-model="form[item.name]"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-else-if="item.is_choice"
|
||||||
|
:mode="item.is_list ? 'multiple' : 'default'"
|
||||||
|
showSearch
|
||||||
|
allowClear
|
||||||
|
v-model="form[item.name]"
|
||||||
|
>
|
||||||
|
<a-icon slot="suffixIcon" type="caret-down" />
|
||||||
|
<a-select-option
|
||||||
|
v-for="(choiceItem, choiceIndex) in item.selectOption"
|
||||||
|
:key="choiceIndex"
|
||||||
|
:value="choiceItem.value"
|
||||||
|
>
|
||||||
|
{{ choiceItem.label }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-switch
|
||||||
|
v-else-if="item.is_bool"
|
||||||
|
v-model="form[item.name]"
|
||||||
|
/>
|
||||||
|
<a-input
|
||||||
|
v-else
|
||||||
|
:placeholder="$t('placeholder1')"
|
||||||
|
v-model="form[item.name]"
|
||||||
|
/>
|
||||||
|
</a-form-model-item>
|
||||||
|
</a-form-model>
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { postIPAMAddress } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
|
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||||
|
|
||||||
|
import CIReferenceAttr from '@/components/ciReferenceAttr/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AssignForm',
|
||||||
|
components: {
|
||||||
|
CIReferenceAttr
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
attrList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
subnetData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
ipData: {},
|
||||||
|
nodeId: -1,
|
||||||
|
formList: [],
|
||||||
|
form: {},
|
||||||
|
formRules: {},
|
||||||
|
statusSelectOption: [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
label: 'cmdb.ipam.assigned'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: 'cmdb.ipam.reserved'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async open({
|
||||||
|
ipData,
|
||||||
|
nodeId,
|
||||||
|
}) {
|
||||||
|
this.ipData = ipData || {}
|
||||||
|
this.nodeId = nodeId || -1
|
||||||
|
this.visible = true
|
||||||
|
|
||||||
|
const form = {}
|
||||||
|
const formRules = {}
|
||||||
|
let formList = []
|
||||||
|
let attrList = _.cloneDeep(this.attrList)
|
||||||
|
attrList = attrList?.filter?.((attr) => !attr.sys_computed && !attr.is_computed) || []
|
||||||
|
|
||||||
|
if (ipData?.assign_status === 1) {
|
||||||
|
ipData.assign_status = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrList.length) {
|
||||||
|
_.remove(attrList, (item) => {
|
||||||
|
return ['subnet_mask', 'gateway', 'name', 'mac_address', 'is_used', 'ip'].includes(item.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const assingStatusIndex = attrList.findIndex((attr) => attr.name === 'assign_status')
|
||||||
|
if (assingStatusIndex > 0) {
|
||||||
|
const assign_status = attrList.splice(assingStatusIndex, 1)
|
||||||
|
attrList.unshift(...assign_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrList.forEach((attr) => {
|
||||||
|
form[attr.name] = ipData?.[attr.name] ?? undefined
|
||||||
|
|
||||||
|
if (attr?.is_choice) {
|
||||||
|
let choice_value = attr?.choice_value || []
|
||||||
|
if (attr.name === 'assign_status') {
|
||||||
|
choice_value = choice_value.filter((item) => item?.[0] !== 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.selectOption = choice_value.map((item) => {
|
||||||
|
return {
|
||||||
|
label: item?.[1]?.label || item?.[0] || '',
|
||||||
|
value: item?.[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
formList.push(attr)
|
||||||
|
|
||||||
|
if (attr.is_required) {
|
||||||
|
formRules[attr.name] = [
|
||||||
|
{
|
||||||
|
required: true, message: attr?.is_choice ? this.$t('placeholder2') : this.$t('placeholder1')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
formList = await this.handleReferenceAttr(formList, form)
|
||||||
|
|
||||||
|
this.form = form
|
||||||
|
this.formList = formList
|
||||||
|
this.formRules = formRules
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleReferenceAttr(formList, ci) {
|
||||||
|
const map = {}
|
||||||
|
formList.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id && ci[attr.name]) {
|
||||||
|
const ids = Array.isArray(ci[attr.name]) ? ci[attr.name] : ci[attr.name] ? [ci[attr.name]] : []
|
||||||
|
if (ids.length) {
|
||||||
|
if (!map?.[attr.reference_type_id]) {
|
||||||
|
map[attr.reference_type_id] = {}
|
||||||
|
}
|
||||||
|
ids.forEach((id) => {
|
||||||
|
map[attr.reference_type_id][id] = {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!Object.keys(map).length) {
|
||||||
|
return formList
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciTypesRes = await getCITypes({
|
||||||
|
type_ids: Object.keys(map).join(',')
|
||||||
|
})
|
||||||
|
const showAttrNameMap = {}
|
||||||
|
ciTypesRes.ci_types.forEach((ciType) => {
|
||||||
|
showAttrNameMap[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const allRes = await Promise.all(
|
||||||
|
Object.keys(map).map((key) => {
|
||||||
|
return searchCI({
|
||||||
|
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||||
|
count: 9999
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const ciNameMap = {}
|
||||||
|
allRes.forEach((res) => {
|
||||||
|
res.result.forEach((item) => {
|
||||||
|
ciNameMap[item._id] = item
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.forEach((attr) => {
|
||||||
|
if (attr?.is_reference && attr?.reference_type_id) {
|
||||||
|
attr.showAttrName = showAttrNameMap?.[attr?.reference_type_id] || ''
|
||||||
|
|
||||||
|
const referenceShowAttrNameMap = {}
|
||||||
|
const referenceCIIds = ci[attr.name];
|
||||||
|
(Array.isArray(referenceCIIds) ? referenceCIIds : referenceCIIds ? [referenceCIIds] : []).forEach((id) => {
|
||||||
|
referenceShowAttrNameMap[id] = ciNameMap?.[id]?.[attr.showAttrName] ?? id
|
||||||
|
})
|
||||||
|
attr.referenceShowAttrNameMap = referenceShowAttrNameMap
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return formList
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
this.visible = false
|
||||||
|
this.ipData = {}
|
||||||
|
this.nodeId = -1
|
||||||
|
this.form = {}
|
||||||
|
this.formRules = {}
|
||||||
|
this.formList = []
|
||||||
|
this.visible = false
|
||||||
|
|
||||||
|
this.$refs.assignFormRef.clearValidate()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleOk() {
|
||||||
|
this.$refs.assignFormRef.validate(async (valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await postIPAMAddress({
|
||||||
|
ips: [this.ipData.ip],
|
||||||
|
parent_id: this.nodeId,
|
||||||
|
...this.form,
|
||||||
|
subnet_mask: this?.ipData?.subnet_mask ?? undefined,
|
||||||
|
gateway: this?.ipData?.gateway ?? undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$emit('ok')
|
||||||
|
this.handleCancel()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitReferenceSelectOption(attr) {
|
||||||
|
const option = Object.keys(attr?.referenceShowAttrNameMap || {}).map((key) => {
|
||||||
|
return {
|
||||||
|
key: Number(key),
|
||||||
|
title: attr?.referenceShowAttrNameMap?.[Number(key)] ?? ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.assign-form {
|
||||||
|
padding-right: 12px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,39 @@
|
||||||
|
export const ADDRESS_STATUS = {
|
||||||
|
ONLINE_ASSIGNED: '0',
|
||||||
|
ONLINE_UNASSIGNED: '1',
|
||||||
|
OFFLINE_ASSIGNED: '2',
|
||||||
|
OFFLINE_UNASSIGNED: '3',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STATUS_COLOR = {
|
||||||
|
[ADDRESS_STATUS.ONLINE_ASSIGNED]: '#00B42A',
|
||||||
|
[ADDRESS_STATUS.ONLINE_UNASSIGNED]: '#FF7D00',
|
||||||
|
[ADDRESS_STATUS.OFFLINE_ASSIGNED]: '#2F54EB',
|
||||||
|
[ADDRESS_STATUS.OFFLINE_UNASSIGNED]: '#A5A9BC'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STATUS_LABEL = {
|
||||||
|
[ADDRESS_STATUS.ONLINE_ASSIGNED]: 'cmdb.ipam.assignedOnline',
|
||||||
|
[ADDRESS_STATUS.ONLINE_UNASSIGNED]: 'cmdb.ipam.unassignedOnline',
|
||||||
|
[ADDRESS_STATUS.OFFLINE_ASSIGNED]: 'cmdb.ipam.assignedOffline',
|
||||||
|
[ADDRESS_STATUS.OFFLINE_UNASSIGNED]: 'cmdb.ipam.unused'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STATUS_OPTION = [
|
||||||
|
{
|
||||||
|
value: ADDRESS_STATUS.ONLINE_ASSIGNED,
|
||||||
|
label: STATUS_LABEL[ADDRESS_STATUS.ONLINE_ASSIGNED],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ADDRESS_STATUS.ONLINE_UNASSIGNED,
|
||||||
|
label: STATUS_LABEL[ADDRESS_STATUS.ONLINE_UNASSIGNED],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ADDRESS_STATUS.OFFLINE_ASSIGNED,
|
||||||
|
label: STATUS_LABEL[ADDRESS_STATUS.OFFLINE_ASSIGNED],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ADDRESS_STATUS.OFFLINE_UNASSIGNED,
|
||||||
|
label: STATUS_LABEL[ADDRESS_STATUS.OFFLINE_UNASSIGNED],
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,399 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ip-grid"
|
||||||
|
:style="{
|
||||||
|
gap: gridGap + 'px'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item) in gridList"
|
||||||
|
:key="item.ip"
|
||||||
|
class="ip-grid-item"
|
||||||
|
:style="{
|
||||||
|
width: gridItemSize + 'px',
|
||||||
|
height: gridItemSize + 'px',
|
||||||
|
backgroundColor: `${STATUS_COLOR[item._ip_status]}22`,
|
||||||
|
color: STATUS_COLOR[item._ip_status],
|
||||||
|
borderColor: STATUS_COLOR[item._ip_status]
|
||||||
|
}"
|
||||||
|
@click="clickGridItem(item, $event)"
|
||||||
|
>
|
||||||
|
{{ item.gridTitle }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-show="infoCardVisible"
|
||||||
|
class="info-card"
|
||||||
|
:style="{
|
||||||
|
top: infoCardY + 'px',
|
||||||
|
left: infoCardX + 'px',
|
||||||
|
width: infoCardWidth + 'px',
|
||||||
|
height: infoCardHeight + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="info-card-header">
|
||||||
|
<div class="info-card-ip">
|
||||||
|
{{ infoCardData.ip }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="info-card-status-dot"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: `${STATUS_COLOR[infoCardData._ip_status]}22`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="info-card-status-dot-content"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: STATUS_COLOR[infoCardData._ip_status]
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-card-status-text">
|
||||||
|
{{ $t(STATUS_LABEL[infoCardData._ip_status]) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost info-card-recycle"
|
||||||
|
ghost
|
||||||
|
@click="clickRecycle(infoCardData)"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-recycle" />
|
||||||
|
{{ $t('cmdb.ipam.recycle') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="info-card-main">
|
||||||
|
<div
|
||||||
|
v-for="(col) in filterColumns"
|
||||||
|
:key="col.field"
|
||||||
|
class="info-card-main-row"
|
||||||
|
>
|
||||||
|
<div class="info-card-main-title">
|
||||||
|
<a-tooltip :title="col.title">
|
||||||
|
{{ col.title }}
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="info-card-main-value">
|
||||||
|
<a-tooltip :title="infoCardTip[col.field]" placement="topLeft" >
|
||||||
|
<template v-if="col.is_reference && infoCardData[col.field]" >
|
||||||
|
<a
|
||||||
|
v-for="(ciId) in (col.is_list ? infoCardData[col.field] : [infoCardData[col.field]])"
|
||||||
|
:key="ciId"
|
||||||
|
:href="`/cmdb/cidetail/${col.reference_type_id}/${ciId}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getReferenceAttrValue(ciId, col) }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="col.is_link && infoCardData[col.field]">
|
||||||
|
<a
|
||||||
|
v-for="(linkItem, linkIndex) in (col.is_list ? infoCardData[col.field] : [infoCardData[col.field]])"
|
||||||
|
:key="linkIndex"
|
||||||
|
:href="
|
||||||
|
linkItem.startsWith('http') || linkItem.startsWith('https')
|
||||||
|
? `${linkItem}`
|
||||||
|
: `http://${linkItem}`
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getChoiceValueLabel(col, linkItem) || linkItem }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="col.is_choice && infoCardData[col.field]">
|
||||||
|
<span
|
||||||
|
v-for="value in (col.is_list ? infoCardData[col.field] : [infoCardData[col.field]])"
|
||||||
|
:key="value"
|
||||||
|
class="column-default-choice"
|
||||||
|
>
|
||||||
|
{{ getChoiceValueLabel(col, value) || value }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ infoCardData[col.field] !== undefined ? Array.isArray(infoCardData[col.field]) ? infoCardData[col.field].join(', ') : infoCardData[col.field] : '' }}
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { STATUS_COLOR, STATUS_LABEL, ADDRESS_STATUS } from './constants.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'GridIP',
|
||||||
|
props: {
|
||||||
|
ipList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
referenceShowAttrNameMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
referenceCIIdMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
STATUS_COLOR,
|
||||||
|
STATUS_LABEL,
|
||||||
|
|
||||||
|
gridItemSize: 52, //
|
||||||
|
gridGap: 8,
|
||||||
|
|
||||||
|
infoCardX: 0,
|
||||||
|
infoCardY: 0,
|
||||||
|
infoCardWidth: 375,
|
||||||
|
infoCardVisible: false,
|
||||||
|
infoCardData: {},
|
||||||
|
infoCardTip: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
gridList() {
|
||||||
|
const list = this.ipList.map((item) => {
|
||||||
|
const ipSplit = item?.ip?.split('.') || []
|
||||||
|
const gridTitle = ipSplit?.[ipSplit.length - 1] || ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
gridTitle
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
filterColumns() {
|
||||||
|
return this.columns.filter((col) => col.field !== '_ip_status') || []
|
||||||
|
},
|
||||||
|
infoCardHeight() {
|
||||||
|
let infoCardHeight = 311
|
||||||
|
if (this.filterColumns.length < 6) {
|
||||||
|
infoCardHeight -= ((6 - this.filterColumns.length) * 36)
|
||||||
|
}
|
||||||
|
|
||||||
|
return infoCardHeight
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener('click', this.handleClick)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('click', this.handleClick)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(event) {
|
||||||
|
const classStr = event?.target?.classList?.value
|
||||||
|
if (classStr.indexOf('info-card') === -1 && classStr.indexOf('ip-grid-item') === -1) {
|
||||||
|
this.infoCardVisible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clickGridItem(item, event) {
|
||||||
|
if ([ADDRESS_STATUS.OFFLINE_UNASSIGNED, ADDRESS_STATUS.ONLINE_UNASSIGNED].includes(item?._ip_status)) {
|
||||||
|
this.$emit('openAssign', item)
|
||||||
|
} else {
|
||||||
|
this.showInfoCard(item, event)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showInfoCard(item, event) {
|
||||||
|
let infoCardX = event.clientX - event.offsetX
|
||||||
|
let infoCardY = event.clientY - event.offsetY + this.gridItemSize + this.gridGap
|
||||||
|
|
||||||
|
// 右侧是否超出视口边界
|
||||||
|
if (infoCardX + this.infoCardWidth > window.innerWidth) {
|
||||||
|
infoCardX = infoCardX + this.gridItemSize - this.infoCardWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部是否超出视口边界
|
||||||
|
if (infoCardY + this.infoCardHeight > window.innerHeight) {
|
||||||
|
infoCardY = infoCardY - this.gridItemSize - this.gridGap * 2 - this.infoCardHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoCardTip = {}
|
||||||
|
this.filterColumns.forEach((col) => {
|
||||||
|
const arrayValue = Array.isArray(item[col.field]) ? item[col.field] : [item[col.field]]
|
||||||
|
infoCardTip[col.field] = arrayValue.map((value) => {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col.is_reference) {
|
||||||
|
return this.getReferenceAttrValue(value, col) || value
|
||||||
|
}
|
||||||
|
if (col.is_link || col.is_choice) {
|
||||||
|
return this.getChoiceValueLabel(col, value) || value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}).join(', ')
|
||||||
|
})
|
||||||
|
|
||||||
|
this.infoCardX = infoCardX
|
||||||
|
this.infoCardY = infoCardY
|
||||||
|
this.infoCardVisible = true
|
||||||
|
this.infoCardData = item
|
||||||
|
this.infoCardTip = infoCardTip
|
||||||
|
},
|
||||||
|
|
||||||
|
clickRecycle(data) {
|
||||||
|
this.$emit('recycle', data.ip)
|
||||||
|
},
|
||||||
|
|
||||||
|
getReferenceAttrValue(id, col) {
|
||||||
|
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
|
||||||
|
if (!ci) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrName = this.referenceShowAttrNameMap?.[col.reference_type_id]
|
||||||
|
return ci?.[attrName] || id
|
||||||
|
},
|
||||||
|
|
||||||
|
getChoiceValueLabel(col, colValue) {
|
||||||
|
const _find = col?.choice_value?.find((item) => String(item[0]) === String(colValue))
|
||||||
|
if (_find) {
|
||||||
|
return _find?.[1]?.label || ''
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ip-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
max-height: calc(100vh - 230px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-card {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transition: top 0.2s, left 0.2s;
|
||||||
|
padding: 23px 18px;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
box-shadow: -2px 4px 12px 0px rgba(168, 191, 211, 0.25);
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-ip {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 14px;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-recycle {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
margin-top: 15px;
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px #F0F1F5;
|
||||||
|
border-bottom-style: none;
|
||||||
|
max-height: calc(100% - 47px);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&-row {
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
border-right: solid 1px #F0F1F5;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
padding-left: 17px;
|
||||||
|
padding-right: 10px;
|
||||||
|
width: 32%;
|
||||||
|
height: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
border-bottom: solid 1px #E4E7ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
width: 68%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
padding-right: 10px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
border-bottom: solid 1px #F0F1F5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,721 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="addressRef"
|
||||||
|
class="address"
|
||||||
|
>
|
||||||
|
<div v-if="addressNullTip" class="address-null">
|
||||||
|
<img class="address-null-img" :src="require(`@/modules/cmdb/assets/ipam_address_null.png`)"></img>
|
||||||
|
<div class="address-null-tip">{{ $t('noData') }}</div>
|
||||||
|
<div class="address-null-tip2">{{ $t(addressNullTip) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="loading" class="address-loading">
|
||||||
|
<a-icon type="loading" class="address-loading-icon" />
|
||||||
|
<span class="address-loading-text">{{ $t('loading') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="address-header">
|
||||||
|
<div class="address-header-left">
|
||||||
|
<a-input-search
|
||||||
|
v-model="searchValue"
|
||||||
|
:placeholder="$t('placeholderSearch')"
|
||||||
|
class="address-header-search"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a-select
|
||||||
|
class="address-header-filter"
|
||||||
|
v-model="currentStatus"
|
||||||
|
>
|
||||||
|
<a-icon slot="suffixIcon" type="caret-down" />
|
||||||
|
<a-select-option
|
||||||
|
v-for="(item) in filterOption"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ $t(item.label) }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
|
||||||
|
<a-select
|
||||||
|
v-if="scopeSelectOption.length > 1"
|
||||||
|
class="address-header-filter"
|
||||||
|
v-model="currentSelectScope"
|
||||||
|
showSearch
|
||||||
|
>
|
||||||
|
<a-icon slot="suffixIcon" type="caret-down" />
|
||||||
|
<a-select-option
|
||||||
|
v-for="(key) in scopeSelectOption"
|
||||||
|
:key="key"
|
||||||
|
:value="key"
|
||||||
|
>
|
||||||
|
{{ key }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="currentLayout === 'grid'"
|
||||||
|
class="address-header-status"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(item) in statusOption"
|
||||||
|
:key="item.value"
|
||||||
|
class="address-header-status-item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="address-header-status-dot"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: `${STATUS_COLOR[item.value]}22`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="address-header-status-dot-content"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: STATUS_COLOR[item.value]
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="address-header-status-text"
|
||||||
|
>
|
||||||
|
{{ $t(item.label) }}: {{ item.count }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="address-header-right">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost"
|
||||||
|
ghost
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-export" />
|
||||||
|
{{ $t('export') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<div class="address-header-layout">
|
||||||
|
<div
|
||||||
|
v-for="(item) in layoutList"
|
||||||
|
:key="item.value"
|
||||||
|
:class="['address-header-layout-item', currentLayout === item.value ?'address-header-layout-item-active' : '']"
|
||||||
|
@click="currentLayout = item.value"
|
||||||
|
>
|
||||||
|
<ops-icon :type="item.icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="address-main">
|
||||||
|
<TableIP
|
||||||
|
v-if="currentLayout === 'table'"
|
||||||
|
ref="tableIPRef"
|
||||||
|
:columns="columns"
|
||||||
|
:allTableData="filterIPList"
|
||||||
|
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||||
|
:referenceCIIdMap="referenceCIIdMap"
|
||||||
|
:columnWidth="columnWidth"
|
||||||
|
@openAssign="openAssign"
|
||||||
|
@recycle="handleRecycle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GridIP
|
||||||
|
v-if="currentLayout === 'grid'"
|
||||||
|
:ipList="filterIPList"
|
||||||
|
:columns="columns"
|
||||||
|
:referenceShowAttrNameMap="referenceShowAttrNameMap"
|
||||||
|
:referenceCIIdMap="referenceCIIdMap"
|
||||||
|
@openAssign="openAssign"
|
||||||
|
@recycle="handleRecycle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<AssignForm
|
||||||
|
ref="assignFormRef"
|
||||||
|
:attrList="attrList"
|
||||||
|
:subnetData="subnetData"
|
||||||
|
@ok="getIPList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import ExcelJS from 'exceljs'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import { getIPAMAddress, getIPAMHosts, postIPAMAddress, getIPAMSubnetById } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { ADDRESS_STATUS, STATUS_COLOR, STATUS_OPTION, STATUS_LABEL } from './constants.js'
|
||||||
|
import { getCITypes } from '@/modules/cmdb/api/CIType'
|
||||||
|
import { searchCI } from '@/modules/cmdb/api/ci'
|
||||||
|
import { strLength } from '@/modules/cmdb/utils/helper.js'
|
||||||
|
|
||||||
|
import TableIP from './tableIP.vue'
|
||||||
|
import GridIP from './gridIP.vue'
|
||||||
|
import AssignForm from './assignForm.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Address',
|
||||||
|
components: {
|
||||||
|
TableIP,
|
||||||
|
GridIP,
|
||||||
|
AssignForm
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeData: {
|
||||||
|
type: [Object, null],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
addressCIType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
STATUS_COLOR,
|
||||||
|
searchValue: '',
|
||||||
|
ipList: {},
|
||||||
|
currentSelectScope: '',
|
||||||
|
columns: [],
|
||||||
|
attrList: [],
|
||||||
|
subnetData: {},
|
||||||
|
referenceShowAttrNameMap: {},
|
||||||
|
referenceCIIdMap: {},
|
||||||
|
columnWidth: {},
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
currentStatus: 'all',
|
||||||
|
filterOption: [
|
||||||
|
{
|
||||||
|
value: 'all',
|
||||||
|
label: 'cmdb.ipam.allStatus',
|
||||||
|
},
|
||||||
|
...STATUS_OPTION,
|
||||||
|
],
|
||||||
|
|
||||||
|
currentLayout: 'table',
|
||||||
|
layoutList: [
|
||||||
|
{
|
||||||
|
value: 'table',
|
||||||
|
icon: 'monitor-list_view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'grid',
|
||||||
|
icon: 'veops-map_view'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
addressNullTip() {
|
||||||
|
if (
|
||||||
|
this?.nodeData?.isSubnet &&
|
||||||
|
this?.nodeData?.cidr &&
|
||||||
|
this?.nodeData?.children?.length === 0
|
||||||
|
) {
|
||||||
|
const cidrSplit = this.nodeData?.cidr?.split?.('/')
|
||||||
|
const cidrNumber = cidrSplit[cidrSplit.length - 1]
|
||||||
|
if (Number(cidrNumber) >= 16) {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return 'cmdb.ipam.addressNullTip2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'cmdb.ipam.addressNullTip'
|
||||||
|
},
|
||||||
|
addressCITypeId() {
|
||||||
|
return this.addressCIType?.id || null
|
||||||
|
},
|
||||||
|
filterIPList() {
|
||||||
|
let ipList = this.ipList?.[this.currentSelectScope]
|
||||||
|
|
||||||
|
if (!ipList?.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.searchValue) {
|
||||||
|
ipList = ipList.filter((item) => item.ip.indexOf(this.searchValue) !== -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentStatus !== 'all') {
|
||||||
|
ipList = ipList.filter((item) => item._ip_status === this.currentStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipList
|
||||||
|
},
|
||||||
|
scopeSelectOption() {
|
||||||
|
if (typeof this.ipList === 'object') {
|
||||||
|
return Object.keys(this.ipList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
statusOption() {
|
||||||
|
const ipList = this.ipList?.[this.currentSelectScope] || []
|
||||||
|
const statusCount = {
|
||||||
|
[ADDRESS_STATUS.OFFLINE_ASSIGNED]: 0,
|
||||||
|
[ADDRESS_STATUS.OFFLINE_UNASSIGNED]: 0,
|
||||||
|
[ADDRESS_STATUS.ONLINE_ASSIGNED]: 0,
|
||||||
|
[ADDRESS_STATUS.ONLINE_UNASSIGNED]: 0,
|
||||||
|
}
|
||||||
|
ipList.forEach((item) => {
|
||||||
|
if (item._ip_status) {
|
||||||
|
statusCount[item._ip_status]++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return STATUS_OPTION.map((option) => ({
|
||||||
|
...option,
|
||||||
|
count: statusCount[option.value]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
nodeData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(node, oldNode) {
|
||||||
|
if (
|
||||||
|
node &&
|
||||||
|
node?.isSubnet &&
|
||||||
|
node?.cidr &&
|
||||||
|
node?.children?.length === 0 &&
|
||||||
|
node?.key !== oldNode?.key
|
||||||
|
) {
|
||||||
|
const cidrSplit = node?.cidr?.split?.('/')
|
||||||
|
const cidrNumber = cidrSplit[cidrSplit.length - 1]
|
||||||
|
|
||||||
|
if (Number(cidrNumber) >= 16) {
|
||||||
|
this.initData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initData() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
await this.getColumns()
|
||||||
|
await this.handleReferenceShowAttrName()
|
||||||
|
await this.getIPList(true)
|
||||||
|
this.calcColumnWidth()
|
||||||
|
} catch (error) {
|
||||||
|
console.log('initData fail', error)
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
async getColumns() {
|
||||||
|
const getAttrRes = await getCITypeAttributesById(this.addressCITypeId)
|
||||||
|
const attrList = getAttrRes.attributes
|
||||||
|
this.attrList = _.cloneDeep(attrList)
|
||||||
|
|
||||||
|
const filterAttrList = _.remove(attrList, (item) => {
|
||||||
|
return ['ip', 'subnet_mask', 'assign_status', 'is_used', '_updated_by', '_updated_at'].includes(item.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = []
|
||||||
|
;['ip', 'subnet_mask'].forEach((key) => {
|
||||||
|
const attr = filterAttrList.find((item) => item.name === key)
|
||||||
|
if (attr) {
|
||||||
|
columns.push({
|
||||||
|
field: attr.name,
|
||||||
|
title: attr.alias || attr.name || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
field: '_ip_status',
|
||||||
|
title: this.$t('status')
|
||||||
|
})
|
||||||
|
|
||||||
|
attrList.map((attr) => {
|
||||||
|
columns.push({
|
||||||
|
field: attr.name,
|
||||||
|
title: attr.alias || attr.name || '',
|
||||||
|
...attr
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.columns = columns
|
||||||
|
},
|
||||||
|
|
||||||
|
async getIPList(isInit = false) {
|
||||||
|
const hostsList = await getIPAMHosts({
|
||||||
|
cidr: this.nodeData.cidr
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await getIPAMAddress({
|
||||||
|
parent_id: this.nodeData.key
|
||||||
|
})
|
||||||
|
|
||||||
|
const subnetData = await getIPAMSubnetById(this.nodeData.key)
|
||||||
|
this.subnetData = subnetData
|
||||||
|
|
||||||
|
const addressMap = {}
|
||||||
|
if (res?.result?.length) {
|
||||||
|
res.result.forEach((item) => {
|
||||||
|
addressMap[item.ip] = item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipList = {}
|
||||||
|
let currentSelectScope = ''
|
||||||
|
|
||||||
|
hostsList.map((ip) => {
|
||||||
|
let colData = {
|
||||||
|
ip,
|
||||||
|
_ip_status: ADDRESS_STATUS.OFFLINE_UNASSIGNED
|
||||||
|
}
|
||||||
|
if (addressMap[ip]) {
|
||||||
|
const data = addressMap[ip]
|
||||||
|
const assigned = data.assign_status === 0 || data.assign_status === 2
|
||||||
|
|
||||||
|
if (data.is_used) {
|
||||||
|
colData._ip_status = assigned ? ADDRESS_STATUS.ONLINE_ASSIGNED : ADDRESS_STATUS.ONLINE_UNASSIGNED
|
||||||
|
} else if (assigned) {
|
||||||
|
colData._ip_status = ADDRESS_STATUS.OFFLINE_ASSIGNED
|
||||||
|
}
|
||||||
|
|
||||||
|
colData = {
|
||||||
|
...colData,
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemData = {
|
||||||
|
...colData,
|
||||||
|
subnet_mask: colData?.subnet_mask ?? subnetData?.subnet_mask ?? undefined,
|
||||||
|
gateway: colData?.gateway ?? subnetData?.gateway ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = ip.split(/\.(?=[^.]*$)/)?.[0]
|
||||||
|
if (ipList[key]) {
|
||||||
|
ipList[key].push(itemData)
|
||||||
|
} else {
|
||||||
|
if (!currentSelectScope) {
|
||||||
|
currentSelectScope = key
|
||||||
|
}
|
||||||
|
ipList[key] = [itemData]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.ipList = ipList
|
||||||
|
if (isInit) {
|
||||||
|
this.currentSelectScope = currentSelectScope
|
||||||
|
}
|
||||||
|
this.handleReferenceCIIdMap()
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleReferenceShowAttrName() {
|
||||||
|
const needRequiredCITypeIds = this.columns?.filter((col) => col?.is_reference && col?.reference_type_id).map((col) => col.reference_type_id) || []
|
||||||
|
if (!needRequiredCITypeIds.length) {
|
||||||
|
this.referenceShowAttrNameMap = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await getCITypes({
|
||||||
|
type_ids: needRequiredCITypeIds.join(',')
|
||||||
|
})
|
||||||
|
|
||||||
|
const map = {}
|
||||||
|
res.ci_types.forEach((ciType) => {
|
||||||
|
map[ciType.id] = ciType?.show_name || ciType?.unique_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
this.referenceShowAttrNameMap = map
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleReferenceCIIdMap() {
|
||||||
|
const referenceTypeCol = this.columns.filter((col) => col?.is_reference && col?.reference_type_id) || []
|
||||||
|
if (!this.ipList?.[this.currentSelectScope]?.length || !referenceTypeCol?.length) {
|
||||||
|
this.referenceCIIdMap = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = {}
|
||||||
|
this.ipList[this.currentSelectScope].forEach((row) => {
|
||||||
|
referenceTypeCol.forEach((col) => {
|
||||||
|
const ids = Array.isArray(row[col.field]) ? row[col.field] : row[col.field] ? [row[col.field]] : []
|
||||||
|
if (ids.length) {
|
||||||
|
if (!map?.[col.reference_type_id]) {
|
||||||
|
map[col.reference_type_id] = {}
|
||||||
|
}
|
||||||
|
ids.forEach((id) => {
|
||||||
|
map[col.reference_type_id][id] = {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!Object.keys(map).length) {
|
||||||
|
this.referenceCIIdMap = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allRes = await Promise.all(
|
||||||
|
Object.keys(map).map((key) => {
|
||||||
|
return searchCI({
|
||||||
|
q: `_type:${key},_id:(${Object.keys(map[key]).join(';')})`,
|
||||||
|
count: 9999
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
allRes.forEach((res) => {
|
||||||
|
res.result.forEach((item) => {
|
||||||
|
if (map?.[item._type]?.[item._id]) {
|
||||||
|
map[item._type][item._id] = item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.referenceCIIdMap = map
|
||||||
|
},
|
||||||
|
|
||||||
|
calcColumnWidth() {
|
||||||
|
const columnWidth = {}
|
||||||
|
this.columns.forEach((col) => {
|
||||||
|
columnWidth[col.field] = Math.min(Math.max(100, ...this.ipList[this.currentSelectScope].map(item => strLength(item[col.field]))), 350)
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapWidth = this.$refs.addressRef?.clientWidth
|
||||||
|
const totalWidth = Object.values(columnWidth).reduce((acc, cur) => acc + cur, 0)
|
||||||
|
|
||||||
|
if (totalWidth < wrapWidth) {
|
||||||
|
this.columnWidth = {}
|
||||||
|
} else {
|
||||||
|
this.columnWidth = columnWidth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExport() {
|
||||||
|
let tableData = []
|
||||||
|
if (this.currentLayout === 'table') {
|
||||||
|
tableData = this.$refs.tableIPRef.getCheckedTableData()
|
||||||
|
} else {
|
||||||
|
tableData = this.filterIPList
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tableData.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const wb = new ExcelJS.Workbook()
|
||||||
|
const ws = wb.addWorksheet(this.tabActive)
|
||||||
|
|
||||||
|
const columns = this.columns.map((col) => {
|
||||||
|
return {
|
||||||
|
header: col.title,
|
||||||
|
key: col.field,
|
||||||
|
width: 20
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ws.columns = columns
|
||||||
|
|
||||||
|
tableData.forEach((data) => {
|
||||||
|
const row = {}
|
||||||
|
columns.forEach(({ key }) => {
|
||||||
|
let value = data?.[key] ?? null
|
||||||
|
if (key === '_ip_status') {
|
||||||
|
const text = STATUS_LABEL?.[data?.[key]]
|
||||||
|
value = text ? this.$t(text) : null
|
||||||
|
}
|
||||||
|
row[key] = value
|
||||||
|
})
|
||||||
|
ws.addRow(row)
|
||||||
|
})
|
||||||
|
|
||||||
|
wb.xlsx.writeBuffer().then((buffer) => {
|
||||||
|
const fileName = `cmdb-${this.$t('cmdb.ipam.addressAssign')}-${moment().format('YYYYMMDDHHmmss')}.xlsx`
|
||||||
|
const file = new Blob([buffer], {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
})
|
||||||
|
FileSaver.saveAs(file, fileName)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
openAssign(data) {
|
||||||
|
this.$refs.assignFormRef.open({
|
||||||
|
nodeId: this?.nodeData?._id,
|
||||||
|
ipData: _.cloneDeep(data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleRecycle(ip) {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('warning'),
|
||||||
|
content: this.$t('cmdb.ipam.recycleTip'),
|
||||||
|
onOk: () => {
|
||||||
|
postIPAMAddress({
|
||||||
|
ips: [ip],
|
||||||
|
parent_id: this.nodeData._id,
|
||||||
|
assign_status: 1
|
||||||
|
}).then(() => {
|
||||||
|
this.$message.success(this.$t('cmdb.ipam.recycleSuccess', { ip }))
|
||||||
|
this.getIPList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.address {
|
||||||
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
height: 32px;
|
||||||
|
width: 246px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 16px
|
||||||
|
}
|
||||||
|
|
||||||
|
&-filter {
|
||||||
|
width: 150px;
|
||||||
|
margin-right: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
column-gap: 20px;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
column-gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
|
border: solid 1px #E4E7ED;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
height: 100%;
|
||||||
|
width: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: solid 1px #E4E7ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
color: #2F54EB;
|
||||||
|
background-color: #F0F5FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-null {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 130px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&-img {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tip {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tip2 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-loading {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
color: #000000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,346 @@
|
||||||
|
<template>
|
||||||
|
<div class="ip-table">
|
||||||
|
<ops-table
|
||||||
|
ref="xTable"
|
||||||
|
size="small"
|
||||||
|
show-overflow
|
||||||
|
show-header-overflow
|
||||||
|
highlight-hover-row
|
||||||
|
:data="tableData"
|
||||||
|
:row-config="{ useKey: true, keyField: 'ip' }"
|
||||||
|
:column-config="{ resizable: true }"
|
||||||
|
:checkbox-config="{ highlight: true, reserve: true, range: true }"
|
||||||
|
:height="tableHeight"
|
||||||
|
class="ops-unstripe-table checkbox-hover-table"
|
||||||
|
@checkbox-change="onSelectChange"
|
||||||
|
@checkbox-all="onSelectChange"
|
||||||
|
@checkbox-range-end="onSelectChange"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
align="center"
|
||||||
|
type="checkbox"
|
||||||
|
:width="60"
|
||||||
|
>
|
||||||
|
<template #default="{row}">
|
||||||
|
{{ getRowSeq(row) }}
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
v-for="(col) in columns"
|
||||||
|
:key="col.field"
|
||||||
|
:title="col.title"
|
||||||
|
:field="col.field"
|
||||||
|
:width="columnWidth[col.field] || undefined"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="col.field === '_ip_status' || col.is_link || col.is_reference || col.is_choice"
|
||||||
|
#default="{ row }"
|
||||||
|
>
|
||||||
|
<div v-if="col.field === '_ip_status'" class="ip-table-status">
|
||||||
|
<div
|
||||||
|
class="ip-table-status-dot"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: `${STATUS_COLOR[row._ip_status]}22`
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ip-table-status-dot-content"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: STATUS_COLOR[row._ip_status]
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ip-table-status-text"
|
||||||
|
>
|
||||||
|
{{ $t(STATUS_LABEL[row._ip_status]) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="col.is_reference && row[col.field]" >
|
||||||
|
<a
|
||||||
|
v-for="(ciId) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||||
|
:key="ciId"
|
||||||
|
:href="`/cmdb/cidetail/${col.reference_type_id}/${ciId}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getReferenceAttrValue(ciId, col) }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="col.is_link && row[col.field]">
|
||||||
|
<a
|
||||||
|
v-for="(linkItem, linkIndex) in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||||
|
:key="linkIndex"
|
||||||
|
:href="
|
||||||
|
linkItem.startsWith('http') || linkItem.startsWith('https')
|
||||||
|
? `${linkItem}`
|
||||||
|
: `http://${linkItem}`
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ getChoiceValueLabel(col, linkItem) || linkItem }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="col.is_choice && row[col.field]">
|
||||||
|
<span
|
||||||
|
v-for="value in (col.is_list ? row[col.field] : [row[col.field]])"
|
||||||
|
:key="value"
|
||||||
|
class="column-default-choice"
|
||||||
|
>
|
||||||
|
{{ getChoiceValueLabel(col, value) || value }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('operation')"
|
||||||
|
:width="80"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="ip-table-operation">
|
||||||
|
<template v-if="[ADDRESS_STATUS.ONLINE_ASSIGNED, ADDRESS_STATUS.OFFLINE_ASSIGNED].includes(row._ip_status)">
|
||||||
|
<a-tooltip :title="$t('cmdb.ipam.editAssignAddress')">
|
||||||
|
<a @click="assignAddress(row)"><ops-icon type="veops-edit" /></a>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip :title="$t('cmdb.ipam.recycle')">
|
||||||
|
<a @click="clickRecycle(row)"><ops-icon type="veops-recycle" /></a>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-tooltip v-else :title="$t('cmdb.ipam.assign')">
|
||||||
|
<a @click="assignAddress(row)"><ops-icon type="monitor-add2" /></a>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
</ops-table>
|
||||||
|
<div class="ip-table-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="allTableData.length"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@showSizeChange="handlePageSizeChange"
|
||||||
|
@change="changePage"
|
||||||
|
>
|
||||||
|
<template slot="buildOptionText" slot-scope="props">
|
||||||
|
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||||
|
<span v-if="props.value === '100000'">{{ $t('all') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import { STATUS_COLOR, STATUS_LABEL, ADDRESS_STATUS } from './constants.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TableIP',
|
||||||
|
props: {
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
allTableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
referenceShowAttrNameMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
referenceCIIdMap: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
columnWidth: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
STATUS_COLOR,
|
||||||
|
STATUS_LABEL,
|
||||||
|
ADDRESS_STATUS,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return `${this.windowHeight - 270}px`
|
||||||
|
},
|
||||||
|
tableData() {
|
||||||
|
const start = (this.page - 1) * this.pageSize
|
||||||
|
const end = start + this.pageSize
|
||||||
|
const tableData = this.allTableData.slice(start, end)
|
||||||
|
|
||||||
|
return _.cloneDeep(tableData)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
allTableData: {
|
||||||
|
immediate: true,
|
||||||
|
handler() {
|
||||||
|
this.page = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRowSeq(row) {
|
||||||
|
const table = this.$refs?.['xTable']?.getVxetableRef?.() || null
|
||||||
|
return table?.getRowSeq?.(row)
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePageSizeChange(_, pageSize) {
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.page = 1
|
||||||
|
},
|
||||||
|
|
||||||
|
changePage(page) {
|
||||||
|
this.page = page
|
||||||
|
},
|
||||||
|
|
||||||
|
assignAddress(data) {
|
||||||
|
this.$emit('openAssign', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
clickRecycle(data) {
|
||||||
|
this.$emit('recycle', data.ip)
|
||||||
|
},
|
||||||
|
|
||||||
|
getCheckedTableData(clearCheckbox = true) {
|
||||||
|
const tableRef = this.$refs.xTable.getVxetableRef()
|
||||||
|
let tableData = _.cloneDeep([
|
||||||
|
...tableRef.getCheckboxReserveRecords(),
|
||||||
|
...tableRef.getCheckboxRecords(true),
|
||||||
|
])
|
||||||
|
if (!tableData.length) {
|
||||||
|
const { fullData } = tableRef.getTableData()
|
||||||
|
tableData = _.cloneDeep(fullData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearCheckbox) {
|
||||||
|
tableRef.clearCheckboxRow()
|
||||||
|
tableRef.clearCheckboxReserve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableData
|
||||||
|
},
|
||||||
|
|
||||||
|
getReferenceAttrValue(id, col) {
|
||||||
|
const ci = this?.referenceCIIdMap?.[col?.reference_type_id]?.[id]
|
||||||
|
if (!ci) {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrName = this.referenceShowAttrNameMap?.[col.reference_type_id]
|
||||||
|
return ci?.[attrName] || id
|
||||||
|
},
|
||||||
|
|
||||||
|
getChoiceValueLabel(col, colValue) {
|
||||||
|
const _find = col?.choice_value?.find((item) => String(item[0]) === String(colValue))
|
||||||
|
if (_find) {
|
||||||
|
return _find?.[1]?.label || ''
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelectChange() {
|
||||||
|
console.log('onSelectChange')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ip-table {
|
||||||
|
&-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-operation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-hover-table {
|
||||||
|
/deep/ .vxe-table--body-wrapper {
|
||||||
|
.vxe-checkbox--label {
|
||||||
|
display: inline;
|
||||||
|
padding-left: 0px !important;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-icon-checkbox-unchecked {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-icon-checkbox-checked ~ .vxe-checkbox--label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-cell--checkbox {
|
||||||
|
&:hover {
|
||||||
|
.vxe-icon-checkbox-unchecked {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-checkbox--label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,96 @@
|
||||||
|
<template>
|
||||||
|
<div class="history">
|
||||||
|
<div class="history-tab">
|
||||||
|
<div
|
||||||
|
v-for="(item) in tabs"
|
||||||
|
:key="item.key"
|
||||||
|
:class="['history-tab-item', activeKey === item.key ? 'history-tab-item-active' : '']"
|
||||||
|
@click="activeKey = item.key"
|
||||||
|
>
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="history-main">
|
||||||
|
<Operation
|
||||||
|
v-if="activeKey === 'operation'"
|
||||||
|
ref="operationRef"
|
||||||
|
/>
|
||||||
|
<Scan v-if="activeKey === 'scan'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Operation from './operation/index.vue'
|
||||||
|
import Scan from './scan/index.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HistoryLog',
|
||||||
|
components: {
|
||||||
|
Operation,
|
||||||
|
Scan
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeKey: 'operation',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
key: 'operation',
|
||||||
|
title: 'cmdb.ipam.operationLog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'scan',
|
||||||
|
title: 'cmdb.ipam.scanLog'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refreshData() {
|
||||||
|
if (this.activeKey === 'operation' && this.$refs.operationRef) {
|
||||||
|
this.$refs.operationRef.getTableData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.history {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-tab {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border: solid 1px #E4E7ED;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 20px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2F54EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
background-color: #2F54EB;
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-main {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,56 @@
|
||||||
|
export const OPERATE_TYPE = {
|
||||||
|
ADD_SCOPE: '0',
|
||||||
|
UPDATE_SCOPE: '1',
|
||||||
|
DELETE_SCOPE: '2',
|
||||||
|
ADD_SUBNET: '3',
|
||||||
|
UPDATE_SUBNET: '4',
|
||||||
|
DELETE_SUBNET: '5',
|
||||||
|
ASSIGN_ADDRESS: '6',
|
||||||
|
REVOKE_ADDRESS: '7',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OPERATE_TYPE_TEXT = {
|
||||||
|
[OPERATE_TYPE.ADD_SCOPE]: 'cmdb.ipam.addCatalog',
|
||||||
|
[OPERATE_TYPE.UPDATE_SCOPE]: 'cmdb.ipam.updateCatalog',
|
||||||
|
[OPERATE_TYPE.DELETE_SCOPE]: 'cmdb.ipam.deleteCatalog',
|
||||||
|
[OPERATE_TYPE.ADD_SUBNET]: 'cmdb.ipam.addSubnet',
|
||||||
|
[OPERATE_TYPE.UPDATE_SUBNET]: 'cmdb.ipam.updateSubnet',
|
||||||
|
[OPERATE_TYPE.DELETE_SUBNET]: 'cmdb.ipam.deleteSubnet',
|
||||||
|
[OPERATE_TYPE.ASSIGN_ADDRESS]: 'cmdb.ipam.addressAssign',
|
||||||
|
[OPERATE_TYPE.REVOKE_ADDRESS]: 'cmdb.ipam.revokeAddress',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OPERATE_TYPE_COLOR = {
|
||||||
|
[OPERATE_TYPE.ADD_SCOPE]: {
|
||||||
|
color: '#2F54EB',
|
||||||
|
backgroundColor: '#DCF5FF'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.UPDATE_SCOPE]: {
|
||||||
|
color: '#FF7D00',
|
||||||
|
backgroundColor: '#FFECCF'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.DELETE_SCOPE]: {
|
||||||
|
color: '#FD4C6A',
|
||||||
|
backgroundColor: '#FFECE8'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.ADD_SUBNET]: {
|
||||||
|
color: '#2F54EB',
|
||||||
|
backgroundColor: '#DCF5FF'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.UPDATE_SUBNET]: {
|
||||||
|
color: '#FF7D00',
|
||||||
|
backgroundColor: '#FFECCF'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.DELETE_SUBNET]: {
|
||||||
|
color: '#FD4C6A',
|
||||||
|
backgroundColor: '#FFECE8'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.ASSIGN_ADDRESS]: {
|
||||||
|
color: '#00B42A',
|
||||||
|
backgroundColor: '#F6FFED'
|
||||||
|
},
|
||||||
|
[OPERATE_TYPE.REVOKE_ADDRESS]: {
|
||||||
|
color: '#0AA5A8',
|
||||||
|
backgroundColor: '#E8FFFB'
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
<template>
|
||||||
|
<div class="operate">
|
||||||
|
<a-input-search
|
||||||
|
class="operate-search"
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ops-table
|
||||||
|
ref="xTable"
|
||||||
|
size="small"
|
||||||
|
show-overflow
|
||||||
|
show-header-overflow
|
||||||
|
highlight-hover-row
|
||||||
|
:data="tableData"
|
||||||
|
:height="tableHeight"
|
||||||
|
class="ops-unstripe-table operate-table"
|
||||||
|
:filter-config="{ remote: true }"
|
||||||
|
:sort-config="{ remote: true, trigger: 'cell' }"
|
||||||
|
:column-config="{ resizable: true }"
|
||||||
|
@filter-change="handlefilterChange"
|
||||||
|
@sort-change="handleSortChange"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.operateTime')"
|
||||||
|
sortable
|
||||||
|
field="created_at"
|
||||||
|
:width="150"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.operateUser')"
|
||||||
|
field="uid"
|
||||||
|
:filters="userFilters"
|
||||||
|
:width="130"
|
||||||
|
>
|
||||||
|
<template #default="{row}">
|
||||||
|
{{ row.nickname }}
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.operateType')"
|
||||||
|
field="operate_type"
|
||||||
|
:filters="operateTypeFilters"
|
||||||
|
:width="150"
|
||||||
|
>
|
||||||
|
<template #default="{row}">
|
||||||
|
<div
|
||||||
|
v-if="row.operate_type"
|
||||||
|
class="operate-table-type"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: OPERATE_TYPE_COLOR[row.operate_type].backgroundColor,
|
||||||
|
color: OPERATE_TYPE_COLOR[row.operate_type].color
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ $t(OPERATE_TYPE_TEXT[row.operate_type]) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
title="CIDR"
|
||||||
|
field="cidr"
|
||||||
|
:width="150"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.description')"
|
||||||
|
field="description"
|
||||||
|
></vxe-table-column>
|
||||||
|
</ops-table>
|
||||||
|
|
||||||
|
<div class="operate-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="totalNumber"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@change="handleChangePage"
|
||||||
|
@showSizeChange="onShowSizeChange"
|
||||||
|
>
|
||||||
|
<template slot="buildOptionText" slot-scope="props">
|
||||||
|
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||||
|
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import { OPERATE_TYPE_TEXT, OPERATE_TYPE_COLOR, OPERATE_TYPE } from './constants.js'
|
||||||
|
import { getIPAMHistoryOperate } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Operate',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
OPERATE_TYPE_TEXT,
|
||||||
|
OPERATE_TYPE_COLOR,
|
||||||
|
searchValue: '',
|
||||||
|
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
tableData: [],
|
||||||
|
totalNumber: 0,
|
||||||
|
getTableDataParams: {
|
||||||
|
reverse: 1
|
||||||
|
},
|
||||||
|
userFilters: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
allEmployees: (state) => state.user.allEmployees,
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return `${this.windowHeight - 308}px`
|
||||||
|
},
|
||||||
|
operateTypeFilters() {
|
||||||
|
const filters = Object.values(OPERATE_TYPE).map((key) => {
|
||||||
|
return {
|
||||||
|
value: key,
|
||||||
|
label: this.$t(OPERATE_TYPE_TEXT[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getTableData() {
|
||||||
|
const res = await getIPAMHistoryOperate({
|
||||||
|
page: this.page,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
...this.getTableDataParams
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableData = res?.result || []
|
||||||
|
const userFilters = []
|
||||||
|
const defaultUserChecked = this.getTableDataParams.uid ? this.getTableDataParams.uid.split(',') : []
|
||||||
|
|
||||||
|
tableData.forEach((item) => {
|
||||||
|
const nickname = this.allEmployees?.find?.((user) => user?.acl_uid === item?.uid)?.nickname
|
||||||
|
item.nickname = nickname
|
||||||
|
userFilters.push({
|
||||||
|
label: nickname,
|
||||||
|
value: item.uid,
|
||||||
|
checked: defaultUserChecked.includes(String(item.uid))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.totalNumber = res?.numfound || 0
|
||||||
|
this.tableData = tableData
|
||||||
|
this.userFilters = _.uniqBy(userFilters, 'value')
|
||||||
|
},
|
||||||
|
handleSearch(v) {
|
||||||
|
if (v) {
|
||||||
|
this.getTableDataParams.cidr = `*${v}*`
|
||||||
|
} else if (this.getTableDataParams.cidr) {
|
||||||
|
delete this.getTableDataParams.cidr
|
||||||
|
}
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
handleChangePage(page) {
|
||||||
|
this.page = page
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowSizeChange(_, pageSize) {
|
||||||
|
this.page = 1
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
handlefilterChange({ field, values }) {
|
||||||
|
this.page = 1
|
||||||
|
const value = values.join(',')
|
||||||
|
if (!value && this.getTableDataParams[field]) {
|
||||||
|
delete this.getTableDataParams[field]
|
||||||
|
} else {
|
||||||
|
this.getTableDataParams[field] = values.join(',')
|
||||||
|
}
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSortChange(data) {
|
||||||
|
if (data?.order === 'asc') {
|
||||||
|
this.getTableDataParams.reverse = 0
|
||||||
|
} else {
|
||||||
|
this.getTableDataParams.reverse = 1
|
||||||
|
}
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.operate {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
width: 244px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-table {
|
||||||
|
&-type {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 0 9px;
|
||||||
|
height: 22px;
|
||||||
|
line-height: 22px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,286 @@
|
||||||
|
<template>
|
||||||
|
<div class="scan">
|
||||||
|
<a-input-search
|
||||||
|
class="scan-search"
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ops-table
|
||||||
|
ref="xTable"
|
||||||
|
size="small"
|
||||||
|
show-overflow
|
||||||
|
show-header-overflow
|
||||||
|
highlight-hover-row
|
||||||
|
:data="tableData"
|
||||||
|
:height="tableHeight"
|
||||||
|
:column-config="{ resizable: true }"
|
||||||
|
class="ops-unstripe-table scan-table"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
title="CIDR"
|
||||||
|
field="cidr"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.ipNumber')"
|
||||||
|
field="ip_num"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.startTime')"
|
||||||
|
field="start_at"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.endTime')"
|
||||||
|
field="end_at"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.scanningTime')"
|
||||||
|
field="scanning_time"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.isSuccess')"
|
||||||
|
field="status"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="scan-table-success" v-if="row.status === 0">
|
||||||
|
<a-icon class="scan-table-success-icon" type="check-circle" theme="filled" />
|
||||||
|
<div class="scan-table-success-text">{{ $t('success') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="scan-table-fail" v-else>
|
||||||
|
<a-icon class="scan-table-fail-icon" type="close-circle" theme="filled" />
|
||||||
|
<div class="scan-table-fail-text">{{ $t('fail') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.viewResult')"
|
||||||
|
field="operation"
|
||||||
|
:show-overflow="false"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<a-popover placement="left">
|
||||||
|
<span class="scan-table-operation">
|
||||||
|
{{ row.status === 0 ? row.ips ? row.ips.join(', ') : '' : row.stdout }}
|
||||||
|
</span>
|
||||||
|
<template slot="content">
|
||||||
|
<div
|
||||||
|
v-if="row.status === 0"
|
||||||
|
class="scan-table-ip"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(ip, index) in row.ips"
|
||||||
|
:key="index"
|
||||||
|
class="scan-table-ip-item"
|
||||||
|
>
|
||||||
|
{{ ip }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="scan-table-error-log"
|
||||||
|
>
|
||||||
|
{{ row.stdout }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
</ops-table>
|
||||||
|
|
||||||
|
<div class="scan-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="totalNumber"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@change="handleChangePage"
|
||||||
|
@showSizeChange="onShowSizeChange"
|
||||||
|
>
|
||||||
|
<template slot="buildOptionText" slot-scope="props">
|
||||||
|
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||||
|
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import { getIPAMHistoryScan } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Scan',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
tableData: [],
|
||||||
|
totalNumber: 0,
|
||||||
|
getTableDataParams: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return `${this.windowHeight - 308}px`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getTableData() {
|
||||||
|
const res = await getIPAMHistoryScan({
|
||||||
|
page: this.page,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
reverse: 1,
|
||||||
|
...this.getTableDataParams
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableData = res?.result || []
|
||||||
|
|
||||||
|
tableData.forEach((item) => {
|
||||||
|
if (item.start_at && item.end_at) {
|
||||||
|
const startAt = moment(item.start_at)
|
||||||
|
const endAt = moment(item.end_at)
|
||||||
|
item.scanning_time = `${endAt.diff(startAt, 'seconds')}s`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.tableData = tableData
|
||||||
|
this.totalNumber = res?.numfound || 0
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangePage(page) {
|
||||||
|
this.page = page
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowSizeChange(_, pageSize) {
|
||||||
|
this.page = 1
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch(v) {
|
||||||
|
if (v) {
|
||||||
|
this.getTableDataParams.cidr = `*${v}*`
|
||||||
|
} else if (this.getTableDataParams.cidr) {
|
||||||
|
delete this.getTableDataParams.cidr
|
||||||
|
}
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.scan {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-search {
|
||||||
|
width: 244px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-table {
|
||||||
|
&-success {
|
||||||
|
padding: 4px 7px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: #DCF3E3;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #00B42A;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #30AD2D;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fail {
|
||||||
|
padding: 0px 7px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: #FFECE8;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #FD4C6A;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #FD4C6A;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-operation {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-ip {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 216px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border: solid 1px #F0F1F5;
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1D2129;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: solid 1px #F0F1F5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-error-log {
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,385 @@
|
||||||
|
<template>
|
||||||
|
<div ref="wrapRef">
|
||||||
|
<div class="table-header">
|
||||||
|
<SearchForm
|
||||||
|
ref="search"
|
||||||
|
:preferenceAttrList="preferenceAttrList"
|
||||||
|
:typeId="addressCITypeId"
|
||||||
|
@copyExpression="copyExpression"
|
||||||
|
@refresh="handleSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table-header-right">
|
||||||
|
<EditAttrsPopover
|
||||||
|
:typeId="addressCITypeId"
|
||||||
|
@refresh="refreshAfterEditAttrs"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
ghost
|
||||||
|
class="ops-button-ghost"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-configuration_table" />
|
||||||
|
{{ $t('cmdb.configTable') }}
|
||||||
|
</a-button>
|
||||||
|
</EditAttrsPopover>
|
||||||
|
<a-button
|
||||||
|
v-if="instanceList && instanceList.length"
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost"
|
||||||
|
ghost
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-export" />
|
||||||
|
{{ $t('export') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CITable
|
||||||
|
ref="xTable"
|
||||||
|
:loading="loading"
|
||||||
|
:attrList="preferenceAttrList"
|
||||||
|
:columns="columns"
|
||||||
|
:data="instanceList"
|
||||||
|
:height="tableHeight"
|
||||||
|
@sort-change="handleSortCol"
|
||||||
|
@openDetail="openDetail"
|
||||||
|
@deleteCI="deleteCI"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="totalNumber"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@change="handleChangePage"
|
||||||
|
@showSizeChange="onShowSizeChange"
|
||||||
|
>
|
||||||
|
<template slot="buildOptionText" slot-scope="props">
|
||||||
|
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||||
|
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BatchDownload
|
||||||
|
ref="batchDownload"
|
||||||
|
:showFileTypeSelect="false"
|
||||||
|
@batchDownload="batchDownload"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CIDetailDrawer ref="detail" :typeId="addressCITypeId" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import ExcelJS from 'exceljs'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import { searchCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||||
|
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||||
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { getCITableColumns } from '@/modules/cmdb/utils/helper'
|
||||||
|
|
||||||
|
import SearchForm from '@/modules/cmdb/components/searchForm/SearchForm.vue'
|
||||||
|
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||||
|
import BatchDownload from '@/modules/cmdb/components/batchDownload/batchDownload.vue'
|
||||||
|
import CIDetailDrawer from '@/modules/cmdb/views/ci/modules/ciDetailDrawer.vue'
|
||||||
|
import EditAttrsPopover from '@/modules/cmdb/views/ci/modules/editAttrsPopover.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'IPSearch',
|
||||||
|
components: {
|
||||||
|
SearchForm,
|
||||||
|
CITable,
|
||||||
|
BatchDownload,
|
||||||
|
CIDetailDrawer,
|
||||||
|
EditAttrsPopover
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
addressCIType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
loading: false,
|
||||||
|
sortByTable: undefined,
|
||||||
|
|
||||||
|
instanceList: [],
|
||||||
|
totalNumber: 0,
|
||||||
|
columns: [],
|
||||||
|
preferenceAttrList: [],
|
||||||
|
attrList: [],
|
||||||
|
attributes: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return this.windowHeight - 260
|
||||||
|
},
|
||||||
|
addressCITypeId() {
|
||||||
|
return this.addressCIType?.id || null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
handleSearch: this.getTableData,
|
||||||
|
attrList: () => {
|
||||||
|
return this.attrList
|
||||||
|
},
|
||||||
|
attributes: () => {
|
||||||
|
return this.attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.$nextTick(async () => {
|
||||||
|
if (this.addressCITypeId) {
|
||||||
|
await this.getAttributeList()
|
||||||
|
await this.getPreferenceAttrList()
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getAttributeList() {
|
||||||
|
await getCITypeAttributesById(this.addressCITypeId).then((res) => {
|
||||||
|
this.attrList = res.attributes
|
||||||
|
this.attributes = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async getPreferenceAttrList() {
|
||||||
|
const subscribed = await getSubscribeAttributes(this.addressCITypeId)
|
||||||
|
this.preferenceAttrList = subscribed.attributes
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTableData() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||||||
|
const expression = this.$refs['search'].expression || ''
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
const regSort = /(?<=sort=).+/g
|
||||||
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
|
||||||
|
let sort
|
||||||
|
if (this.sortByTable) {
|
||||||
|
sort = this.sortByTable
|
||||||
|
} else {
|
||||||
|
sort = expression.match(regSort) ? expression.match(regSort)[0] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await searchCI({
|
||||||
|
q: `_type:${this.addressCITypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||||
|
count: this.pageSize,
|
||||||
|
page: this.page,
|
||||||
|
sort,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.totalNumber = res?.numfound
|
||||||
|
const instanceList = res.result
|
||||||
|
|
||||||
|
const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
||||||
|
instanceList.forEach((item) => {
|
||||||
|
jsonAttrList.forEach(
|
||||||
|
(jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.getColumns(instanceList)
|
||||||
|
this.instanceList = instanceList
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getColumns(data) {
|
||||||
|
const width = this.$refs.wrapRef.clientWidth - 50
|
||||||
|
const columns = getCITableColumns(data, this.preferenceAttrList, width)
|
||||||
|
columns.forEach((item) => {
|
||||||
|
if (item.editRender) {
|
||||||
|
item.editRender.enabled = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.columns = columns
|
||||||
|
},
|
||||||
|
|
||||||
|
copyExpression() {
|
||||||
|
const expression = this.$refs['search'].expression || ''
|
||||||
|
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||||||
|
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
|
||||||
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
const text = `q=_type:${this.addressCITypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`
|
||||||
|
this.$copyText(text)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('copySuccess'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch() {
|
||||||
|
this.$refs.xTable.getVxetableRef().clearSort()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangePage(page) {
|
||||||
|
this.page = page
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowSizeChange(_, pageSize) {
|
||||||
|
this.page = 1
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSortCol({ property, order }) {
|
||||||
|
let sortByTable
|
||||||
|
if (order === 'asc') {
|
||||||
|
sortByTable = property
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
sortByTable = `-${property}`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortByTable = sortByTable
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExport() {
|
||||||
|
this.$refs.batchDownload.open({
|
||||||
|
preferenceAttrList: this.preferenceAttrList,
|
||||||
|
ciTypeName: this.$t('cmdb.ipam.ipSearch') || '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
batchDownload({ checkedKeys, filename }) {
|
||||||
|
const wb = new ExcelJS.Workbook()
|
||||||
|
|
||||||
|
const tableRef = this.$refs.xTable.getVxetableRef()
|
||||||
|
let tableData = _.cloneDeep([
|
||||||
|
...tableRef.getCheckboxReserveRecords(),
|
||||||
|
...tableRef.getCheckboxRecords(true),
|
||||||
|
])
|
||||||
|
if (!tableData.length) {
|
||||||
|
const { fullData } = tableRef.getTableData()
|
||||||
|
tableData = _.cloneDeep(fullData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = wb.addWorksheet(this.tabActive)
|
||||||
|
const columns = []
|
||||||
|
|
||||||
|
const attrMap = new Map()
|
||||||
|
this.columns.filter((col) => checkedKeys.includes(col.field)).map((col) => {
|
||||||
|
attrMap.set(col.field, col)
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
header: col.title || '',
|
||||||
|
key: col.field,
|
||||||
|
width: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.columns = columns
|
||||||
|
|
||||||
|
tableData.forEach((item) => {
|
||||||
|
const row = {}
|
||||||
|
|
||||||
|
columns.forEach(({ key }) => {
|
||||||
|
const value = item?.[key] ?? null
|
||||||
|
const attr = attrMap.get(key)
|
||||||
|
if (attr.valueType === '6') {
|
||||||
|
row[key] = value ? JSON.stringify(value) : value
|
||||||
|
} else if (attr.is_list && Array.isArray(value)) {
|
||||||
|
row[key] = value.join(',')
|
||||||
|
} else {
|
||||||
|
row[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.addRow(row)
|
||||||
|
})
|
||||||
|
|
||||||
|
wb.xlsx.writeBuffer().then((buffer) => {
|
||||||
|
const file = new Blob([buffer], {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
})
|
||||||
|
FileSaver.saveAs(file, `${filename}.xlsx`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||||
|
},
|
||||||
|
|
||||||
|
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||||
|
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshAfterEditAttrs() {
|
||||||
|
await this.getPreferenceAttrList()
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCI(record) {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('warning'),
|
||||||
|
content: this.$t('confirmDelete'),
|
||||||
|
onOk: () => {
|
||||||
|
deleteCI(record.ci_id || record._id).then(() => {
|
||||||
|
this.$message.success(this.$t('deleteSuccess'))
|
||||||
|
this.getTableData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.table-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,65 @@
|
||||||
|
<template>
|
||||||
|
<div class="overview">
|
||||||
|
<Stats :statsData="statsData" />
|
||||||
|
<SubnetTable :tableData="tableData" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getIPAMStats } from '@/modules/cmdb/api/ipam.js'
|
||||||
|
|
||||||
|
import Stats from './stats.vue'
|
||||||
|
import SubnetTable from './subnetTable.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Overview',
|
||||||
|
components: {
|
||||||
|
Stats,
|
||||||
|
SubnetTable
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nodeId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
statsData: {},
|
||||||
|
tableData: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
nodeId: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(newValue, oldValue) {
|
||||||
|
if (newValue !== oldValue) {
|
||||||
|
this.initData(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initData() {
|
||||||
|
const res = await getIPAMStats({
|
||||||
|
parent_id: this.nodeId === 'all' ? 0 : this.nodeId
|
||||||
|
})
|
||||||
|
const tableData = res?.subnets || []
|
||||||
|
tableData.forEach((item) => {
|
||||||
|
item.hosts_count = item?.hosts_count || 0
|
||||||
|
item.used_ratio = item?.used_count && item?.hosts_count ? Math.round((item.used_count / item.hosts_count) * 100) : 0
|
||||||
|
})
|
||||||
|
|
||||||
|
this.statsData = res
|
||||||
|
this.tableData = tableData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.overview {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,212 @@
|
||||||
|
<template>
|
||||||
|
<div class="stats">
|
||||||
|
<div
|
||||||
|
class="stats-card"
|
||||||
|
v-for="(item, index) in statsListData"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<div class="stats-card-left">
|
||||||
|
<div class="stats-card-title">{{ $t(item.title) }}</div>
|
||||||
|
|
||||||
|
<div class="stats-card-row">
|
||||||
|
<div
|
||||||
|
v-for="(dataItem, dataIndex) in item.data"
|
||||||
|
:key="dataIndex"
|
||||||
|
class="stats-card-data"
|
||||||
|
>
|
||||||
|
<span class="stats-card-data-label">{{ $t(dataItem.label) }}</span>
|
||||||
|
<span class="stats-card-data-value">{{ dataItem.value }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="item.logo"
|
||||||
|
class="stats-card-logo"
|
||||||
|
>
|
||||||
|
<ops-icon
|
||||||
|
:type="item.logo"
|
||||||
|
class="stats-card-logo-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatsChart
|
||||||
|
v-else
|
||||||
|
:statsData="item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import StatsChart from './statsChart.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Statistics',
|
||||||
|
components: {
|
||||||
|
StatsChart
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
statsData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
statsListData() {
|
||||||
|
const {
|
||||||
|
subnet_num = 0,
|
||||||
|
address_num = 0,
|
||||||
|
address_free_num = 0,
|
||||||
|
address_assign_num = 0,
|
||||||
|
address_unassign_num = 0,
|
||||||
|
address_used_num = 0,
|
||||||
|
address_used_free_num = 0
|
||||||
|
} = this.statsData || {}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: 'cmdb.ipam.subnetStats',
|
||||||
|
logo: 'caise-IPAM',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.total',
|
||||||
|
value: subnet_num
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'cmdb.ipam.addressStats',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.total',
|
||||||
|
value: address_num,
|
||||||
|
chartValue: address_num - address_free_num,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.free',
|
||||||
|
value: address_free_num
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ratio: address_num && address_free_num ? Math.round((address_free_num / address_num) * 100) : 0,
|
||||||
|
chartColor: ['#6EE3EB', '#6592FD']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'cmdb.ipam.assignStats',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.assigned',
|
||||||
|
value: address_assign_num
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.unassigned',
|
||||||
|
value: address_unassign_num
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ratio: address_num && address_assign_num ? Math.round((address_assign_num / address_num) * 100) : 0,
|
||||||
|
chartColor: ['#8C85ED', '#387BFD']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'cmdb.ipam.onlineStats',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.online',
|
||||||
|
value: address_used_num
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'cmdb.ipam.offline',
|
||||||
|
value: address_used_free_num
|
||||||
|
}
|
||||||
|
],
|
||||||
|
ratio: address_num && address_used_num ? Math.round((address_used_num / address_num) * 100) : 0,
|
||||||
|
chartColor: ['#009FA9', '#17D4B0']
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
statsData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(data) {
|
||||||
|
this.initData(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.stats {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
column-gap: 24px;
|
||||||
|
row-gap: 12px;
|
||||||
|
|
||||||
|
&-card {
|
||||||
|
padding: 14px 17px;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-top: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-data {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #1D2129;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-logo {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
border-radius: 52px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,105 @@
|
||||||
|
<template>
|
||||||
|
<div class="stats-chart">
|
||||||
|
<div
|
||||||
|
class="stats-chart-pie"
|
||||||
|
ref="statsChartRef"
|
||||||
|
></div>
|
||||||
|
<div class="stats-chart-ratio">
|
||||||
|
{{ statsData.ratio }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'StatsChart',
|
||||||
|
props: {
|
||||||
|
statsData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
statsData: {
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
handler(data) {
|
||||||
|
this.updateChart(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.chart) {
|
||||||
|
this.chart.dispose()
|
||||||
|
this.chart = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateChart(data) {
|
||||||
|
const option = {
|
||||||
|
color: data?.chartColor || [],
|
||||||
|
tooltip: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['60%', '85%'],
|
||||||
|
data: data?.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
name: this.$t(item?.label),
|
||||||
|
value: item?.chartValue ?? item.value
|
||||||
|
}
|
||||||
|
}) || [],
|
||||||
|
itemStyle: {
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 1
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (!this.chart) {
|
||||||
|
const el = this.$refs.statsChartRef
|
||||||
|
this.chart = echarts.init(el)
|
||||||
|
}
|
||||||
|
this.chart.setOption(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.stats-chart {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&-pie {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-ratio {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1D2129;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,207 @@
|
||||||
|
<template>
|
||||||
|
<div class="subnet-table">
|
||||||
|
<div class="subnet-table-title">
|
||||||
|
{{ $t('cmdb.ipam.onlineUsageStats') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ops-table
|
||||||
|
ref="xTable"
|
||||||
|
show-overflow
|
||||||
|
show-header-overflow
|
||||||
|
highlight-hover-row
|
||||||
|
:data="tableData"
|
||||||
|
size="small"
|
||||||
|
:height="tableHeight"
|
||||||
|
:column-config="{ resizable: true }"
|
||||||
|
class="ops-unstripe-table"
|
||||||
|
>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.subnetName')"
|
||||||
|
:min-width="130"
|
||||||
|
field="name"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
title="CIDR"
|
||||||
|
field="cidr"
|
||||||
|
:min-width="130"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.addressCount')"
|
||||||
|
field="hosts_count"
|
||||||
|
:min-width="70"
|
||||||
|
></vxe-table-column>
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.onlineRatio')"
|
||||||
|
field="onlineRatio"
|
||||||
|
:min-width="180"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="subnet-table-ratio">
|
||||||
|
<div class="subnet-table-ratio-value">
|
||||||
|
{{ row.used_ratio }}%
|
||||||
|
</div>
|
||||||
|
<div class="subnet-table-ratio-progress">
|
||||||
|
<div
|
||||||
|
class="subnet-table-ratio-progress-content"
|
||||||
|
:style="{
|
||||||
|
width: row.used_ratio + '%'
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="subnet-table-ratio-count">
|
||||||
|
{{ row.used_count }}/{{ row.hosts_count }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.assigned')"
|
||||||
|
field="assign_count"
|
||||||
|
:min-width="70"
|
||||||
|
></vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.free')"
|
||||||
|
field="free_count"
|
||||||
|
:min-width="50"
|
||||||
|
></vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.scanEnable')"
|
||||||
|
field="scan_enabled"
|
||||||
|
:min-width="100"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="subnet-table-scan-yes" v-if="row.scan_enabled">
|
||||||
|
<a-icon class="subnet-table-scan-yes-icon" type="check-circle" theme="filled" />
|
||||||
|
<div class="subnet-table-scan-yes-text">{{ $t('yes') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="subnet-table-scan-no" v-else>
|
||||||
|
<a-icon class="subnet-table-scan-no-icon" type="close-circle" theme="filled" />
|
||||||
|
<div class="subnet-table-scan-no-text">{{ $t('no') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</vxe-table-column>
|
||||||
|
|
||||||
|
<vxe-table-column
|
||||||
|
:title="$t('cmdb.ipam.lastScanTime')"
|
||||||
|
field="last_scan_time"
|
||||||
|
:min-width="100"
|
||||||
|
></vxe-table-column>
|
||||||
|
</ops-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SubnetTable',
|
||||||
|
props: {
|
||||||
|
tableData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return `${this.windowHeight - 337}px`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.subnet-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-ratio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progress {
|
||||||
|
width: 84px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #EBEFF8;
|
||||||
|
margin-left: 12px;
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #7F97FA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-count {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #86909C;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-scan-yes {
|
||||||
|
padding: 4px 7px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: #DCF3E3;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #00B42A;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #30AD2D;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-scan-no {
|
||||||
|
padding: 0px 7px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: #E4E7ED;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #A5A9BC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #4E5969;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,387 @@
|
||||||
|
<template>
|
||||||
|
<div ref="wrapRef">
|
||||||
|
<div class="table-header">
|
||||||
|
<SearchForm
|
||||||
|
ref="search"
|
||||||
|
:preferenceAttrList="preferenceAttrList"
|
||||||
|
:typeId="subnetCITypeId"
|
||||||
|
@copyExpression="copyExpression"
|
||||||
|
@refresh="handleSearch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table-header-right">
|
||||||
|
<EditAttrsPopover
|
||||||
|
:typeId="subnetCITypeId"
|
||||||
|
@refresh="refreshAfterEditAttrs"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
ghost
|
||||||
|
class="ops-button-ghost"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-configuration_table" />
|
||||||
|
{{ $t('cmdb.configTable') }}
|
||||||
|
</a-button>
|
||||||
|
</EditAttrsPopover>
|
||||||
|
<a-button
|
||||||
|
v-if="instanceList && instanceList.length"
|
||||||
|
type="primary"
|
||||||
|
class="ops-button-ghost"
|
||||||
|
ghost
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<ops-icon type="veops-export" />
|
||||||
|
{{ $t('export') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CITable
|
||||||
|
ref="xTable"
|
||||||
|
:loading="loading"
|
||||||
|
:attrList="preferenceAttrList"
|
||||||
|
:columns="columns"
|
||||||
|
:data="instanceList"
|
||||||
|
:height="tableHeight"
|
||||||
|
@sort-change="handleSortCol"
|
||||||
|
@openDetail="openDetail"
|
||||||
|
@deleteCI="deleteCI"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="table-pagination">
|
||||||
|
<a-pagination
|
||||||
|
:showSizeChanger="true"
|
||||||
|
:current="page"
|
||||||
|
size="small"
|
||||||
|
:total="totalNumber"
|
||||||
|
show-quick-jumper
|
||||||
|
:page-size="pageSize"
|
||||||
|
:page-size-options="pageSizeOptions"
|
||||||
|
:show-total="
|
||||||
|
(total, range) =>
|
||||||
|
$t('pagination.total', {
|
||||||
|
range0: range[0],
|
||||||
|
range1: range[1],
|
||||||
|
total,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
@change="handleChangePage"
|
||||||
|
@showSizeChange="onShowSizeChange"
|
||||||
|
>
|
||||||
|
<template slot="buildOptionText" slot-scope="props">
|
||||||
|
<span v-if="props.value !== '100000'">{{ props.value }}{{ $t('itemsPerPage') }}</span>
|
||||||
|
<span v-if="props.value === '100000'">{{ $t('cmdb.ci.all') }}</span>
|
||||||
|
</template>
|
||||||
|
</a-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BatchDownload
|
||||||
|
ref="batchDownload"
|
||||||
|
:showFileTypeSelect="false"
|
||||||
|
@batchDownload="batchDownload"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CIDetailDrawer ref="detail" :typeId="subnetCITypeId" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
import ExcelJS from 'exceljs'
|
||||||
|
import FileSaver from 'file-saver'
|
||||||
|
import { searchCI, deleteCI } from '@/modules/cmdb/api/ci'
|
||||||
|
import { getSubscribeAttributes } from '@/modules/cmdb/api/preference'
|
||||||
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { getCITableColumns } from '@/modules/cmdb/utils/helper'
|
||||||
|
|
||||||
|
import SearchForm from '@/modules/cmdb/components/searchForm/SearchForm.vue'
|
||||||
|
import CITable from '@/modules/cmdb/components/ciTable/index.vue'
|
||||||
|
import BatchDownload from '@/modules/cmdb/components/batchDownload/batchDownload.vue'
|
||||||
|
import CIDetailDrawer from '@/modules/cmdb/views/ci/modules/ciDetailDrawer.vue'
|
||||||
|
import EditAttrsPopover from '@/modules/cmdb/views/ci/modules/editAttrsPopover.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SubnetList',
|
||||||
|
components: {
|
||||||
|
SearchForm,
|
||||||
|
CITable,
|
||||||
|
BatchDownload,
|
||||||
|
CIDetailDrawer,
|
||||||
|
EditAttrsPopover
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
subnetCIType: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
pageSizeOptions: ['50', '100', '200'],
|
||||||
|
loading: false,
|
||||||
|
sortByTable: undefined,
|
||||||
|
|
||||||
|
instanceList: [],
|
||||||
|
totalNumber: 0,
|
||||||
|
columns: [],
|
||||||
|
preferenceAttrList: [],
|
||||||
|
attrList: [],
|
||||||
|
attributes: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
windowHeight: (state) => state.windowHeight,
|
||||||
|
}),
|
||||||
|
tableHeight() {
|
||||||
|
return this.windowHeight - 260
|
||||||
|
},
|
||||||
|
subnetCITypeId() {
|
||||||
|
return this?.subnetCIType?.id || null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
handleSearch: this.getTableData,
|
||||||
|
attrList: () => {
|
||||||
|
return this.attrList
|
||||||
|
},
|
||||||
|
attributes: () => {
|
||||||
|
return this.attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.$nextTick(async () => {
|
||||||
|
if (this.subnetCITypeId) {
|
||||||
|
await this.getAttributeList()
|
||||||
|
await this.getPreferenceAttrList()
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getAttributeList() {
|
||||||
|
await getCITypeAttributesById(this.subnetCITypeId).then((res) => {
|
||||||
|
this.attrList = res.attributes
|
||||||
|
this.attributes = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async getPreferenceAttrList() {
|
||||||
|
const subscribed = await getSubscribeAttributes(this.subnetCITypeId)
|
||||||
|
this.preferenceAttrList = subscribed.attributes
|
||||||
|
},
|
||||||
|
|
||||||
|
async getTableData() {
|
||||||
|
try {
|
||||||
|
this.loading = true
|
||||||
|
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||||||
|
const expression = this.$refs['search'].expression || ''
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
const regSort = /(?<=sort=).+/g
|
||||||
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
|
||||||
|
let sort
|
||||||
|
if (this.sortByTable) {
|
||||||
|
sort = this.sortByTable
|
||||||
|
} else {
|
||||||
|
sort = expression.match(regSort) ? expression.match(regSort)[0] : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await searchCI({
|
||||||
|
q: `_type:${this.subnetCITypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`,
|
||||||
|
count: this.pageSize,
|
||||||
|
page: this.page,
|
||||||
|
sort,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.totalNumber = res?.numfound
|
||||||
|
const instanceList = res.result
|
||||||
|
|
||||||
|
const jsonAttrList = this.preferenceAttrList.filter((attr) => attr.value_type === '6')
|
||||||
|
instanceList.forEach((item) => {
|
||||||
|
jsonAttrList.forEach(
|
||||||
|
(jsonAttr) => (item[jsonAttr.name] = item[jsonAttr.name] ? JSON.stringify(item[jsonAttr.name]) : '')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.getColumns(instanceList)
|
||||||
|
this.instanceList = instanceList
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getColumns(data) {
|
||||||
|
const width = this.$refs.wrapRef.clientWidth - 50
|
||||||
|
const columns = getCITableColumns(data, this.preferenceAttrList, width)
|
||||||
|
columns.forEach((item) => {
|
||||||
|
if (item.editRender) {
|
||||||
|
item.editRender.enabled = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.columns = columns
|
||||||
|
},
|
||||||
|
|
||||||
|
copyExpression() {
|
||||||
|
const expression = this.$refs['search'].expression || ''
|
||||||
|
const fuzzySearch = this.$refs['search'].fuzzySearch
|
||||||
|
|
||||||
|
const regQ = /(?<=q=).+(?=&)|(?<=q=).+$/g
|
||||||
|
|
||||||
|
const exp = expression.match(regQ) ? expression.match(regQ)[0] : null
|
||||||
|
const text = `q=_type:${this.subnetCITypeId}${exp ? `,${exp}` : ''}${fuzzySearch ? `,*${fuzzySearch}*` : ''}`
|
||||||
|
this.$copyText(text)
|
||||||
|
.then(() => {
|
||||||
|
this.$message.success(this.$t('copySuccess'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSearch() {
|
||||||
|
this.$refs.xTable.getVxetableRef().clearSort()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleChangePage(page) {
|
||||||
|
this.page = page
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowSizeChange(_, pageSize) {
|
||||||
|
this.page = 1
|
||||||
|
this.pageSize = pageSize
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSortCol({ property, order }) {
|
||||||
|
let sortByTable
|
||||||
|
if (order === 'asc') {
|
||||||
|
sortByTable = property
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
sortByTable = `-${property}`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sortByTable = sortByTable
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.page = 1
|
||||||
|
this.getTableData()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExport() {
|
||||||
|
this.$refs.batchDownload.open({
|
||||||
|
preferenceAttrList: this.preferenceAttrList,
|
||||||
|
ciTypeName: this.$t('cmdb.ipam.subnetList') || '',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
batchDownload({ checkedKeys, filename }) {
|
||||||
|
const wb = new ExcelJS.Workbook()
|
||||||
|
|
||||||
|
const tableRef = this.$refs.xTable.getVxetableRef()
|
||||||
|
let tableData = _.cloneDeep([
|
||||||
|
...tableRef.getCheckboxReserveRecords(),
|
||||||
|
...tableRef.getCheckboxRecords(true),
|
||||||
|
])
|
||||||
|
if (!tableData.length) {
|
||||||
|
const { fullData } = tableRef.getTableData()
|
||||||
|
tableData = _.cloneDeep(fullData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = wb.addWorksheet(this.tabActive)
|
||||||
|
const columns = []
|
||||||
|
|
||||||
|
const attrMap = new Map()
|
||||||
|
this.columns.filter((col) => checkedKeys.includes(col.field)).map((col) => {
|
||||||
|
attrMap.set(col.field, col)
|
||||||
|
|
||||||
|
columns.push({
|
||||||
|
header: col.title || '',
|
||||||
|
key: col.field,
|
||||||
|
width: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.columns = columns
|
||||||
|
|
||||||
|
tableData.forEach((item) => {
|
||||||
|
const row = {}
|
||||||
|
|
||||||
|
columns.forEach(({ key }) => {
|
||||||
|
const value = item?.[key] ?? null
|
||||||
|
const attr = attrMap.get(key)
|
||||||
|
if (attr.valueType === '6') {
|
||||||
|
row[key] = value ? JSON.stringify(value) : value
|
||||||
|
} else if (attr.is_list && Array.isArray(value)) {
|
||||||
|
row[key] = value.join(',')
|
||||||
|
} else {
|
||||||
|
row[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.addRow(row)
|
||||||
|
})
|
||||||
|
|
||||||
|
wb.xlsx.writeBuffer().then((buffer) => {
|
||||||
|
const file = new Blob([buffer], {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
})
|
||||||
|
FileSaver.saveAs(file, `${filename}.xlsx`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||||
|
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||||
|
},
|
||||||
|
|
||||||
|
openDetail(id, activeTabKey, ciDetailRelationKey) {
|
||||||
|
this.$refs.detail.create(id, activeTabKey, ciDetailRelationKey)
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshAfterEditAttrs() {
|
||||||
|
await this.getPreferenceAttrList()
|
||||||
|
this.getTableData()
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteCI(record) {
|
||||||
|
this.$confirm({
|
||||||
|
title: this.$t('warning'),
|
||||||
|
content: this.$t('confirmDelete'),
|
||||||
|
onOk: () => {
|
||||||
|
deleteCI(record.ci_id || record._id).then(() => {
|
||||||
|
this.$message.success(this.$t('deleteSuccess'))
|
||||||
|
this.getTableData()
|
||||||
|
this.$emit('delete')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-pagination {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -233,6 +233,7 @@ import CollapseTransition from '@/components/CollapseTransition'
|
||||||
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
|
import SubscribeSetting from '../../components/subscribeSetting/subscribeSetting'
|
||||||
import { getCIAdcStatistics } from '../../api/ci'
|
import { getCIAdcStatistics } from '../../api/ci'
|
||||||
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
import { ops_move_icon as OpsMoveIcon } from '@/core/icons'
|
||||||
|
import { SUB_NET_CITYPE_NAME, SCOPE_CITYPE_NAME, ADDRESS_CITYPE_NAME } from '../ipam/constants.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Preference',
|
name: 'Preference',
|
||||||
|
@ -282,8 +283,16 @@ export default {
|
||||||
getPreference2(true, true),
|
getPreference2(true, true),
|
||||||
getCIAdcStatistics(),
|
getCIAdcStatistics(),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const IPAM_CI = [
|
||||||
|
SUB_NET_CITYPE_NAME,
|
||||||
|
SCOPE_CITYPE_NAME,
|
||||||
|
ADDRESS_CITYPE_NAME
|
||||||
|
]
|
||||||
|
|
||||||
ciTypeGroup.forEach((group) => {
|
ciTypeGroup.forEach((group) => {
|
||||||
if (group.ci_types && group.ci_types.length) {
|
if (group.ci_types && group.ci_types.length) {
|
||||||
|
group.ci_types = group.ci_types.filter((type) => !IPAM_CI.includes(type.name))
|
||||||
group.ci_types.forEach((type) => {
|
group.ci_types.forEach((type) => {
|
||||||
const idx = pref.type_ids.findIndex((p) => p === type.id)
|
const idx = pref.type_ids.findIndex((p) => p === type.id)
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
|
@ -304,10 +313,17 @@ export default {
|
||||||
const { self, type_id2users } = pref2
|
const { self, type_id2users } = pref2
|
||||||
this.self = self
|
this.self = self
|
||||||
this.type_id2users = type_id2users
|
this.type_id2users = type_id2users
|
||||||
|
|
||||||
|
const prefGroupTypes = pref.group_types.filter((group) => {
|
||||||
|
group.ci_types = group?.ci_types?.filter((type) => !IPAM_CI.includes(type?.name)) || []
|
||||||
|
return group?.ci_types?.length
|
||||||
|
})
|
||||||
|
const prefTreeTypes = pref?.tree_types?.filter((type) => !IPAM_CI.includes(type?.name)) || []
|
||||||
|
|
||||||
const _myPreferences = [
|
const _myPreferences = [
|
||||||
{
|
{
|
||||||
name: this.$t('cmdb.menu.ciTable'),
|
name: this.$t('cmdb.menu.ciTable'),
|
||||||
groups: pref.group_types,
|
groups: prefGroupTypes,
|
||||||
icon: 'cmdb-ci',
|
icon: 'cmdb-ci',
|
||||||
type: 'ci',
|
type: 'ci',
|
||||||
},
|
},
|
||||||
|
@ -315,7 +331,7 @@ export default {
|
||||||
name: this.$t('cmdb.menu.ciTree'),
|
name: this.$t('cmdb.menu.ciTree'),
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
ci_types: pref.tree_types,
|
ci_types: prefTreeTypes,
|
||||||
name: null,
|
name: null,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -382,12 +398,14 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
resetRoute() {
|
resetRoute() {
|
||||||
resetRouter()
|
|
||||||
const roles = store.getters.roles
|
const roles = store.getters.roles
|
||||||
store.dispatch('GenerateRoutes', { roles }, { root: true }).then(() => {
|
store.dispatch('GenerateRoutes', { roles }, { root: true }).then(() => {
|
||||||
|
resetRouter()
|
||||||
|
this.$nextTick(() => {
|
||||||
router.addRoutes(store.getters.appRoutes)
|
router.addRoutes(store.getters.appRoutes)
|
||||||
this.getCITypes()
|
this.getCITypes()
|
||||||
})
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleSubscribeCIType(ciType) {
|
async handleSubscribeCIType(ciType) {
|
||||||
|
|
|
@ -984,7 +984,28 @@ export default {
|
||||||
this.batchTreeKey = []
|
this.batchTreeKey = []
|
||||||
} else {
|
} else {
|
||||||
const childTypeId = menuKey
|
const childTypeId = menuKey
|
||||||
this.$refs.addTableModal.openModal(firstCIObj, firstCIId, childTypeId, 'children', ancestor_ids)
|
|
||||||
|
let typeName = ''
|
||||||
|
if (this?.relationViews?.id2type?.[childTypeId]) {
|
||||||
|
typeName = this.relationViews.id2type[childTypeId]?.name || ''
|
||||||
|
} else {
|
||||||
|
const node2show_types = this?.relationViews?.views?.[this.viewName]?.node2show_types
|
||||||
|
const typeId = _tempTree?.[1]
|
||||||
|
if (node2show_types?.[typeId]?.length) {
|
||||||
|
const findType = node2show_types[typeId].find((item) => item.id === childTypeId)
|
||||||
|
typeName = findType?.name || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$refs.addTableModal.openModal(
|
||||||
|
firstCIObj,
|
||||||
|
firstCIId,
|
||||||
|
{
|
||||||
|
id: childTypeId,
|
||||||
|
name: typeName
|
||||||
|
},
|
||||||
|
'children',
|
||||||
|
ancestor_ids
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
@refresh="handleSearch"
|
@refresh="handleSearch"
|
||||||
>
|
>
|
||||||
<a-button
|
<a-button
|
||||||
|
v-if="showCreateBtn"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
$refs.createInstanceForm.handleOpen(true, 'create')
|
$refs.createInstanceForm.handleOpen(true, 'create')
|
||||||
|
@ -116,6 +117,7 @@ import { getCITableColumns } from '../../../utils/helper'
|
||||||
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
import SearchForm from '../../../components/searchForm/SearchForm.vue'
|
||||||
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
|
import CreateInstanceForm from '../../ci/modules/CreateInstanceForm.vue'
|
||||||
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
import { getCITypeAttributesById } from '@/modules/cmdb/api/CITypeAttr'
|
||||||
|
import { SUB_NET_CITYPE_NAME, SCOPE_CITYPE_NAME, ADDRESS_CITYPE_NAME } from '@/modules/cmdb/views/ipam/constants.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddTableModal',
|
name: 'AddTableModal',
|
||||||
|
@ -137,6 +139,7 @@ export default {
|
||||||
preferenceAttrList: [],
|
preferenceAttrList: [],
|
||||||
ancestor_ids: undefined,
|
ancestor_ids: undefined,
|
||||||
attrList1: [],
|
attrList1: [],
|
||||||
|
showCreateBtn: true, // 是否展示新增按钮
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -159,18 +162,20 @@ export default {
|
||||||
},
|
},
|
||||||
watch: {},
|
watch: {},
|
||||||
methods: {
|
methods: {
|
||||||
async openModal(ciObj, ciId, addTypeId, type, ancestor_ids = undefined) {
|
async openModal(ciObj, ciId, addType, type, ancestor_ids = undefined) {
|
||||||
console.log(ciObj, ciId, addTypeId, type)
|
console.log(ciObj, ciId, addType, type)
|
||||||
this.visible = true
|
this.visible = true
|
||||||
this.ciObj = ciObj
|
this.ciObj = ciObj
|
||||||
this.ciId = ciId
|
this.ciId = ciId
|
||||||
this.addTypeId = addTypeId
|
this.addTypeId = addType.id
|
||||||
this.type = type
|
this.type = type
|
||||||
this.ancestor_ids = ancestor_ids
|
this.ancestor_ids = ancestor_ids
|
||||||
await getSubscribeAttributes(addTypeId).then((res) => {
|
this.showCreateBtn = ![SUB_NET_CITYPE_NAME, SCOPE_CITYPE_NAME, ADDRESS_CITYPE_NAME].includes(addType.name)
|
||||||
|
|
||||||
|
await getSubscribeAttributes(this.addTypeId).then((res) => {
|
||||||
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
this.preferenceAttrList = res.attributes // 已经订阅的全部列
|
||||||
})
|
})
|
||||||
getCITypeAttributesById(addTypeId).then((res) => {
|
getCITypeAttributesById(this.addTypeId).then((res) => {
|
||||||
this.attrList = res.attributes
|
this.attrList = res.attributes
|
||||||
})
|
})
|
||||||
this.getTableData(true)
|
this.getTableData(true)
|
||||||
|
@ -232,6 +237,7 @@ export default {
|
||||||
this.expression = ''
|
this.expression = ''
|
||||||
this.isFocusExpression = false
|
this.isFocusExpression = false
|
||||||
this.visible = false
|
this.visible = false
|
||||||
|
this.showCreateBtn = true
|
||||||
},
|
},
|
||||||
async handleOk() {
|
async handleOk() {
|
||||||
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
|
const selectRecordsCurrent = this.$refs.xTable.getCheckboxRecords()
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
:column-config="{ resizable: true }"
|
:column-config="{ resizable: true }"
|
||||||
:resizable-config="{ minWidth: 60 }"
|
:resizable-config="{ minWidth: 60 }"
|
||||||
class="checkbox-hover-table"
|
class="checkbox-hover-table"
|
||||||
|
@checkbox-change="onSelectChange"
|
||||||
|
@checkbox-all="onSelectChange"
|
||||||
|
@checkbox-range-end="onSelectChange"
|
||||||
>
|
>
|
||||||
<vxe-table-column
|
<vxe-table-column
|
||||||
v-if="tableData.ciList && tableData.ciList.length"
|
v-if="tableData.ciList && tableData.ciList.length"
|
||||||
|
@ -137,7 +140,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import moment from 'moment'
|
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import ExcelJS from 'exceljs'
|
import ExcelJS from 'exceljs'
|
||||||
import FileSaver from 'file-saver'
|
import FileSaver from 'file-saver'
|
||||||
|
@ -266,8 +268,7 @@ export default {
|
||||||
ciTypeName: this.tabActive || '',
|
ciTypeName: this.tabActive || '',
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
batchDownload({ checkedKeys }) {
|
batchDownload({ checkedKeys, filename }) {
|
||||||
const excel_name = `cmdb-${this.tabActive}-${moment().format('YYYYMMDDHHmmss')}.xlsx`
|
|
||||||
const wb = new ExcelJS.Workbook()
|
const wb = new ExcelJS.Workbook()
|
||||||
|
|
||||||
const tableRef = this.$refs.xTable.getVxetableRef()
|
const tableRef = this.$refs.xTable.getVxetableRef()
|
||||||
|
@ -341,12 +342,16 @@ export default {
|
||||||
const file = new Blob([buffer], {
|
const file = new Blob([buffer], {
|
||||||
type: 'application/octet-stream',
|
type: 'application/octet-stream',
|
||||||
})
|
})
|
||||||
FileSaver.saveAs(file, excel_name)
|
FileSaver.saveAs(file, `${filename}.xlsx`)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
this.$refs.xTable.getVxetableRef().clearCheckboxRow()
|
||||||
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
this.$refs.xTable.getVxetableRef().clearCheckboxReserve()
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onSelectChange() {
|
||||||
|
console.log('onSelectChange')
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue