随着大数据技术的发展和信息安全要求的提高,数据规模的不断扩大给数据运维带来了严峻的挑战。 面对海量数据带来的沉重管理压力,运维人员面临效率瓶颈,人力成本不断上升,单纯依靠扩容运维团队解决问题已不再可行。
由此可见,智能化、高效化、便捷化是运维发展的必然方向。 袋鼠云的检测报告功能就是为了实现这一目标而设计的,并提供优化的解决方案。
什么是检验报告?
检查报告是指对某个系统或设备进行全面检查,并将检查结果和建议整理成报告的过程。 检查报告通常用于评估系统或设备的健康状况和性能,并为发现问题、优化系统、提高效率和降低故障率提供参考。
本文将详细阐述检验报告的功能和实施方案,以期为有此类需求的用户提供实用参考。
自定义布局。
可以拖放报表中的面板以更改布局。
在拖拽过程中,拖拽区域受到限制,只允许拖拽同一个父级,不允许跨目录移动,不允许改变目录的级别,比如将一级目录移动到另一个一级目录,成为二级目录。
目录可以通过收缩来扩展。
可以通过收缩来扩展内容列表,收缩时隐藏所有子面板,展开时显示所有子面板。
移动目录时,子面板会跟随移动。
更改目录后,更新右侧的目录面板。
生成目录号。
右侧的目录树。
生成目录号。
支持锚点滚动。
支持扩展和收缩。
链接到左侧的报告。
数据面板。 根据日期范围获取指标数据。
以图表的形式显示指标信息。
查看详细信息并删除。
每个面板的请求设计都支持刷新请求。
面板导入。 计算目录中选择的面板数。
导入新面板时,无法销毁现有布局,新面板只能跟随旧面板。
导入已有面板时,需要对数据进行对比,如果数据发生变化,则需要再次获取最新数据。
救。 在保存之前,与布局相关的所有操作都是临时的,包括导入面板。 只有点击保存后,才会将当前数据提交到后端保存。
支持 PDF 和 Word 导出。
那么,这套检验报告功能是如何实现的呢? 下面将介绍数据结构设计、组件设计、目录、面板等。
让我们看一下使用扁平结构下的图表:
在扁平化结构中,只需要找到下一行面板即可确定子级,多级目录也是如此,但一级目录需要额外的处理。
扁平化结构实现起来相对简单,但为了满足特定需求,它限制了目录的拖拽。 限制目录需要面板的清晰层次结构,很明显,树结构可以非常恰当地清晰地描述数据层次结构。
它不同于传统的组件编程。 该实现将呈现和数据处理分为两部分:
React 组件:主要负责页面渲染。
class :负责数据的处理。
dashboardmodel
class dashboardmodelpanelmodel
class panelmodel每个仪表盘组件对应一个仪表盘模型,每个面板组件对应一个面板模型。
React 组合基于类实例中的数据进行渲染。 实例一旦投入生产,就不会轻易被销毁或引用地址被更改,从而防止依赖实例数据进行渲染的 react 组件触发更新渲染。
我们需要一种方法,让我们在实例中的数据发生变化后手动触发组件的更新渲染。
组件呈现控件。
由于我们之前使用过 hooks 组件,因此与类组件不同,该组件可以通过调用 forceupdate 方法触发。
在 React18 中,有一个名为 usesyncexternalstore 的新功能,它允许我们订阅外部数据并在数据发生变化时触发组件的渲染。
实际上,usesyncexternalstore 通过在内部维护一个状态来触发组件渲染,当状态值发生变化时,会导致外部组件渲染。
考虑到这一点,我们简单地实现了一个触发组件渲染的 useforceupdate 方法。
export function useforceupdate()虽然实现了 useforceupdate,但在实际使用过程中,需要在组件被销毁时删除该事件。 UseSyncExternalStore 已在内部实现,您可以直接使用它。
usesyncexternalstore(dashboard?.subscribe ??=>要更新面板布局数据,可以使用面板映射准确定位对应的面板,并进一步调用其 UpdateGridPOS 方法进行布局更新操作。
至此,我们才刚刚完成了面板本身数据的更新,还需要执行仪表盘的 sortpanelssbygridpos 方法对所有面板进行排序。
class dashboardmodel else }// ..面板拖动范围。
当前拖拽范围为整个仪表盘,可随意拖拽,绿色为仪表盘的可拖拽区域,灰色为面板。 如下:
如果需要限制,需要将其更改为结构,如下图所示:
在原来的基础上,将目录划分为多个单元,绿色为整体可移动区域,黄色为一级目录块,可在绿色区域拖拽,拖拽时可拖动整个黄色块,紫色为二级目录块,可在当前黄色区域内拖拽, 并且不能与当前黄色块分离,灰色面板只能拖拽到当前目录下。
需要在原有数据结构的基础上进行转换:
class panelmodel面板的进口设计。
后端返回的数据是一棵带有**级别的树,我们得到后,维护成三个map:modulemap、dashboardmap和panelmap。
import from 'react';export interface module export interface dashboard export interface panel type expr = ;export const dashboardcontext = createcontext();当我们渲染一个模块时,我们会遍历 modulemap 并通过模块内的仪表板信息找到辅助目录。
选择辅助目录后,通过辅助目录仪表板的面板找到相关面板,并将其渲染到右侧区域。
对于这 3 个地图的操作,维护在 usehandledata,导出:
面板选择回填。进入面板管理时,我们需要回填选中的面板,可以通过gets**emodel获取当前检测报告的信息,并将对应的选定信息存储在selectpanel中。
现在我们只需要更改选择面板中的值即可选择相应的面板。
面板检查重置。
直接遍历仪表板地图并重置每个 SelectPanels。
dashboardmap.foreach((dashboard) => )面板插入。
选择面板后,插入所选面板时有几种情况:
这次还选择了检测报告的原始面板,插入时会对比数据,如果数据发生变化,需要根据最新的数据源信息进行请求和渲染。
如果这次没有选择检查报告的原始面板,则在插入时需要删除未选择的面板。
插入新选定的面板时,该面板将插入到相应目录的末尾。
添加新面板需要,类似于目录收缩,但需要:
目录收缩仅适用于一个目录,而插入仅适用于整个目录。
目录收缩是直接从子节点向上冒泡,而插入是从根节点开始向下,插入完成后,根据最新的目录数据更新布局。
class dashboardmodel ** 将当前数据与传入数据进行对比,以传入数据为标准,按当前顺序进行修改 * param panels * updatepanels(panels: paneldata)return false; }panelmap.foreach((panel) => )addpanel(paneldata: any) )resetdashboardgridpos(panels: panelmodel = this.panels) else const gridpos = ; panel.updategridpos();sumh += h; }return sumh; }class panelmodel this.restoremodel(panel); if (this.dashboard) this.needrequest &&this.forceupdate();小组请求。
needrequest 控制面板是否需要发出请求,如果为 true,则下次面板渲染时会请求请求,并且请求的处理也放在 PanelModel 中。
import from '../../components/useparams';class panelmodel as params; }request = ()=> ;fetchdata = async (params: params) => ;fetch = async (params: params) => }我们的数据渲染组件一般是深层次的,请求时需要时间间隔等外部参数,使用全局变量来维护这些参数。 上层组件使用 change 来修改参数,数据渲染组件根据抛出的参数发出请求。
export let params: params = ;function useparams() as params; }return ;}export default useparams;面板刷新。
从根节点向下搜索,找到叶节点,并触发相应的请求。
class dashboardmodel );class panelmodel else }rerequest()删除面板。
对于面板的删除,我们只需要在对应的 dashboard 下进行删除,删除后会改变当前的 dashboard 高度,这与下面的目录收缩相同。
class dashboardmodel ** 基于传入面板的过滤器 * 参数面板 要过滤的面板数组 * 返回过滤的面板 * filterpanelsbypanels(panels: panelmodel) 。保存面板。
与后端沟通后,当前检测报告的数据结构由前端维护,最后给后端一个字符串。 获取当前面板数据,并使用 json 进行转换。
从面板获取信息的过程从根节点开始,遍历到叶节点,然后从叶节点开始,逐层返回,这就是回溯的过程。
class dashboardmodel // ..最终保存所需的属性,其他属性不需要 const persistedproperties: = ; class panelmodel ; for (const property in this) model.panels = this.dashboard?.gets**emodel() return model; }// ..面板详细信息显示。
在查看面板时,可以修改时间等,这些操作会影响实例中的数据,因此需要在细节中区分原始数据和数据。
通过重新生成原始面板数据的 panelmodel 实例,对此实例的任何操作都不会影响原始数据。
const model = panel.gets**emodel();const newpanel = new panelmodel();创建一个新实例 seteditpanel(newpanel); 设置为详细信息在DOM上,详情页面是绝对定位的,上面覆盖着检验报告。
目录会缩小和扩展。
维护“目录”面板的折叠属性,以控制面板的隐藏显示。
类 PanelModel 组件呈现当您缩小和展开目录时,您将更改其高度,现在您需要将此更改的高度同步到下一级的仪表板。
下一个级别需要做的是我们如何控制目录。 如下所示,控制第一个辅助目录的收缩:
当面板发生变化时,需要通知父面板进行相应的操作。
添加 top 以获取父实例。
class dashboardmodel this.panels = [.this.panels];顶级仪表板容器没有 top thistop?.changeheight(h); this.forceupdate();// ..class panelmodel ** param h changed height * changeheight(h: number) ) change the height of the own panel thistop.togglepanelheight(this, h);触发父项更改此项forceupdate();// ..组织流程和冒泡类型,一直到顶级仪表板。 展开的宫缩也是如此。
呈现目录的右侧。
锚点序列号。
锚点使用锚点 + id 来选择组件。
序数是按渲染生成的。
渲染使用发布-订阅方法进行管理。
每当仪表盘更改布局时,都需要同步更新目录右侧,任何面板都可能需要触发目录右侧进行更新。
如果我们以在实例中维护相应组件的渲染事件为例,则存在两个问题:
需要做区分,比如刷新面板时,不需要触发右侧目录的渲染。
每个面板如何订阅右侧目录中的呈现事件。
最后,采用发布-订阅者模型来管理事件。
class eventemitter ;** 订阅 * 参数事件 * 参数 fn 订阅事件** 返回 * on(event: string, fn: (=> void) { ** 取消订阅 * 参数事件 订阅事件 * 参数 fn 订阅事件** 返回 * off(event: string, fn: (=> void) { ** publish * 参数事件 订阅事件 * 参数参数 额外参数 * 返回 * emit(event: string, ..)arg: any)eventemitter.emit(this.key);触发面板的订阅事件事件发射器emit(global);触发顶级订阅事件,包括对右侧目录的更新PDF 导出由 html2canvas + jspdf 实现。 需要注意的是,当PDF太长时,内容区域可能会被拆分。 我们需要手动计算面板的高度,是否超过当前文档,如果超过当前文档,我们需要提前划分,添加到下一页,并尽可能地拆分目录面板和数据面板。
Word 导出是通过 html-docx-js 实现的,它需要保留目录的结构,并且可以在面板下添加摘要,这需要我们单独转换每个面板。
实现的思路是按照面板遍历,找到目录面板就是插入带有 h1 和 h2 标签的面板,如果是数据面板,在数据面板中维护一个 ref 属性,这样我们就可以获取当前面板的 dom 信息,按照这个进行转换, 并以 base64 格式使用(word 仅支持 base64 插入)。
当前版本的检查报告仍处于起步阶段,尚未达到最终形式,但随着我们不断迭代和升级,我们将逐步添加包括摘要描述在内的一些功能。
采用当前方法后,如果以后需要调整UI界面,只需要修改相关的UI组件,如添加饼图、**等。 对于数据交互级别的更改,只需转到 dashboardmodel 和 panelmodel 即可进行必要的更新。 此外,我们可以针对特定场景灵活提取特殊类,确保整个迭代过程更加模块化和高效。