With the evolution of big data technology and the improvement of information security requirements, the continuous expansion of data scale has brought severe challenges to data operation and maintenance. In the face of the heavy management pressure caused by massive data, O&M personnel are facing efficiency bottlenecks, and rising labor costs make it no longer feasible to simply rely on expanding the O&M team to solve problems.
It can be seen that intelligence, efficiency and convenience are the inevitable direction of operation and maintenance development. Kangaroo Cloud's inspection report function is designed to meet this goal and provide an optimized solution.
What is an inspection report?
The inspection report refers to the process of conducting a comprehensive inspection of a certain system or equipment, and organizing the inspection results and recommendations into a report. Inspection reports are usually used to evaluate the health status and performance of systems or equipment, and provide references for finding problems, optimizing systems, improving efficiency, and reducing failure rates.
This article will elaborate on the functions and implementation solutions of the inspection report, so as to provide a practical reference for users with such needs.
Custom layouts.
The panels in the report can be dragged and dropped to change the layout.
In the process of dragging, the dragging area is restricted, and only the same parent is allowed to be dragged and dropped, and it is not allowed to move across directories, and it is not allowed to change the level of the directory, such as moving the first-level directory to another first-level directory and becoming a second-level directory.
The table of contents can be expanded by contraction.
The table of contents can be expanded by shrinking, hiding all sub-panels when shrinking, and showing all sub-panels when expanding.
When you move a directory, the sub-panels follow the move.
After changing the directory, update the directory panel on the right side.
Generate catalog numbers.
The directory tree on the right.
Generate catalog numbers.
Anchor scrolling is supported.
Support for expansion and contraction.
Linked to the report on the left.
Data panel. Get metric data based on a date range.
Displays metric information in the form of charts.
View details and delete.
The request design of each panel supports refresh requests.
Panel import. Count the number of panels selected in the directory.
When importing a new panel, you cannot destroy the existing layout, and the new panel can only follow the old panel.
When importing an existing panel, you need to compare the data, and if the data changes, you need to obtain the latest data again.
Save. All operations related to the layout are temporary until saved, including the import panel. Only after clicking Save will the current data be submitted to the backend for saving.
Support for PDF and Word exports.
So, how is this set of inspection report functions realized? The following will introduce the data structure design, component design, table of contents, panels, etc.
Let's take a look at the diagram under Using a Flat Structure:
In a flat structure, you only need to find the next row panel to determine a child, and the same is true for multi-level directories, but additional processing is required for first-level directories.
The flat structure is relatively simple to implement, but in order to meet a specific need, it restricts the dragging and dropping of directories. Restricting directories requires a clear hierarchy of panels, and it is clear that a tree structure can describe a hierarchy of data very appropriately and clearly.
It is different from traditional component programming. The implementation separates rendering and data processing into two parts:
React component: Mainly responsible for page rendering.
class : Responsible for the processing of data.
dashboardmodel
class dashboardmodelpanelmodel
class panelmodelEach dashboard component corresponds to a dashboardmodel, and each panel component corresponds to a panelmodel.
React composition renders based on the data in the class instance. Once an instance is in production, it will not be easily destroyed or the reference address changed, which prevents react components that rely on instance data for rendering to trigger an update rendering.
We need a way for us to manually trigger the update rendering of the component after the data in the instance changes.
Component Rendering Control.
Since we used the hooks component before, unlike the class component, the component can be triggered by calling the forceupdate method.
In React18 there is a new feature called usesyncexternalstore that allows us to subscribe to external data and trigger the rendering of the component if the data changes.
In fact, usesyncexternalstore triggers component rendering by maintaining a state internally, and when the state value is changed, it causes the external component to render.
With this in mind, we simply implemented a useforceupdate method that triggers component rendering.
export function useforceupdate()Although useforceupdate is implemented, in the actual use process, it is necessary to remove the event when the component is destroyed. UseSyncExternalStore has been implemented internally, and you can use it directly.
usesyncexternalstore(dashboard?.subscribe ??=>To update the panel layout data, you can use the panelmap to accurately locate the corresponding panel and further call its UpdateGridPOS method to perform the layout update operation.
At this point, we have only finished updating the data of the panel itself, and we also need to execute the sortpanelssbygridpos method of the dashboard to sort all the panels.
class dashboardmodel else }// ..Panel drag range.
The current drag range is the entire dashboard, which can be dragged at will, green is the draggable area of the dashboard, and gray is the panel. As follows:
If you need to restrict it, you need to change it to the structure as shown below:
On the basis of the original, the directory is divided into units, green is the overall movable area, yellow is the first-level directory block, which can be dragged in the green area, and the entire yellow block can be dragged when dragging, and purple is the second-level directory block, which can be dragged within the current yellow area, and cannot be separated from the current yellow block, and the gray panel can only be dragged under the current directory.
It needs to be transformed on the basis of the original data structure:
class panelmodelImported design of the panel.
The data returned by the backend is a tree with ** level, and after we get it, we maintain it into three maps: modulemap, dashboardmap and panelmap.
import from 'react';export interface module export interface dashboard export interface panel type expr = ;export const dashboardcontext = createcontext();When we render a module, we iterate through the modulemap and find the secondary directory through the dashboards information inside the module.
When the secondary directory is selected, find the relevant panel through the panels of the secondary directory dashboard and render it to the right area.
For the operation of these 3 maps, the maintenance is in usehandledata, export:
panel selects Backfill.When entering the panel management, we need to backfill the selected panels, and we can get the information of the current inspection report through gets**emodel, and store the corresponding selected information in the selectpanels.
Now we only need to change the value in the selectpanels to make the corresponding panel selected.
panel checks Reset.
Directly iterate through the dashboardmap and reset each SelectPanels.
dashboardmap.foreach((dashboard) => )Panel insertion.
After we have selected the panel, there are several cases when inserting the selected panel:
The original panel of the inspection report is also selected this time, and the data will be compared when inserted, and if the data changes, it needs to be requested and rendered according to the latest data source information.
If the original panel of the inspection report is not selected this time, you need to delete the unselected panel when inserting it.
When inserting a newly selected panel, it is inserted at the end of the corresponding directory.
Adding a new panel requires, similar to the catalog shrink, except that:
Directory shrinkage is for only one directory, while insertion is for the whole.
Directory shrinkage is to bubble up directly from the child node, while insertion is to start from the root node and downwards, and after the insertion is completed, the layout is updated according to the latest directory data.
class dashboardmodel ** Compare the current with the incoming data, take the incoming data as the standard, and modify it in the current order * 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();Panel requests.
needrequest controls whether the panel needs to make a request, if it is true, the next time the panel renders, the request will be requested, and the processing of the request is also placed in the panelmodel.
import from '../../components/useparams';class panelmodel as params; }request = ()=> ;fetchdata = async (params: params) => ;fetch = async (params: params) => }Our data rendering component is generally at a deep level, and external parameters such as time intervals are required when requesting, and global variables are used to maintain these parameters. The upper-level component uses change to modify the parameters, and the data rendering component makes requests based on the thrown params.
export let params: params = ;function useparams() as params; }return ;}export default useparams;Panel refresh.
Search down from the root node, find the leaf node, and trigger the corresponding request.
class dashboardmodel );class panelmodel else }rerequest()Deletion of panels.
For the deletion of the panel, we only need to remove it under the corresponding dashboard, and the current dashboard height will be changed after the deletion, which is the same as the directory shrinkage below.
class dashboardmodel ** filter based on incoming panels * param panels array of panels to be filtered * returns filtered panels * filterpanelsbypanels(panels: panelmodel) .Saving of the panel.
After communicating with the backend, the data structure of the current inspection report is maintained by the front-end, and finally a string is given to the back-end. Get the current panel data and convert it with json.
The process of obtaining information from the panel starts from the root node, traverses to the leaf node, and then starts from the leaf node, and returns layer by layer, which is the process of backtracking.
class dashboardmodel // ..The properties that are required for the final save, and the others do not need const persistedproperties: = ; class panelmodel ; for (const property in this) model.panels = this.dashboard?.gets**emodel() return model; }// ..Panel details display.
When viewing a panel, you can modify the time, etc., and these operations affect the data in the instance, so you need to distinguish the original data from the data in the details.
By regenerating a panelmodel instance of the original panel data, any operation on this instance will not affect the original data.
const model = panel.gets**emodel();const newpanel = new panelmodel();Create a new instance seteditpanel(newpanel); Set to DetailsOn the DOM, the detail page is absolutely positioned, overlaid with the inspection report.
The catalog shrinks and expands.
Maintain a collapsed property for the Catalog panel to control the hidden display of the panel.
class panelmodel component renderingWhen you shrink and expand the directory, you will change its height, and now you need to synchronize this changed height to the dashboard of the next level.
What the next level needs to do is something like how we control the directory. As follows, control the contraction of the first secondary directory:
When a panel changes, you need to notify the parent panel to perform corresponding operations.
Add a top to get the parent instance.
class dashboardmodel this.panels = [.this.panels];The top-level dashboard container doesn't have a top thistop?.changeheight(h); this.forceupdate();// ..class panelmodel ** param h changed height * changeheight(h: number) ) change the height of its own panel thistop.togglepanelheight(this, h);Trigger the parent change thisforceupdate();// ..Organize processes and bubbling types, all the way to the top-of-the-line dashboard. The same goes for unfolding contractions.
The right side of the directory is rendered.
Anchor serial number.
The anchor point uses anchor + id to select the component.
Ordinal numbers are generated on a per-render basis.
Renders are managed with a publish-subscribe approach.
Whenever the dashboard changes the layout, the right side of the directory needs to be updated synchronously, and any panel may need to trigger the right side of the table of contents to update.
If we take the rendering event that maintains the corresponding component within the instance, there are two problems:
A distinction needs to be made, such as when refreshing a panel, you don't need to trigger the rendering of the directory on the right.
How each panel subscribes to render events in the directory on the right.
Finally, the publish-subscriber model was adopted to manage events.
class eventemitter ;** Subscribe * param event * param fn subscribe to event** returns * on(event: string, fn: (=> void) { ** unsubscribe * param event subscribe to event * param fn subscribe to event** returns * off(event: string, fn: (=> void) { ** publish * param event subscribe to event * param arg extra parameter * returns * emit(event: string, ..)arg: any)eventemitter.emit(this.key);Triggers the panel's subscription event eventemitteremit(global);Triggers a top-level subscription event, including updates to the directory on the rightPDF export is implemented by html2canvas + jspdf. It should be noted that when a PDF is too long, it is possible that the content area will be split. We need to manually calculate the height of the panel, whether it exceeds the current document, if it exceeds the current document, we need to divide it in advance, add it to the next page, and split the catalog panel and the data panel as much as possible.
Word export is implemented by html-docx-js, which needs to preserve the structure of the table of contents and can add summaries under the panel, which requires us to convert each panel separately.
The idea of implementation is to traverse according to panels, and to find the directory panel is to insert it with h1 and h2 tags, if it is a data panel, maintain a ref attribute in the data panel, which allows us to get the dom information of the current panel, convert it according to this, and use it in base64 format (word only supports base64 insertion).
The current version of the inspection report is still in its infancy and is not in its final form, but we will gradually add a number of features including summary descriptions as we continue to iterate and upgrade.
After the current method is adopted, if you need to adjust the UI interface in the future, you only need to modify the relevant UI components, such as adding pie charts, **, etc. For changes at the data interaction level, you only need to go to the dashboardmodel and panelmodel to make the necessary updates. In addition, we can flexibly extract special classes for specific scenarios to ensure that the entire iteration process is more modular and efficient.