Difference between revisions of "Widget:SpaceAPI"

From Hackerspace ACKspace
Jump to: navigation, search
m (array fix)
m (Try and fix SpaceAPI popup alignment)
 
(44 intermediate revisions by the same user not shown)
Line 10: Line 10:
 
|url=/spaceAPI/
 
|url=/spaceAPI/
 
|width=260px
 
|width=260px
|height=20px
+
|height=300px
 
|padding=8px
 
|padding=8px
 
|interval=20
 
|interval=20
 
|float=right
 
|float=right
|features=beacon
+
|features=beacon,annex
 
}}</nowiki>
 
}}</nowiki>
  
 
This will give the following result:<br/>
 
This will give the following result:<br/>
 
{{#widget:{{PAGENAME}}
 
{{#widget:{{PAGENAME}}
|url=/spaceAPI/
+
|url=/spaceAPI/?beacon_log=HoT
 
|width=260px
 
|width=260px
|height=20px
+
|height=300px
 
|padding=8px
 
|padding=8px
 
|interval=20
 
|interval=20
 
|float=right
 
|float=right
|features=beacon
+
|features=beacon,annex
 
}}<br/>
 
}}<br/>
 
'''Notes'''
 
'''Notes'''
Line 82: Line 82:
 
SpaceAPI.prototype._colorUnknown  = "#f70";
 
SpaceAPI.prototype._colorUnknown  = "#f70";
 
SpaceAPI.prototype._debug   = null;
 
SpaceAPI.prototype._debug   = null;
 +
 +
SpaceAPI.prototype._drawBeaconPolyLine = function( )
 +
{
 +
if ( !this.data.sensors || !this.data.sensors.beacon || this.data.sensors.beacon.length < 50 )
 +
return;
 +
 +
var points = [];
 +
var bounds = this._leaflet.map.getBounds();
 +
 +
// Only add points within the bounds
 +
if ( this._leaflet.beaconCount !== this.data.sensors.beacon.length )
 +
{
 +
this._leaflet.beaconCount = this.data.sensors.beacon.length;
 +
for ( var n = 0, len = this.data.sensors.beacon.length; n < len; ++n )
 +
{
 +
var point = L.latLng( this.data.sensors.beacon[n].location.lat, this.data.sensors.beacon[n].location.lon );
 +
points.push( point );
 +
}
 +
 +
if ( !this._leaflet.beaconline )
 +
this._leaflet.beaconline = L.polyline(points, {color: 'green'}).addTo( this._leaflet.map );
 +
else
 +
this._leaflet.beaconline.setLatLngs( points );
 +
}
 +
};
  
 
SpaceAPI.prototype.start = function( )
 
SpaceAPI.prototype.start = function( )
Line 101: Line 126:
 
}
 
}
 
node.style.width = this._width;
 
node.style.width = this._width;
 +
node.style.height = this._height;
 +
 
node.style.textAlign = "center";
 
node.style.textAlign = "center";
 
node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)";
 
node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)";
Line 111: Line 138:
 
this._node.style.display = "block";
 
this._node.style.display = "block";
 
this._node.style.color = "inherit";
 
this._node.style.color = "inherit";
this._node.style.height = this._height + "px";
 
 
this._node.style.padding = this._padding;
 
this._node.style.padding = this._padding;
 
this._node.textContent = this._msgLoading;
 
this._node.textContent = this._msgLoading;
Line 121: Line 147:
 
var mapNode = node.appendChild( document.createElement( "div" ) );
 
var mapNode = node.appendChild( document.createElement( "div" ) );
 
mapNode.style.width = "100%";
 
mapNode.style.width = "100%";
mapNode.style.height = "276px";
+
mapNode.style.height = "calc(" + this._height + " + " + this._padding + " - 2em)";
 
 
 
srcNode = document.createElement( "link" );
 
srcNode = document.createElement( "link" );
Line 138: Line 164:
 
this._leaflet = {};
 
this._leaflet = {};
 
this._leaflet.point = L.latLng( 50.8925,5.9713 );
 
this._leaflet.point = L.latLng( 50.8925,5.9713 );
this._leaflet.map = L.map( mapNode ).setView( this._leaflet.point, 16);
+
this._leaflet.map = L.map( mapNode ).panTo( this._leaflet.point );
 +
//this._leaflet.map = L.map( mapNode ).setView( this._leaflet.point, 16);
  
 
L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857,
 
L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857,
Line 276: Line 303:
 
maxZoom: 28
 
maxZoom: 28
 
} ).addTo( this._leaflet.map );
 
} ).addTo( this._leaflet.map );
 +
 +
this._leaflet.map.on('moveend', function( _event )
 +
{
 +
//this._drawBeaconPolyLine( _event );
 +
 +
}, this );
 
 
 
this._leaflet.map.on('zoomend', function( _event )
 
this._leaflet.map.on('zoomend', function( _event )
Line 293: Line 326:
 
*/
 
*/
  
if ( !this._leaflet.temperatures["28151767050000a0"] )
+
 
 +
//this._drawBeaconPolyLine( _event );
 +
 
 +
 
 +
if ( !this._leaflet.temperatures["000005667ABD"] )
 
return;
 
return;
  
Line 299: Line 336:
 
if ( z > 17 )
 
if ( z > 17 )
 
o = Math.max( (23-z) / 5, 0 );
 
o = Math.max( (23-z) / 5, 0 );
this._leaflet.temperatures["28151767050000a0"].setStyle( { fillOpacity: o } );
+
this._leaflet.temperatures["000005667ABD"].setStyle( { fillOpacity: o } );
 
}, this );
 
}, this );
  
Line 359: Line 396:
 
bounds.extend( this._leaflet.point );
 
bounds.extend( this._leaflet.point );
  
this._leaflet.map.fitBounds( bounds );
+
// Depending on polyline: don't zoom
if ( this._leaflet.map.getZoom() > 18 )
+
//this._leaflet.map.fitBounds( bounds );
this._leaflet.map.setZoom( 18 );
+
//if ( this._leaflet.map.getZoom() > 18 )
 +
//this._leaflet.map.setZoom( 18 );
 
}
 
}
 
}.bind( this ) );
 
}.bind( this ) );
Line 379: Line 417:
 
popupAnchor: [0, -26],
 
popupAnchor: [0, -26],
 
shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.shadow.png',
 
shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.shadow.png',
 +
shadowSize: [59, 32],
 +
shadowAnchor: [16, 26]
 +
} ),
 +
"HoT" : L.icon( {
 +
iconUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.png',
 +
iconSize: [32, 32],
 +
iconAnchor: [16, 26],
 +
popupAnchor: [0, -26],
 +
shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.shadow.png',
 
shadowSize: [59, 32],
 
shadowSize: [59, 32],
 
shadowAnchor: [16, 26]
 
shadowAnchor: [16, 26]
Line 384: Line 431:
 
};
 
};
 
this._leaflet.descriptions = {
 
this._leaflet.descriptions = {
"HoaB" : "Hackers on a Bike"
+
"HoaB" : "Hackers on a Bike",
 +
"HoT" : "Hackers on Tour"
 
};
 
};
  
Line 394: Line 442:
 
this._leaflet.temperatures = {
 
this._leaflet.temperatures = {
 
// outside
 
// outside
"28bd7a660500002f" : L.polygon([[50.892537811238,5.9711101056338], [50.892517508729,5.9710966945888], [50.892534427487,5.9710564614536], [50.892715457818,5.9712093473674], [50.892696847256,5.9712790848018], [50.892808510518,5.9713836909534], [50.892788208127,5.9714373351337], [50.892774673195,5.9714319707157], [50.892681620427,5.971697509408], [50.892641015525,5.9716653228998], [50.892559805614,5.971893310666], [50.892569956861,5.9719120861291], [50.892559805614,5.9719496370554], [50.892377082796,5.9717940689325], [50.892399077248,5.9717189670801], [50.89233140198,5.9716545940637], [50.892350012689,5.9716009498835], [50.892326326331,5.9715794922114], [50.892414304169,5.9713032246828], [50.892458293026,5.971340775609]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),
+
"000005671715" : L.polygon([[50.892537811238,5.9711101056338], [50.892517508729,5.9710966945888], [50.892534427487,5.9710564614536], [50.892715457818,5.9712093473674], [50.892696847256,5.9712790848018], [50.892808510518,5.9713836909534], [50.892788208127,5.9714373351337], [50.892774673195,5.9714319707157], [50.892681620427,5.971697509408], [50.892641015525,5.9716653228998], [50.892559805614,5.971893310666], [50.892569956861,5.9719120861291], [50.892559805614,5.9719496370554], [50.892377082796,5.9717940689325], [50.892399077248,5.9717189670801], [50.89233140198,5.9716545940637], [50.892350012689,5.9716009498835], [50.892326326331,5.9715794922114], [50.892414304169,5.9713032246828], [50.892458293026,5.971340775609]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),
 
// cold zone
 
// cold zone
"28151767050000a0" : L.polygon( [[50.892458293019,5.971340775608], [50.892410920402,5.9713032246818], [50.8924743319376, 5.9711186739150435], [50.89251872057557, 5.97115655487869]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),
+
"000005667ABD" : L.polygon( [[50.892458293019,5.971340775608], [50.892410920402,5.9713032246818], [50.8924743319376, 5.9711186739150435], [50.89251872057557, 5.97115655487869]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),
 
// barbecue
 
// barbecue
"DEADBEEF0" : L.circle( [ 50.89277, 5.97134 ], 1, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map )
+
"DEADBEEF0" : L.circle( [ 50.89277, 5.97134 ], 1, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),
 
// hot zone
 
// hot zone
/*"288a13670500002a" : null*/
+
"00000567138A" : L.circle( [ 50.89250, 5.9711867 ], 2, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map )
 
};
 
};
  
var linesRed = [
+
var linesRed = [[
[50.892514028030334, 5.971161649524675], [50.89244602602161, 5.971209481358529]],[
+
[50.892514028030334, 5.971161649524675], [50.89251319586179, 5.971160894608546]],[
[50.89251114428003, 5.9711696663976], [50.89244602602161, 5.971209481358529]],[
+
[50.89251114428003, 5.9711696663976], [50.89251024343285, 5.9711689967662105]],[
[50.8924980950481, 5.971208224073053], [50.89244602602161, 5.971209481358529]],[
+
[50.8924980950481, 5.971208224073053], [50.892496785531904, 5.971207094194142]],[
[50.892495831897186, 5.971215394539513], [50.89244602602161, 5.971209481358529]],[
+
[50.892495831897186, 5.971215394539513], [50.89249461680935, 5.971214339470522]],[
[50.892482398287335, 5.971254612622375], [50.89244602602161, 5.971209481358529]],[
+
[50.892482398287335, 5.971254612622375], [50.89248148152484, 5.971253695897759]],[
[50.89248006555828, 5.971261391486564], [50.89244602602161, 5.971209481358529]],[
+
[50.89248006555828, 5.971261391486564], [50.8924792609356, 5.971260443329812]],[
[50.89246652683481, 5.971301496043679], [50.89244602602161, 5.971209481358529]],[
+
[50.89246652683481, 5.971301496043679], [50.8924656730421, 5.971300341188908]],[
[50.892464393586884, 5.971307247454547], [50.89244602602161, 5.971209481358529]
+
[50.892464393586884, 5.971307247454547], [50.89246356324314, 5.971306176293467]]
 
];
 
];
  
var linesBlue = [
+
var linesBlue = [[
[50.892505941019564, 5.971185087508389], [50.89244602602161, 5.971209481358529]],[
+
[50.892505941019564, 5.971185087508389], [50.89250524711004, 5.971184503287078]],[
[50.892503858003316, 5.971191544085743], [50.89244602602161, 5.971209481358529]],[
+
[50.892503858003316, 5.971191544085743], [50.892503132264245, 5.9711907897144565]],[
[50.89249048160161, 5.971230519935489], [50.89244602602161, 5.971209481358529]],[
+
[50.89249048160161, 5.971230519935489], [50.89248932268764, 5.971229581079457]],[
[50.89248770274671, 5.971239335350448], [50.89244602602161, 5.971209481358529]],[
+
[50.89248770274671, 5.971239335350448], [50.89248661219504, 5.97123836979705]],[
[50.89247468364275, 5.9712776963039005], [50.89244602602161, 5.971209481358529]],[
+
[50.89247468364275, 5.9712776963039005], [50.892473913492424, 5.97127688527138]],[
[50.892472469662096, 5.971284294786302], [50.89244602602161, 5.971209481358529]],[
+
[50.892472469662096, 5.971284294786302], [50.89247146830519, 5.971283237998365]],[
[50.89245897037926, 5.971323475241662], [50.89244602602161, 5.971209481358529]],[
+
[50.89245897037926, 5.971323475241662], [50.89245824463948, 5.9713221760466695]],[
[50.89245672874569, 5.971331032450281], [50.89244602602161, 5.971209481358529]
+
[50.89245672874569, 5.971331032450281], [50.89245581256435, 5.971329635940493]]
 
];
 
];
  
Line 434: Line 482:
 
// Load the script
 
// Load the script
 
( document.head || document.documentElement ).appendChild( srcNode );
 
( document.head || document.documentElement ).appendChild( srcNode );
}
+
} // beacon
  
 
// Update the space state immediately
 
// Update the space state immediately
Line 614: Line 662:
 
}, this );
 
}, this );
 
}
 
}
 +
 +
var bounds = null;
 +
var maxZoom = 18;
  
 
// Handle beacons
 
// Handle beacons
if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length )
+
if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length < 25 )
 
{
 
{
 
var bHoaB = false;
 
var bHoaB = false;
Line 630: Line 681:
  
 
// Closure variable
 
// Closure variable
if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" ) )
+
if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" || _apiBeacon.name === "HoT" ) )
 
bHoaB = true;
 
bHoaB = true;
  
Line 671: Line 722:
 
}
 
}
 
}
 
}
 +
else if (this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length)
 +
{
 +
var apiBeacon = this.data.sensors.beacon[ 0 ];
 +
var beacon = {};
 +
var bHoaB = false;
 +
 +
beacon.point  = L.latLng( apiBeacon.location.lat,apiBeacon.location.lon );
 +
beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map );
 +
beacon.circle = L.circle( beacon.point, apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map );
 +
 +
var delta = (Date.now() - new Date( apiBeacon.ext_lastchange * 1000 )) / 1000;
 +
 +
// Closure variable
 +
if ( ( delta < 3600 ) && ( apiBeacon.name === "HoaB" || apiBeacon.name === "HoT" ) )
 +
bHoaB = true;
 +
 +
if ( this._leaflet.icons[ apiBeacon.name ] )
 +
this._leaflet.icons[ apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";
 +
 +
var popup = beacon.marker.getPopup();
 +
if ( !popup )
 +
popup = beacon.marker.bindPopup().getPopup();
 +
 +
popup.setContent( ( this._leaflet.descriptions[ apiBeacon.name ] || apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );
 +
 +
// TODO: clean up old beacons!!
 +
this._leaflet.beacons.forEach( function( _beacon )
 +
{
 +
// Destroy popup
 +
_beacon.marker.unbindPopup( );
 +
 +
// Remove marker and circle
 +
this._leaflet.map.removeLayer( _beacon.marker );
 +
this._leaflet.map.removeLayer( _beacon.circle );
 +
}, this );
 +
this._leaflet.beacons = [ beacon ];
  
 +
if ( bHoaB && this._leaflet.follow === null )
 +
{
 +
this._leaflet.follow = true;
 +
document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle";
 +
}
 +
 +
this._drawBeaconPolyLine( );
 +
 +
maxZoom = 10;
 +
}
  
 
// TODO: Update if coordinate is incorrect
 
// TODO: Update if coordinate is incorrect
Line 681: Line 778:
 
this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon );
 
this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon );
 
this._leaflet.marker.setLatLng( this._leaflet.point );
 
this._leaflet.marker.setLatLng( this._leaflet.point );
 +
this._leaflet.map.setZoom( 18 );
 +
  
 
// Set popup data and open it
 
// Set popup data and open it
Line 687: Line 786:
 
popup = this._leaflet.marker.bindPopup().getPopup();
 
popup = this._leaflet.marker.bindPopup().getPopup();
  
var info = "<img src='" + this.data.logo + "'><br/>";
+
var info = '<img src="' + this.data.logo + '" style="max-width:'+this._width+'px;width:100%"><br/>';
 
var l = this.data.location, s = this.data.spacefed;
 
var l = this.data.location, s = this.data.spacefed;
 
info += l.address+"<br/>";
 
info += l.address+"<br/>";
Line 695: Line 794:
 
if ( l.ext_room )
 
if ( l.ext_room )
 
info += ", room " + l.ext_room;
 
info += ", room " + l.ext_room;
info += "<br/>" + (s.spacenet ? "&#x2714;" : "&#x274C;") + ' <a target="blank" href="Spacenet">spacenet</a>';
+
info += "<br/>" + '<a target="blank" href="tel:+'+this.data.contact.phone+'">+'+this.data.contact.phone+'</a>';
info += "<br/>" + (s.ext_spacenet5g ? "&#x2714;" : "&#x274C;") + " spacenet (5GHz)";
+
info += "<br/>" + this._tristate( s.spacenet ) + ' <a target="blank" href="Spacenet">spacenet</a>';
info += "<br/>" + (s.spacesaml ? "&#x2714;" : "&#x274C;") + " spacesaml";
+
info += "<br/>" + this._tristate( s.ext_spacenet5g ) + " spacenet (5GHz)";
info += "<br/>" + (s.ext_spaceconnect ? "&#x2714;" : "&#x274C;") + " spaceconnect";
+
info += "<br/>" + this._tristate( s.spacesaml ) + " spacesaml";
info += "<br/>" + (s.spacephone ? "&#x2714;" : "&#x274C;") + ' <a target="blank" href="Spacephone">spacephone</a>';
+
info += "<br/>" + this._tristate( s.ext_spaceconnect ) + " spaceconnect";
 +
info += "<br/>" + this._tristate( s.spacephone ) + ' <a target="blank" href="Spacephone">spacephone</a>';
 
if ( s.ext_spacephone_extension )
 
if ( s.ext_spacephone_extension )
info += ": +" + s.ext_spacephone_extension;
+
info += ": E" + s.ext_spacephone_extension;
  
 
popup.setContent( info );
 
popup.setContent( info );
Line 712: Line 812:
 
{
 
{
 
// Determine the bounding box to 'follow
 
// Determine the bounding box to 'follow
var bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )
+
bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )
 
{
 
{
 
return _beacon.point;
 
return _beacon.point;
Line 721: Line 821:
 
bounds.extend( this._leaflet.point );
 
bounds.extend( this._leaflet.point );
  
this._leaflet.map.fitBounds( bounds );
+
// Disable zoom (in) if already zoomed beyond set max
if ( this._leaflet.map.getZoom() > 18 )
+
if ( this._leaflet.map.getZoom() > maxZoom )
this._leaflet.map.setZoom( 18 );
+
{
 +
this._leaflet.map.panTo( bounds.getCenter() );
 +
}
 +
else
 +
{
 +
this._leaflet.map.fitBounds( bounds );
 +
if ( this._leaflet.map.getZoom() > maxZoom )
 +
this._leaflet.map.setZoom( maxZoom );
 +
}
 
}
 
}
 +
} // leaflet
 +
 +
if ( this._features.split(",").indexOf( "annex" ) >= 0 )
 +
{
 +
var node = this._node;
 +
[].forEach.call(document.querySelectorAll(".state.annex"),function(_n)
 +
{
 +
node.removeChild( _n );
 +
});
 +
 +
(this.data.sensors&&this.data.sensors.service||[]).filter(function(_s)
 +
{
 +
return (_s.source==="annex" && _s.name!=="annex");
 +
}).forEach(function(_d,_i)
 +
{
 +
var anode = node.appendChild( document.createElement( "div" ) );
 +
anode.style.position = "absolute";
 +
anode.style.width = "0.4em";
 +
anode.style.height = "0.4em";
 +
anode.style.top = "calc(2em + 10px)";
 +
anode.style.right = (0.5 * _i) + "em";
 +
anode.style.backgroundColor = (_d.status|0)?"green":"red";
 +
anode.style.zIndex = 500;
 +
anode.className = "state annex";
 +
var t = new Date( 0 );
 +
t.setUTCSeconds( _d.ext_lastchange );
 +
anode.title = _d.name + " (updated " + t.toLocaleString( )+")";
 +
});
 
}
 
}
 
};
 
};
 +
 +
SpaceAPI.prototype._tristate = function( _state )
 +
{
 +
if ( _state )
 +
return "&#x2705;";
 +
else if ( _state === null )
 +
return "&#x2753;";
 +
else
 +
return "&#x274C;";
 +
}
  
 
SpaceAPI.prototype._xhr_onerror = function( )
 
SpaceAPI.prototype._xhr_onerror = function( )
Line 736: Line 882:
 
var state;
 
var state;
 
//state = new SpaceAPI( "auto", "auto", "none", "8px-->", "//ackspace.nl/spaceAPI/", 15, "beacon" );
 
//state = new SpaceAPI( "auto", "auto", "none", "8px-->", "//ackspace.nl/spaceAPI/", 15, "beacon" );
state = new SpaceAPI( "<!--{$width|escape:html|default:auto}-->", "<!--{$height|escape:html|default:auto}-->", "<!--{$float|escape:html|default:none}-->", "<!--{$padding|escape:html|default:8px}-->", "<!--{$url|escape:urlpathinfo}-->", <!--{$interval|validate:int|default:0}-->, "<!--{$features|escape:'quotes'}-->" );
+
state = new SpaceAPI( "<!--{$width|escape:html|default:auto}-->", "<!--{$height|escape:html|default:auto}-->", "<!--{$float|escape:html|default:none}-->", "<!--{$padding|escape:html|default:8px}-->", "<!--{$url}-->", <!--{$interval|validate:int|default:0}-->, "<!--{$features|escape:'quotes'}-->" );
 
state.start();
 
state.start();
  

Latest revision as of 16:51, 24 April 2023

This widget allows you to display the Space API data (provided as JSON)

Created by Xopr

Using this widget

To insert this widget, use the following code:

{{#widget:SpaceAPI
|url=/spaceAPI/
|width=260px
|height=300px
|padding=8px
|interval=20
|float=right
|features=beacon,annex
}}

This will give the following result:

Notes

  • url is mandatory, the rest is optional (leave out interval to make the data static).
    it also must be written without protocol since colon (:) is not allowed, and may be relative, for example: //ackspace.nl/spaceAPI/ or /spaceAPI/
  • You must provide a unit for the sizes (i.e. px, %, etc.)

Copy to your site

To use this widget on your site, just install MediaWiki Widgets extension and copy full source code of this page to your wiki as Widget:SpaceAPI article.