This script is an unfinished/stripped down version of a php script I wrote to display Mysql schemas side by side. The idea was to display two DB schemas in a tree view side by side. This is meant to integrate with some perl (or other) scripts that would visually display database replication. This is extremely powerful in two potential places:
1) A monitoring system not unlike nagios. If the nagios check is able to drop runtime data (possibly in XML?) that can be used to present a realtime weather map of your Mysql replication health.
2) An internal web portal that diagrams complex database structures and properly links tables/columns/databases to a well documented wiki page.
When working with large applications I've noticed that the data models become unruly. It becomes difficult to diagnose why a particular data model will not load or why it's not appearing as expected. For example, if your data model relies on 20 different tables to be constructed, it may become difficult to pinpoint where the data anomaly may be. This application can become useful in SOP's for diagnosis. This is of course only as useful as the user makes it.
This relies on a Javascript library called Ortho which used to be found here. I'm no longer sure about where to find this project or who to give credit to, so I will include it in this post. Ortho relies on prototype.
This script is really expensive on the client side in large databases. I wouldn't recommend running this on any antiquated hardware/browsers.
Prototype can be found here.
<html xmlns="http://wwww.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title> ::Database Diagram:: </title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" href="ortho/ortho.css" type="text/css"/>
<script type="text/javascript" src="ortho/prototype.js"></script>
<script type="text/javascript" src="ortho/ortho_core.js"></script>
<script type="text/javascript">
<?php
$edb = "example.com";
$edbuser = "user";
$edbpass = "pass";
$eventdb = "database";
$dbh = mysql_connect($edb, $edbuser, $edbpass) or die(mysql_error());
mysql_select_db($eventdb) or die(mysql_error());
$query = "SHOW TABLES";
$tables = array();
$someshit = mysql_query($query, $dbh) or die(mysql_error());
while ($result = mysql_fetch_array($someshit, MYSQL_ASSOC)) {
foreach($result as $k => $v) {
array_push($tables, $v);
}
}
$total = count($tables);
$ciel = $total * 25;
$divheight = $ciel + 125;
$length = round($total, 10);
echo "function main() {\n";
echo "\tvar panel = new orthoPanel('panel', 0, 0, 450, ".$divheight.");\n";
echo "\tvar vline0 = new orthoLine(panel, 100, 50, 0, 50);\n";
echo "\tvar label0 = new orthoLabel(panel, 100, 50, '<a href=\"#\">$edb</a>', 'center', {cssClass: 'tree-label'});\n";
echo "\n";
echo "\tvar hline1 = new orthoLine(panel, 100, 100, 50, 0);\n";
echo "\tvar vline1 = new orthoLine(panel, 150, 100, 0, $ciel);\n";
echo "\tvar label1 = new orthoLabel(panel, 150, 100, '$eventdb', 'center', {cssClass: 'tree-label'});\n";
$mark = 125;
foreach ($tables as $k => $v) {
echo "\tvar hline".$mark." = new orthoLine(panel, 150, ".$mark.", 50, 0);\n";
echo "\tvar label".$mark." = new orthoLabel(panel, 200, ".$mark.", '".$v."', 'right', {cssClass: 'tree-label'});\n";
$mark = $mark + 25;
}
echo "}\n";
?>
</script>
</head>Here is the Ortho source. If you know where the project has moved please let us know.
// Ortho - A Javascript Graphics library, built on top of the Prototype JavaScript library // Copyright (c) 2007 Robert Jones, Craic Computing LLC <a href="http://www.craic.com<br /> //<br /> //" title="http://www.craic.com<br /> //<br /> //">http://www.craic.com<br /> //<br /> //</a> Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // orthoBase is a set of functions that are used in the main classes var orthoBase = { getParents: function(parent) { // Get the parent and the panel objects that this object // resides in this.parentObject = parent; if(parent != null) { while(parent.orthoClass != 'orthoPanel') { parent = parent.parentObject; } } this.panelObject = parent; this.panelObject.childObjects.push(this); }, setCssClasses: function(options, default_class) { this.cssClassName = (options['cssClass'] == undefined) ? default_class : options['cssClass']; this.element.addClassName(this.cssClassName); // use a default name for highlight - or pick it up from the options this.cssClassNameHighlight = this.cssClassName + '-highlight'; }, // Utility functions to set and get arbitrary metadata (user data) setMetadata: function(proplist) { if(this.metadata == null) { this.metadata = new Hash(); } this.metadata.merge(proplist); }, getMetadata: function(key) { if(this.metadata == null) { return null; } if(key != undefined) { return (this.metadata[key] == undefined) ? null : this.metadata[key]; } else { // return the JSON of the hash for all keys return this.metadata.toJSON(); } }, dumpMetadata: function() { // Dump the entire hash as a JSON string if(this.metadata == null) { return null; } return this.metadata.toJSON(); }, // Utility functions to move an element and set Z index setZIndex: function(zindex) { // if no zindex is specified then place the item at the // front (highest zindex), else place it according to the // supplied integer. If the zindex is the string 'front' then // move it to the front, if 'back' then move it to the back if(zindex == undefined) { this.zindex = this.panelObject.maxZIndex++; } else if(zindex == 'front') { this.zindex = this.panelObject.maxZIndex++; } else if(zindex == 'back') { this.panelObject.minZIndex-- this.zindex = this.panelObject.minZIndex; } else { this.zindex = zindex; if(zindex > this.panelObject.maxZIndex) { this.panelObject.maxZIndex = zindex; } } this.element.setStyle({zIndex: this.zindex}); }, moveRelative: function(x, y) { // move the div by x and y (not to x and y) element = this.element; var xnew = parseInt(element.getStyle('left')) + x; var ynew = parseInt(element.getStyle('top')) + y; element.setStyle({left: xnew + 'px', top: ynew + 'px'}); return element; }, moveAbsolute: function(x, y) { element = this.element; element.setStyle({left: x + 'px', top: y + 'px'}); return element; } } //------------------------------------------------------------ var ortho = Class.create(); Object.extend(ortho, { // This function ensures completed preloading of any // images before calling the user's main() // Default name of that is 'main' but any other can be // passed in the function arg main: null, init: function(usermain) { // Default main function is called 'main' ortho.main = (usermain) ? usermain : main; if(orthoImage.imagesTotal == 0) { // If there are no images then run main() directly ortho.main(); } else { // Preload the images and only run main after they are all loaded orthoImage.images.each(function(img) { orthoImage.preload(img); }); } } }); //------------------------------------------------------------ var orthoPanel = Class.create(); Object.extend(orthoPanel, { count: 0 }); orthoPanel.prototype = { orthoClass: 'orthoPanel', // Array of all child objects childObjects: null, // The highest current Z Index for any element on this panel // Set this to a non-zero value so there is room to push objects // to the back maxZIndex: 100, minZIndex: 100, initialize: function(id, left, top, width, height) { var options = arguments[5] || {}; if(id != null) { // Use a pre-existing div var div = $(id); } else { var div = document.createElement('div'); div = $(div); div.id = this.orthoClass + '_' + orthoPanel.count++; div.setStyle({position: 'absolute', left: left + 'px', top: top + 'px'}); var body = document.getElementsByTagName("body").item(0); body.appendChild(div); } div.setAttribute('name', this.orthoClass); this.element = div; this.cssClassName = (options['cssClass'] == undefined) ? this.orthoClass : options['cssClass']; div.addClassName(this.cssClassName); div.setStyle({width: width + 'px', height: height + 'px'}); this.childObjects = new Array(); }, getObjectFromElement: function(element) { // Given an element in a specific panel, search the objects // on that panel and return the one that 'contains' that element var obj = null; this.childObjects.each( function(child) { if(child.element.id == element.id) { obj = child; return; } }); return obj; } } //------------------------------------------------------------------------ var orthoRectangle = Class.create(); Object.extend(orthoRectangle, { count: 0 }); orthoRectangle.prototype = { orthoClass: 'orthoRectangle', initialize: function(parent, left, top, width, height) { var options = arguments[5] || {}; var div = document.createElement('div'); div = $(div); div.id = this.orthoClass + '_' + orthoRectangle.count++; div.setAttribute('name', this.orthoClass); this.element = div; this.getParents(parent); this.setZIndex(); this.setCssClasses(options, this.orthoClass); div.setStyle({position: 'absolute', visibility: 'hidden', top: top + 'px', left: left + 'px', width: width + 'px', height: height + 'px' }); this.parentObject.element.appendChild(div); var borderWidth = parseInt(div.getStyle('border-left-width')); if(borderWidth == '' || isNaN(borderWidth)) { borderWidth = 0; } div.setStyle({ visibility: 'visible', width: (width - (2*borderWidth)) + 'px', height: (height - (2*borderWidth)) + 'px'}); } } Object.extend(orthoRectangle.prototype, orthoBase); //------------------------------------------------------------------------ var orthoLine = Class.create(); Object.extend(orthoLine, { count: 0 }); orthoLine.prototype = { orthoClass: 'orthoLine', initialize: function(parent, left, top, width, height) { var options = arguments[5] || {}; var linetype = null; if(width > 0 && height == 0) { linetype = 'horizontal'; } else if(width == 0 && height > 0) { linetype = 'vertical'; } else { // error - but default to horizontal linetype = 'horizontal'; } var name = this.orthoClass + linetype.capitalize(); var div = document.createElement('div'); div = $(div); div.id = name + orthoLine.count++; div.setAttribute('name', name); this.element = div; this.getParents(parent); this.setZIndex(); this.setCssClasses(options, name); div.setStyle({position: 'absolute', left: left + 'px', top: top + 'px', width: width + 'px', height: height + 'px'}); parent.element.appendChild(div); } } Object.extend(orthoLine.prototype, orthoBase); //------------------------------------------------------------------------ var orthoLabel = Class.create(); Object.extend(orthoLabel, { count: 0 }); orthoLabel.prototype = { orthoClass: 'orthoLabel', initialize: function(parent, left, top, string, placement) { var options = arguments[5] || {}; var div = document.createElement('div'); div = $(div); div.id = this.orthoClass + '_' + orthoLabel.count++; div.setAttribute('name', this.orthoClass); this.element = div; this.getParents(parent); this.setZIndex(); this.setCssClasses(options, this.orthoClass); div.setStyle({position: 'absolute', visibility: 'hidden', left: left + 'px', top: top + 'px' }); var textNode = document.createTextNode(''); div.appendChild(textNode); parent.element.appendChild(div); // Update the div to add the real string div.update(string); // Handle the image placement - default is bottom-right - like rectangle var labelDims = div.getDimensions(); if(placement == undefined) { placement = 'bottom-right'; } placement = placement.toLowerCase(); var mode = (options['relation'] != undefined) ? 'relative' : 'absolute'; if(mode == 'relative') { // Get dimensions of the containing element var elementDims = parent.element.getDimensions(); var xOffset = Math.floor(elementDims.width/2 - labelDims.width/2); var yOffset = Math.floor(elementDims.height/2 - labelDims.height/2); if(options['relation'] != undefined && options['relation'].match(/inside/i)) { // INSIDE the parent object if(placement != undefined && !placement.match(/center/i)) { // Horizontal placement if(placement.match(/left/i)) { xOffset = 0; } else if(placement.match(/right/i)) { xOffset = elementDims.width - labelDims.width - 1; } // Vertical placement if(placement.match(/top/i)) { yOffset = 0; } else if(placement.match(/bottom/i)) { yOffset = elementDims.height - labelDims.height - 1; } } } else { // OUTSIDE the parent object if(placement != undefined && !placement.match(/center/i)) { // Horizontal placement if(placement.match(/left/i)) { xOffset = - labelDims.width - 1; } else if(placement.match(/right/i)) { xOffset = elementDims.width; } // Vertical placement if(placement.match(/top/i)) { yOffset = - labelDims.height; } else if(placement.match(/bottom/i)) { yOffset = elementDims.height; } } } } else { // Absolute placement - relative to the supplied left and top values var xOffset = left - Math.round(labelDims.width/2); var yOffset = top - Math.round(labelDims.height/2); if(placement != undefined && !placement.match(/center/i)) { // Horizontal placement if(placement.match(/left/i)) { xOffset = left - labelDims.width; } else if(placement.match(/right/i)) { xOffset = left; } // Vertical placement if(placement.match(/top/i)) { yOffset = top - labelDims.height; } else if(placement.match(/bottom/i)) { yOffset = top; } } } div.setStyle({ left: xOffset + 'px', top: yOffset + 'px', visibility: 'visible' }); } } Object.extend(orthoLabel.prototype, orthoBase); //------------------------------------------------------------------------ var orthoImage = Class.create(); Object.extend(orthoImage, { imagesTotal: 0, imagesLoaded: 0, count: 0, src: new Hash(), images: new Array(), loadImages: function() { // Load the images into an array - but don't display them - this // kicks off image preloading for(var i=0; i< arguments.length; i++) { var name = arguments[i].gsub(/^.*\//, ''); orthoImage.src.set(name, arguments[i]); orthoImage.imagesTotal++; var img = document.createElement('img'); img = $(img); img.setAttribute('src', arguments[i]); img.setStyle({visibility: 'hidden', display: 'none' }); orthoImage.images.push(img); } }, preload: function(element) { // Check that an image has finished loading - loop with a delay // until done, and then call ortho.main() if(element.complete) { orthoImage.imagesLoaded++; if(orthoImage.imagesLoaded == orthoImage.imagesTotal) { ortho.main(); } } else { orthoImage.preload.delay(0.25, element); } } }); orthoImage.prototype = { orthoClass: 'orthoImage', initialize: function(parent, left, top, name, placement) { var options = arguments[5] || {}; // Default placement is 'bottom-right' if(placement == undefined) { placement = 'bottom-right'; } placement = placement.toLowerCase(); // Look into using cloneNode() on the original image object... var div = document.createElement('img'); div = $(div); div.id = this.orthoClass + '_' + orthoImage.count++; div.setAttribute('name', this.orthoClass); this.element = div; this.getParents(parent); this.setZIndex(); this.setCssClasses(options, this.orthoClass); div.setAttribute('src', orthoImage.src.get(name)); div.setStyle({position: 'absolute', visibility: 'hidden' }); // To be added... detect if we're trying to display a PNG format image // under Internet Explorer... if so add a IE Filter to the style info // to handle Alpha transparency.... parent.element.appendChild(div); // get image dimensions after display var dims = div.getDimensions(); var imageWidth = dims.width; var imageHeight = dims.height; var xOffset = Math.floor(imageWidth/2); var yOffset = Math.floor(imageHeight/2); // Horizontal placement if(placement.match(/left/i)) { xOffset = imageWidth; } else if(placement.match(/right/i)) { xOffset = 0; } // Vertical placement if(placement.match(/top/i)) { yOffset = imageHeight; } else if(placement.match(/bottom/i)) { yOffset = 0; } div.setStyle({left: (left - xOffset) + 'px', top: (top - yOffset) + 'px', visibility: 'visible' }); } } Object.extend(orthoImage.prototype, orthoBase); //------------------------------------------------------------------------
| Attachment | Size |
|---|---|
| index.php.txt | 2 KB |
| ortho_core.js.txt | 14.29 KB |