http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/controllers/validation-controller.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/controllers/validation-controller.js b/jena-fuseki2/src/main/webapp/js/app/controllers/validation-controller.js new file mode 100644 index 0000000..43ed8ce --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/controllers/validation-controller.js @@ -0,0 +1,38 @@ +/** Controller for the main index.html page */ +define( + function( require ) { + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + ValidationOptions = require( "app/views/validation-options" ), + ValidationService = require( "app/services/validation-service" ); + + var ValidationController = function() { + this.initServices(); + this.initEvents(); + }; + + // add the behaviours defined on the controller + _.extend( ValidationController.prototype, { + initEvents: function() { + fui.vent.on( "models.validation-options.ready", this.onValidationOptionsModelReady ); + $(".validation").on( "click", "a.perform-validation", function( event ) { + fui.services.validation.performValidation( fui.views.validationOptions.model ); + } ); + }, + + onValidationOptionsModelReady: function( e ) { + fui.views.validationOptions = new ValidationOptions( {model: fui.models.validationOptions} ); + }, + + initServices: function() { + fui.services.validation = new ValidationService( "#query-edit-cm", "#validation-output-cm" ); + fui.services.validation.init(); + } + + } ); + + return ValidationController; + } +);
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/fui.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/fui.js b/jena-fuseki2/src/main/webapp/js/app/fui.js new file mode 100644 index 0000000..e8bc41d --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/fui.js @@ -0,0 +1,33 @@ +/** + * Top-level application code module for Fuseki UI + */ + +define( ['require', 'backbone', 'marionette'], + function( require, Backbone, Marionette ) { + // define the application object, and add it to the global namespace + var fui = new Marionette.Application(); + + // define some Marionette modules, because they have a lifecycle component + // see https://github.com/marionettejs/backbone.marionette/wiki/AMD-Modules-vs-Marionette%27s-Modules + fui.module( "models" ); + fui.module( "views" ); + fui.module( "layouts" ); + fui.module( "controllers" ); + fui.module( "services" ); + + // define top-level regions where our layouts will go + fui.addRegions({ + }); + + fui.on('initialize:before', function( options ) { + }); + + fui.on('initialize:after', function( options ) { + // Backbone.history.start(); + this.initialized = true; + }); + + + return fui; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/layouts/.svnkeep ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/layouts/.svnkeep b/jena-fuseki2/src/main/webapp/js/app/layouts/.svnkeep new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/main.dataset.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/main.dataset.js b/jena-fuseki2/src/main/webapp/js/app/main.dataset.js new file mode 100644 index 0000000..50ba27b --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/main.dataset.js @@ -0,0 +1,31 @@ +/** RequireJS dependency configuration for dataset.html page */ + +define( ['require', '../common-config'], + function( require ) { + require( + ['underscore', 'jquery', 'backbone', 'marionette', 'app/fui', 'app/controllers/dataset-controller', + 'sprintf', + 'bootstrap-select.min', + 'app/controllers/query-controller', + 'app/controllers/upload-controller', + 'app/models/fuseki-server', + 'app/models/dataset', + 'app/views/dataset-selector', + 'app/views/tabbed-view-manager', + 'app/services/ping-service', + 'jquery.xdomainrequest', + 'jquery.form', + 'jquery.fileupload' + ], + function( _, $, Backbone, Marionette, fui, DatasetController ) { + var options = { }; + + // initialise the backbone application + fui.controllers.datasetController = new DatasetController(); + fui.start( options ); + + // additional services + require( 'app/services/ping-service' ).start(); + }); + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/main.index.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/main.index.js b/jena-fuseki2/src/main/webapp/js/app/main.index.js new file mode 100644 index 0000000..56c8903 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/main.index.js @@ -0,0 +1,24 @@ + +define( ['require', '../common-config'], + function( require ) { + require( + ['underscore', 'jquery', 'backbone', 'marionette', + 'app/fui', 'app/controllers/index-controller', + 'sprintf', 'bootstrap', + 'app/models/fuseki-server', + 'app/models/dataset', + 'app/views/dataset-selection-list', + 'app/services/ping-service' + ], + function( _, $, Backbone, Marionette, fui, IndexController ) { + var options = { }; + + // initialise the backbone application + fui.controllers.indexController = new IndexController(); + fui.start( options ); + + // additional services + require( 'app/services/ping-service' ).start(); + }); + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/main.manage.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/main.manage.js b/jena-fuseki2/src/main/webapp/js/app/main.manage.js new file mode 100644 index 0000000..312972c --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/main.manage.js @@ -0,0 +1,27 @@ + +define( ['require', '../common-config'], + function( require ) { + require( + ['underscore', 'jquery', 'backbone', 'marionette', + 'app/fui', 'app/controllers/manage-controller', + 'sprintf', 'bootstrap', + 'app/models/fuseki-server', + 'app/models/dataset', + 'app/models/task', + 'app/views/dataset-management', + 'app/services/ping-service', + 'jquery.xdomainrequest' + ], + function( _, $, Backbone, Marionette, fui, ManageController ) { + + var options = { } ; + + // initialise the backbone application + fui.controllers.manageController = new ManageController(); + fui.start( options ); + + // additional services + require( 'app/services/ping-service' ).start(); + }); + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/main.validation.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/main.validation.js b/jena-fuseki2/src/main/webapp/js/app/main.validation.js new file mode 100644 index 0000000..0e30fad --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/main.validation.js @@ -0,0 +1,24 @@ + +define( ['require', '../common-config'], + function( require ) { + require( + ['underscore', 'jquery', 'backbone', 'marionette', + 'app/fui', 'app/controllers/validation-controller', + 'sprintf', 'bootstrap', + 'app/models/validation-options', + 'app/services/ping-service', + 'app/services/validation-service', + 'jquery.xdomainrequest' + ], + function( _, $, Backbone, Marionette, fui, ValidationController ) { + var options = { } ; + + // initialise the backbone application + fui.controllers.validationController = new ValidationController(); + fui.start( options ); + + // additional services +// require( 'services/ping-service' ).start(); TODO restore + }); + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/models/dataset-stats.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/models/dataset-stats.js b/jena-fuseki2/src/main/webapp/js/app/models/dataset-stats.js new file mode 100644 index 0000000..35527c7 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/models/dataset-stats.js @@ -0,0 +1,102 @@ +/** + * Backbone model denoting statistics on a dataset + */ +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ); + + /** + * This model represents the statistics available on a one or more datasets + */ + var DatasetStats = Backbone.Model.extend( { + initialize: function( dataset, stats ) { + this.set( {dataset: dataset, stats: stats} ); + }, + + /** Return the number of datasets we have statistics for */ + size: function() { + return _.keys( datasets() ).length; + }, + + toJSON: function() { + return this.asTable(); + }, + + /** Return a table of the statistics we have, one row per dataset */ + asTable: function() { + var ds = this.datasets(); + var endpoints = this.collectEndpoints( ds ); + var rows = []; + + _.each( ds, function( d, dsName ) { + var row = [dsName, d.Requests, d.RequestsGood, d.RequestsBad]; + var es = d.endpoints; + + _.each( endpoints, function( e ) { + if (es[e.key]) { + var servStats = es[e.key]; + + if (servStats.Requests === 0) { + row.push( "0" ); + } + else { + row.push( sprintf( "%d (%d bad)", servStats.Requests, servStats.RequestsBad )) + } + } + else { + row.push( "–" ); + } + } ); + + rows.push( row ); + } ); + + return {headings: this.columnHeadings( endpoints ), rows: rows}; + }, + + stats: function() { + return this.get( "stats" ); + }, + + datasets: function() { + return this.stats().datasets; + }, + + /** Reload the numbers from the server */ + refresh: function() { + var self = this; + + this.get( "dataset" ) + .statistics() + .done( function( data ) { + self.set( "stats", data ); + } ); + }, + + // internal methods + + collectEndpoints: function( ds ) { + var endpoints = []; + _.each( ds, function( d ) { + var ep = _.each( d.endpoints, function( v, k ) { + endpoints.push( {key: k, label: v.description} ); + } ); + } ); + + return _.uniq( endpoints ).sort(); + }, + + columnHeadings: function( services ) { + return ["Name", "Overall", "Overall good", "Overall bad"].concat( _.pluck( services, 'label' ) ); + } + } ); + + return DatasetStats; + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/models/dataset.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/models/dataset.js b/jena-fuseki2/src/main/webapp/js/app/models/dataset.js new file mode 100644 index 0000000..80dc092 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/models/dataset.js @@ -0,0 +1,251 @@ +/** + * Backbone model denoting the remote Fuseki server. + */ +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ), + Task = require( "app/models/task" ); + + /** + * This model represents the core representation of the remote Fuseki + * server. Individual datasets have their own model. + */ + var Dataset = Backbone.Model.extend( { + initialize: function( datasetDescription, baseURL, mgmtURL ) { + this.set( datasetDescription ); + this.set( { + baseURL: baseURL, + mgmtURL: mgmtURL, + counts: {}, + countPerformed: false, + counting: false, + statistics: false + } ); + }, + + baseURL: function() { + return this.get( "baseURL" ); + }, + + mgmtURL: function() { + return this.get( "mgmtURL" ); + }, + + mgmtActionURL: function() { + return this.get( "mgmtURL" ) + this.name(); + }, + + statisticsURL: function() { + return sprintf( "%s/$/stats%s", this.baseURL(), this.name() ); + }, + + name: function() { + return this.get( "ds.name" ); + }, + + services: function() { + return this.get( "ds.services" ); + }, + + countPerformed: function() { + return this.get( "countPerformed" ); + }, + + counts: function() { + return this.get( "counts" ); + }, + + serviceTypes: function() { + return _.map( this.services(), function( s ) {return s["srv.type"];} ); + }, + + /** Return a descriptive data-structure listing all this datasets services */ + servicesDescription: function() { + var description = []; + var self = this; + + _.each( this.services(), function( s ) { + _.each( s["srv.endpoints"], function( e ) { + description.push( {label: s["srv.description"], + url: self.datasetEndpointURL( e ) + } ); + } ); + } ); + + description.sort( function( d0, d1 ) { + return (d0.label < d1.label) ? -1 : (d0.label > d1.label ? 1 : 0); + } ); + + return description; + }, + + /** Return the first service that has the given type */ + serviceOfType: function( serviceType ) { + return _.find( this.services(), function( s ) { + return s["srv.type"] === serviceType; + } ); + }, + + /** Return the first endpoint of the first service that has the given type */ + endpointOfType: function( serviceType ) { + var service = this.serviceOfType( serviceType ); + return service && _.first( service["srv.endpoints"] ); + }, + + /* Return URL for a service of a given type or null, if no such service */ + endpointURL: function( serviceType ) { + var endpoint = this.endpointOfType( serviceType ); + return endpoint ? this.datasetEndpointURL( endpoint ) : null; + }, + + /** Return the URL for the given endpoint */ + datasetEndpointURL: function( endpoint ) { + return sprintf( "%s%s/%s", this.baseURL(), this.name(), endpoint ); + }, + + /** Return the sparql query URL for this dataset, if it has one, or null */ + queryURL: function() { + return this.endpointURL( "Query" ) ; + }, + + /** Return the sparql query URL for this dataset, if it has one, or null */ + quadsURL: function() { + return this.endpointURL( "Quads" ) ; + }, + + /** Return the sparql update URL for this dataset, if it has one, or null */ + updateURL: function() { + return this.endpointURL( "Update" ) ; + }, + + /** Return the GSP write URL for this dataset, if it has one, or null */ + graphStoreProtocolURL: function() { + return this.endpointURL( "GSP" ) ; + }, + + /** Return the GSP read URL for this dataset, if it has one, or null */ + graphStoreProtocolReadURL: function() { + return this.endpointURL( "GSP_R" ) ; + }, + + /** Return the upload URL for this dataset, if it has one, or null */ + uploadURL: function( graphName ) { + if (this.graphStoreProtocolURL() !== null) { + return sprintf( "%s%s", this.graphStoreProtocolURL(), (graphName === "default" ? "" : ("?graph=" + graphName) )); + } + else { + return null; + } + }, + + /** Perform the action to delete the dataset. Returns the Ajax deferred object */ + deleteDataset: function() { + return $.ajax( { + url: this.mgmtActionURL(), + type: 'DELETE' + } ); + }, + + /** Perform the action of taking a backup of this dataset */ + backupDataset: function() { + var backupURL = sprintf( "%s/$/backup%s", this.baseURL(), this.name() ); + var ds = this; + + return $.ajax( { + url: backupURL, + type: 'POST' + } ) + .done( function( taskDescription ) { + new Task( ds, "backup", taskDescription ); + } ); + }, + + /** + * Request the statistics for this dataset, and return the promise object for the callback. + * @param keep If truthy, and we have existing statistics, re-use the existing stats + * */ + statistics: function( keep ) { + var self = this; + var currentStats = this.get( "statistics" ); + + if (currentStats && keep) { + return $.Deferred().resolveWith( null, currentStats ); + } + else { + return $.getJSON( this.statisticsURL() ) + .then( function( data ) { + self.set( "statistics", data ); + return data; + } ); + } + }, + + /** Perform a count query to determine the size of the dataset. Changes the size property when done, + * but also returns the JQuery promise object used to monitor the query response */ + count: function() { + var self = this; + var query1 = sprintf( "select (count(*) as ?count) {?s ?p ?o}" ); + var query2 = sprintf( "select ?g (count(*) as ?count) {graph ?g {?s ?p ?o}} group by ?g" ); + + self.set( "counting", true ); + + var updateCount = function( model, result, graph ) { + var n = parseInt( result.count.value ); + var counts = _.extend( {}, model.get( "counts" ) ); + counts[graph] = n; + model.set( "counts", counts ); + }; + + $.getJSON( sprintf( "%s?query=%s", self.queryURL(), query1 ) ) + .done( function( data ) { + updateCount( self, data.results.bindings[0], "default graph" ); + + $.getJSON( sprintf( "%s?query=%s", self.queryURL(), query2 ) ) + .done( function( data ) { + _.each( data.results.bindings, function( binding ) { + if (binding.g) { + updateCount( self, binding, binding.g.value ); + } + } ); + } ); + + self.set( {countPerformed: true, counting: false} ); + } ); + }, + + /** + * Fetch the content of the given graph as Turtle. Return the jQuery promise + * object for the Ajax call. + */ + fetchGraph: function( graphName ) { + return $.ajax( this.graphStoreProtocolReadURL(), + {method: "get", + headers: {Accept : "text/turtle; charset=utf-8"}, + dataType: "html", + data: {graph: graphName} + } ); + }, + + /** + * Put the given Turtle content back to the server using the given graph name + */ + putGraph: function( turtle, graphName ) { + return $.ajax( sprintf( "%s?graph=%s", this.graphStoreProtocolURL(), graphName ), + {method: "put", + dataType: "json", + data: turtle, + contentType: "text/turtle; charset=uft-8" + } ); + } + + } ); + + return Dataset; + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/models/fuseki-server.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/models/fuseki-server.js b/jena-fuseki2/src/main/webapp/js/app/models/fuseki-server.js new file mode 100644 index 0000000..59d8d31 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/models/fuseki-server.js @@ -0,0 +1,155 @@ +/** + * Backbone model denoting the remote Fuseki server. + */ + +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ), + Dataset = require( "app/models/dataset" ), + PageUtils = require( "app/util/page-utils" ); + + var DATASETS_MANAGEMENT_PATH = "/$/datasets"; + + /** + * This model represents the core representation of the remote Fuseki + * server. Individual datasets have their own model. + */ + var FusekiServer = Backbone.Model.extend( { + /** This initializer occurs when the module starts, not when the constructor is invoked */ + init: function( options ) { + this._baseURL = this.currentRootPath(); + this._managementURL = null; + this.set( "selectedDatasetName", PageUtils.queryParam( "ds" ) ) + }, + + baseURL: function() { + return this._baseURL; + }, + + /** Return the URL from which we extract the details of the current server */ + serverDetailsURL: function() { + return sprintf( "%s/$/server", this.baseURL() ); + }, + + /** Return the URL for issuing commands to the management API, or null if no API defined */ + managementURL: function() { + return this._managementURL; + }, + + /** Return the URL for getting the stats for all datasets */ + statsURL: function() { + return sprintf( "%s/$/stats", this.managementURL() ); + }, + + /** Return the list of datasets that this server knows about. Each dataset will be a Dataset model object */ + datasets: function() { + return this.get( "datasets" ); + }, + + /** Return the dataset with the given name */ + dataset: function( dsName ) { + return _.find( this.datasets(), function( ds ) {return dsName === ds.name();} ) + }, + + /** Return the name of the currently selected dataset, if known */ + selectedDatasetName: function() { + return this.get( "selectedDatasetName" ); + }, + + /** Return the dataset that is currently selected, or null */ + selectedDataset: function() { + var dsName = this.selectedDatasetName(); + return dsName && this.dataset( dsName ); + }, + + /** Load and cache the remote server description. Trigger change event when done */ + loadServerDescription: function() { + var self = this; + return this.getJSON( this.serverDetailsURL() ) + .done( function( data ) { + self.saveServerDescription( data ); + } ) + .then( function() { + fui.vent.trigger( "models.fuseki-server.ready" ); + }); + }, + + /** Store the server description in this model */ + saveServerDescription: function( serverDesc ) { + // wrap each dataset JSON description as a dataset model + var bURL = this.baseURL(); + var mgmtURL = bURL; + + if (serverDesc.admin) { + mgmtURL = bURL.replace( ":" + window.location.port, ":" + serverDesc.admin.port ); + this._managementURL = mgmtURL; + } + + var datasets = _.map( serverDesc.datasets, function( d ) { + return new Dataset( d, bURL, mgmtURL + DATASETS_MANAGEMENT_PATH ); + } ); + + datasets.sort( function( ds0, ds1 ) { + if (ds0.name() > ds1.name()) { + return 1; + } + else if (ds0.name() < ds1.name()) { + return -1; + } + else + return 0; + } ); + + this.set( { + serverDescription: serverDesc, + datasets: datasets, + ready: true + } ); + }, + + /** + * Get the given relative path from the server, and return a promise object which will + * complete with the JSON object denoted by the path. + */ + getJSON: function( path, data ) { + return $.getJSON( path, data ); + }, + + /** Update or create a dataset by posting to its endpoint */ + updateOrCreateDataset: function( datasetId, data ) { + var url = sprintf( "%s/$/datasets%s", this.managementURL(), + datasetId ? ("/" + datasetId) : "" + ); + + return $.ajax( url, + { data: data, + method: "post" + } + ); + }, + + /** Extract the server root path from the current window href */ + currentRootPath: function() { + var path = window.location.pathname.replace( /\/[^/]*$/, "" ); + return sprintf( "http://%s:%s%s", window.location.hostname, window.location.port, path ); + } + } ); + + // when the models module starts, automatically load the server description + fui.models.addInitializer( function( options ) { + var fusekiServer = new FusekiServer(); + fui.models.fusekiServer = fusekiServer; + + fusekiServer.init( options ); + fusekiServer.loadServerDescription(); + } ); + + return FusekiServer; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/models/task.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/models/task.js b/jena-fuseki2/src/main/webapp/js/app/models/task.js new file mode 100644 index 0000000..10c0563 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/models/task.js @@ -0,0 +1,105 @@ +/** + * A long-running task, which periodically pings the server for its task status + */ + +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ); + + /* Constants */ + + var MS = 1000; + var MAX_DELAY = 10 * MS; + + /** + * This model represents a long running task process + */ + var Task = function( ds, operationType, taskDescription ) { + this.taskDescription = taskDescription; + this.ds = ds; + this.operationType = operationType; + this.delay = 500; + + _.bindAll( this, "checkTaskStatus", "onCurrentTaskStatusFail", "onCurrentTaskStatus" ); + + this.checkTaskStatus(); + }; + + _.extend( Task.prototype, { + /** Return the unique ID (on this server) of the task */ + taskId: function() { + return this.taskDescription.taskId; + }, + + /** Return the URL for the task's API */ + taskURL: function() { + return sprintf( "%s/$/tasks/%s", this.ds.baseURL(), this.taskId() ); + }, + + /** Test the current status of the task */ + checkTaskStatus: function() { + $.getJSON( this.taskURL() ) + .done( this.onCurrentTaskStatus ) + .fail( this.onCurrentTaskStatusFail ) + }, + + /** Successful result from checking the task */ + onCurrentTaskStatus: function( taskDescription ) { + this.taskDescription = taskDescription; + + var status = { + task: this, + dsId: this.ds.name(), + finished: this.taskFinished() + }; + + fui.vent.trigger( "task.status", status ); + + this.queueTaskStatusCheck(); + }, + + /** Failed to check the task */ + onCurrentTaskStatusFail: function( jqxhr, msg, err ) { + var status = { + task: this, + dsId: this.ds.name(), + errorMessage: err || msg + }; + + fui.vent.trigger( "task.failed", status ); + }, + + /** Re-queue the status check if the task is not yet complete */ + queueTaskStatusCheck: function() { + if (!this.taskFinished()) { + _.delay( this.checkTaskStatus, this.statusDelay() ); + } + }, + + /** Return the completion time if the task has been fid, otherwise null */ + taskFinished: function() { + return this.taskDescription.finished; + }, + + /** Return the delay in ms until the next status check is due. */ + statusDelay: function() { + var t = this.delay; + + if (t < MAX_DELAY) { + this.delay = t * 2; + } + + return t; + } + } ); + + return Task; + } +); + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/models/validation-options.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/models/validation-options.js b/jena-fuseki2/src/main/webapp/js/app/models/validation-options.js new file mode 100644 index 0000000..b114cf9 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/models/validation-options.js @@ -0,0 +1,85 @@ +/** + * Backbone model denoting the remote Fuseki server. + */ +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ); + + /** + * This model represents the users current choice of options to the + * validation service. + */ + var ValidationOptions = Backbone.Model.extend( { + initialize: function() { + this.set( {validateAs: "sparql"} ); + this.set( {outputFormat: "algebra"} ); + }, + + validateAs: function() { + return this.get( "validateAs" ); + }, + + validateAsQuery: function() { + return this.validateAs() === "sparql" || this.validateAs() === "arq"; + }, + + setValidateAs: function( va ) { + this.set( "validateAs", va ); + console.log( JSON.stringify( this.toJSON() )); + console.log( "----" ); + }, + + outputFormat: function() { + return this.get( "outputFormat" ); + }, + + setOutputFormat: function( of ) { + this.set( "outputFormat", of ); + }, + + validationURL: function() { + switch (this.get( "validateAs" )) { + case "sparql": return "/validate/query"; + case "arq": return "/validate/query"; + case "Turtle": return "/validate/data"; + case "TriG": return "/validate/data"; + case "N-Triples": return "/validate/data"; + case "N-Quads": return "/validate/data"; + } + }, + + payloadParam: function() { + return this.validateAsQuery() ? "query" : "data"; + }, + + toJSON: function() { + var json = { + languageSyntax: this.validateAs(), + lineNumbers: true + }; + + if (this.validateAsQuery()) { + json.outputFormat = this.outputFormat(); + } + + return json; + } + + } ); + + // when the models module starts, create the model + fui.models.addInitializer( function( options ) { + fui.models.validationOptions = new ValidationOptions(); + fui.vent.trigger( "models.validation-options.ready" ); + } ); + + + return ValidationOptions; + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/qonsole-config.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/qonsole-config.js b/jena-fuseki2/src/main/webapp/js/app/qonsole-config.js new file mode 100644 index 0000000..98cbf9b --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/qonsole-config.js @@ -0,0 +1,26 @@ +/** Standalone configuration for qonsole on index page */ + +define( [], function() { + return { + prefixes: { + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "owl": "http://www.w3.org/2002/07/owl#", + "xsd": "http://www.w3.org/2001/XMLSchema#" + }, + queries: [ + { "name": "Selection of triples", + "query": "select ?subject ?predicate ?object\nwhere {\n" + + " ?subject ?predicate ?object\n}\n" + + "limit 25" + }, + { "name": "Selection of classes", + "query": "select distinct ?class ?label ?description\nwhere {\n" + + " ?class a owl:Class.\n" + + " optional { ?class rdfs:label ?label}\n" + + " optional { ?class rdfs:comment ?description}\n}\n" + + "limit 25" + } + ] + }; +} ); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/routers/.svnkeep ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/routers/.svnkeep b/jena-fuseki2/src/main/webapp/js/app/routers/.svnkeep new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/services/ping-service.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/services/ping-service.js b/jena-fuseki2/src/main/webapp/js/app/services/ping-service.js new file mode 100644 index 0000000..75ef6a4 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/services/ping-service.js @@ -0,0 +1,54 @@ +/** + * The ping service checks the status of the attached server and sets the light in the + * control bar accordingly. + */ +define( ['jquery', 'underscore', 'sprintf'], + function( $, _, sprintf ) { + + var PING_URL = "$/ping" + var DEFAULT_PING_TIME = 500000; // TODO slowed down during debugging phase + var _startTime = 0; + + var onBeforeSend = function() { + _startTime = new Date().getTime(); + }; + + var duration = function() { + return new Date().getTime() - _startTime; + }; + + var onPingSuccess = function( ) { + setPingStatus( "server-up", sprintf( "Last ping returned OK in %dms", duration() ) ); + }; + + var onPingFail = function( jqXHR, msg, errorThrown ) { + setPingStatus( "server-down", sprintf( "Last ping returned '%s' in %dms", errorThrown || msg, duration() ) ); + }; + + var setPingStatus = function( lampClass, statusText ) { + $( "a#server-status-light span").removeClass() + .addClass( lampClass ) + .attr( "title", statusText ); + }; + + /** Return a cache-defeating ping URL */ + var ping_url = function() { + return PING_URL + "?_=" + Math.random(); + }; + + var start = function( period ) { + ping( period || DEFAULT_PING_TIME ); + }; + + var ping = function( period ) { + onBeforeSend(); + $.get( ping_url() ).done( onPingSuccess ) + .fail( onPingFail ); + setTimeout( function() {ping( period );}, period ); + }; + + return { + start: start + } + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/services/validation-service.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/services/validation-service.js b/jena-fuseki2/src/main/webapp/js/app/services/validation-service.js new file mode 100644 index 0000000..1a5d919 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/services/validation-service.js @@ -0,0 +1,98 @@ +define( ['underscore', 'jquery', 'fui', + 'lib/codemirror', 'mode/javascript/javascript', 'mode/sparql/sparql'], + function( _, $, fui, CodeMirror ) { + + var ValidationService = function( editor_el, output_el ) { + this.editor_el = editor_el; + this.output_el = output_el; + }; + + _.extend( ValidationService.prototype, { + init: function() { + _.bindAll( this, "handleValidationOutput", "handleJsonValidationOutput" ); + this.editorElement(); + }, + + /** Return the DOM node representing the query editor */ + editorElement: function() { + if (!this._editor) { + this._editor = new CodeMirror( $(this.editor_el).get(0), { + lineNumbers: true, + mode: "text" + } ); + } + return this._editor; + }, + + /** Return the DOM node representing the output editor */ + outputElement: function( mode, lineNumbers, data ) { + $(this.output_el).empty(); + + var cm = new CodeMirror( $(this.output_el).get(0), { + lineNumbers: lineNumbers, + mode: mode || "text", + readOnly: true, + value: data + } ); + + return cm; + }, + + /** Return the current code editor contents */ + editorContent: function() { + return this.editorElement().getValue(); + }, + + /** Perform the given action to validate the current content */ + performValidation: function( optionsModel ) { + var context = {optionsModel: optionsModel}; + var self = this; + + var content = {}; + content[optionsModel.payloadParam()] = this.editorContent(); + + var options = { + data: _.extend( optionsModel.toJSON(), content ), + type: "POST" + }; + + $.ajax( optionsModel.validationURL(), options ) + .done( function( data, status, xhr ) { + self.handleValidationOutput( data, status, xhr, context ); + } ); + }, + + /** Respond to validation output from the server */ + handleValidationOutput: function( data, status, xhr, context ) { + var ct = xhr.getResponseHeader("content-type") || ""; + if (ct.match( /json/ )) { + this.handleJsonValidationOutput( data, context ); + } + else { + // in HTML output, we look for a .error div + var errors = $(data).find( "div.error" ).text(); + this.outputElement( "text", true, errors || "No warnings or errors reported." ); + } + }, + + handleJsonValidationOutput: function( json, context ) { + var outputFormat = context.optionsModel.outputFormat(); + console.log( "output format = " + outputFormat ); + var jsonString = null; + + if (outputFormat && json[outputFormat]) { + jsonString = json[outputFormat]; + } + else { + jsonString = JSON.stringify( json, null, ' ' ); + } + + this.outputElement( "application/json", false, jsonString ); + } + + } ); + + + return ValidationService; + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-edit.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-edit.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-edit.tpl new file mode 100644 index 0000000..8202831 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-edit.tpl @@ -0,0 +1,58 @@ +<div class="row"> + <div class="col-md-4"> + <div class="bordered-box"> + <span class="pull-right"> + <button class="btn btn-sm btn-info action list-graphs">list current graphs</button> + </span> + <h3>Available graphs</h3> + + <% if (countPerformed()) { %> + <ul class="nav nav-pills nav-stacked graphs"> + <% _.each( counts(), function( n, g ) { %> + <li class=""> + <a href="#" class="select-dataset" data-graph-name="<%= g %>" data-graph-size="<%= n %>"> + <%= g %> (<%= n %> triples) + </a> + </li> + <% } ); %> + </ul> + <% } else { %> + <p class="text-muted text-sm">Click to list current graphs</p> + <% } %> + </div> <!-- /.bordered-box --> + </div> <!-- /.col-md-4 --> + + <div class="col-md-8"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="input-group"> + <div class="input-group-addon">graph:</div> + <input class="form-control graph-name" type="text" placeholder=""> + </div> + </div> + </div> + </div> <!-- /.row --> + + <div class="row"> + <div class="col-md-12"> + <div id="graph-editor" class="bordered-box"></div> + </div> + </div> <!-- /.row --> + + <div class="row"> + <div class="col-md-12"> + <p class="feedback"></p> + </div> + </div> <!-- /.row --> + + <div class="row"> + <div class="col-md-12"> + <span class="pull-right"> + <button class="btn btn-default action cancel-edit"><i class="fa fa-times"></i> discard changes</button> + <button class="btn btn-info action save-edit"><i class="fa fa-check"></i> save</button> + </span> + </div> + </div> <!-- /.row --> + </div> <!-- /.col-md-8 --> +</div> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-info.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-info.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-info.tpl new file mode 100644 index 0000000..c2c6891 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-info.tpl @@ -0,0 +1,40 @@ +<h2>Available services</h2> + +<dl class="dl-horizontal"> + <% _.each( servicesDescription(), function( serviceDescription ) { %> + <dt> + <%= serviceDescription.label %>: + </dt> + <dd> + <a href="<%= serviceDescription.url %>"><%= serviceDescription.url %></a> + </dd> + <% } ); %> +</dl> + +<h2>Statistics</h2> +<div id="statistics"></div> + +<h2>Dataset size</h2> +<p> +<strong>Note</strong> this may be slow and impose a significant load on large datasets: +<button href="#" class="action count-graphs btn btn-primary">count triples in all graphs</button> +</p> +<% if (countPerformed()) { %> +<dl class="dl-horizontal"> + <dt><span class="heading">graph name:</span></dt><dd><span class="heading">triples:</span></dd> + <% _.each( counts(), function( n, g ) { %> + <dt class="font-weight-normal"> + <%= g %> + </dt> + <dd> + <div class="numeric"><%= n %></div> + </dd> + <% } ); %> +</dl> + +<% } %> + +<h2>Ongoing operations</h2> + +<p><em>TBD. Will list any long-lasting operations that are ongoing or recently completed, +e.g. backups.</em></p> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-management.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-management.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-management.tpl new file mode 100644 index 0000000..5a2181a --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-management.tpl @@ -0,0 +1,53 @@ +<% if (datasets.length === 0) { %> + <p>No datasets have been created yet. + <a class="btn btn-sm btn-primary" href="?tab=new-dataset">add one</a> + </p> +<% } else { %> + <div class="row"> + <div class="col-md-12"> + <table class='table'> + <tr class="headings"><th>Name</th><th>Active?</th><th></th></tr> + <% _.each( datasets, function( ds ) { %> + <tr> + <td> + <%= ds.name() %> + </td> + <td><input type='checkbox' class='checkbox' checked /></td> + <td> + <div> + <a class="btn btn-sm action remove btn-primary" data-ds-id='<%= ds.name() %>'><i class='fa fa-times-circle'></i> remove</a> + <a class="btn btn-sm action backup btn-primary" data-ds-id='<%= ds.name() %>'><i class='fa fa-download'></i> backup</a> + <a class="btn btn-sm action add-data btn-primary" href="dataset.html?tab=upload&ds=<%= ds.name() %>"><i class='fa fa-upload'></i> upload data</a> + </div> + <div class="action feedback"></a> + </td> + </tr> + <% }) %> + + </table> + </div> + </div> +<% } %> + +<!-- Modal dialogs --> + +<div class="modal fade" id="actionConfirmModal" tabindex="-1" role="dialog" aria-labelledby="actionConfirmModalLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="actionConfirmModalLabel">Confirm action</h4> + </div> + <div class="modal-body"> + <p></p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-icon-remove"></i> Cancel</button> + <button type="button" class="btn btn-primary action confirm"> + <i class="fa fa-icon-confirm"></i> + Confirm <span class="action-label">action</span> + </button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selection-list.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selection-list.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selection-list.tpl new file mode 100644 index 0000000..7eda02d --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selection-list.tpl @@ -0,0 +1,22 @@ +<div class="col-md-span-12"> + <% if (datasets.length > 0) { %> + <table class='table ijd'> + <tr class="headings"><th>dataset name</th><th>actions</th></tr> + <% _.each( datasets, function( ds ) { %> + <tr> + <td> + <%= ds.name() %> + </td> + <td> + <a class="btn btn-sm action remove btn-primary" href="dataset.html?tab=query&ds=<%= ds.name() %>"><i class='fa fa-question-circle'></i> query</a> + <a class="btn btn-sm action remove btn-primary" href="dataset.html?tab=upload&ds=<%= ds.name() %>"><i class='fa fa-upload'></i> add data</a> + <a class="btn btn-sm action configure btn-primary" href="dataset.html?tab=info&ds=<%= ds.name() %>"><i class='fa fa-dashboard'></i> info</a> + </td> + </tr> + <% }) %> + + </table> + <% } else { %> + <p>There are no datasets on this server yet. <a href="manage.html?tab=new-dataset">Add one.</a></p> + <% } %> +</div> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selector.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selector.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selector.tpl new file mode 100644 index 0000000..d684995 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-selector.tpl @@ -0,0 +1,15 @@ +<div class='dataset-selector'> + <% if (datasets.length == 0) { %> + <% } else { %> + <label class=""> + <div class="select-picker-label">Dataset:</div> + <select class='selectpicker show-tick'> + <% _.each( datasets, function( ds ) { %> + <option <%= (ds.name() === selectedDatasetName) ? "selected" : "" %>> + <%= ds.name() %> + </option> + <% } ); %> + </select> + </label> + <% } %> +</div> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-simple-create.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-simple-create.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-simple-create.tpl new file mode 100644 index 0000000..2611908 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-simple-create.tpl @@ -0,0 +1,79 @@ +<div class="row"> + <div class="col-md-12"> + <div class="" id="simple-edit"> + <form class="form-horizontal" role="form"> + <div class="form-group"> + <label for="datasetName" class="col-sm-2 control-label">Dataset name</label> + <div class="col-sm-10"> + <div class="validation-warning dbNameValidation">A name for the dataset is required</div> + <input type="text" class="form-control" name="dbName" placeholder="dataset name" /> + </div> + </div> + <div class="form-group"> + <label class="col-sm-2 control-label">Dataset type</label> + <div class="col-sm-10"> + <div class="radio"> + <label> + <input type="radio" name="dbType" value="mem" checked> + In-memory – dataset will be recreated when Fuseki restarts, but contents will be lost + </label> + </div> + <div class="radio"> + <label> + <input type="radio" name="dbType" value="tdb"> + Persistent – dataset will persist across Fuseki restarts + </label> + </div> + </div> + </div> + + <div class="row"> + <div class="col-md-12"> + <div class="errorOutput bg-warning"></div> + </div> + </div> + + <div class="row controls"> + <div class="col-md-3 col-md-offset-9"> + <a href="#" class="btn btn-sm btn-primary action commit simple"><i class="fa fa-check"></i> create dataset</a> + </div> + </div> + </form> + </div> + + <!-- + <div class="tab-pane" id="upload"> + <p> </p> + <div class="row"> + <p class="col-sm-12">If you have a Fuseki config file (i.e. a Jena assembler description), + you can upload it here:</p> + </div> + <div class="row controls"> + <form id="uploadForm" method="post" action="$/datasets" class="form-horizontal col-sm-12"> + <div class="form-group"> + <label for="assemblerFile" class="col-sm-2 control-label">Configuration file</label> + <div class="col-sm-10"> + <div class="validation-warning assemblerFileValidation">A file name is required</div> + <input type="file" class="form-control" name="assemblerFile" /> + </div> + </div> + </form> + </div> + + <div class="row"> + <div class="errorOutput col-sm-12"></div> + </div> + + <div class="row"> + <div class="col-sm-12"> + <a href="admin-data-management.html" class="btn btn-sm btn-default"><i class="fa fa-mail-reply"></i> cancel</a> + <a href="#" class="btn btn-sm btn-primary action upload"><i class="fa fa-upload"></i> upload config file</a> + </div> + </div> + </div> + --> + + </div><!-- /.col-md-12 --> +</div><!-- /.row --> + + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-stats.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/dataset-stats.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-stats.tpl new file mode 100644 index 0000000..fc3d7d9 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/dataset-stats.tpl @@ -0,0 +1,14 @@ +<table class="table"> + <tr> + <% _.each( headings, function( h ) { %> + <th class="text-right"><%= h %></th> + <% } ); %> + </tr> + <% _.each( rows, function( row ) { %> + <tr> + <% _.each( row, function( cell ) { %> + <td class="text-right"><%= cell %></td> + <% } ); %> + </tr> + <% } ) %> +</table> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/file-upload.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/file-upload.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/file-upload.tpl new file mode 100644 index 0000000..03d7dcc --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/file-upload.tpl @@ -0,0 +1,46 @@ +<div class="col-md-12 ful"> + <h2>Upload files</h2> + <p class="text-muted">Load data into the default graph of the currently selected dataset, + or the given named graph. + You may upload any RDF format, such as Turtle, RDF/XML or TRiG. + </p> + + <div class="row"> + <div class="form-group2 graph-label"> + <label for="uploadGraphName" class="col-sm-3 control-label">Destination graph name</label> + <span class="col-sm-9"> + <p> + <input type="text" name="graph" id="graphName" placeholder="Leave blank for default graph" class="form-control"> + </p> + </span> + </div> <!-- /.form-group --> + </div> + + <div class="row"> + <form id="fileuploadForm" class="form-horizontal" role="form" method="POST" enctype="multipart/form-data"> + <div class="form-group2"> + <label class="col-sm-3 control-label">Files to upload</label> + <div class="col-sm-9"> + <span> + <span class="btn btn-success fileinput-button"> + <i class="fa fa-plus"></i> + <span>select files...</span> + <input id="fileupload" type="file" name="files[]" multiple> + </span> + <button type="submit" class="btn btn-primary start action-upload-all" disabled> + <i class="fa fa-upload"></i> + <span>upload all</span> + </button> + </span> + </div> + </div> <!-- /.form-group --> + + <div class="form-group2"> + <div class="col-sm-9 col-sm-offset-3"> + <ul class="list-unstyled"> + </ul> + </div> + </div> <!-- /.form-group --> + </form> + </div> + </div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/templates/uploadable-file.tpl ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/templates/uploadable-file.tpl b/jena-fuseki2/src/main/webapp/js/app/templates/uploadable-file.tpl new file mode 100644 index 0000000..83b8449 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/templates/uploadable-file.tpl @@ -0,0 +1,23 @@ +<div class="row file-description"> + <div class="col-sm-3"> + <%= file.name %> + </div> + <div class="col-sm-3"> + <em> + <%= file.readableFileSize %> + </em> + </div> + <div class="col-sm-6"> + <button class="btn btn-sm btn-default action action-upload-file"><i class="fa fa-upload"></i> upload now</button> + <button class="btn btn-sm btn-default action action-remove-upload"><i class="fa fa-minus-circle"></i> remove</button> + </div> + <div class="col-sm-12"> + <div class="result"></div> + </div> + <div class="col-sm-12"> + <div class="progress"> + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> + </div> + </div> + +</div> http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/util/page-utils.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/util/page-utils.js b/jena-fuseki2/src/main/webapp/js/app/util/page-utils.js new file mode 100644 index 0000000..45b4ffc --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/util/page-utils.js @@ -0,0 +1,33 @@ +/** Utilities for managing HTML pages */ + +define( + function( require ) { + "use strict"; + + var _ = require( "underscore" ); + + /** Return true if a given query parameter is defined, otherwise null */ + var hasQueryParam = function( param ) { + return !!queryParam( param ); + }; + + /** Return the value of a query parameter, or null */ + var queryParam = function( param ) { + var p = param && queryParams()[param]; + return p ? p : null; + }; + + /** Return the current query params as a map */ + var queryParams = function() { + return _.chain( document.location.search.slice(1).split('&') ) + .invoke('split', '=') + .object() + .value(); + }; + + return { + hasQueryParam: hasQueryParam, + queryParam: queryParam + }; + } +); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/.svnkeep ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/.svnkeep b/jena-fuseki2/src/main/webapp/js/app/views/.svnkeep new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-edit.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-edit.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-edit.js new file mode 100644 index 0000000..017e097 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-edit.js @@ -0,0 +1,205 @@ +/** Component for showing detailed information about a dataset */ + +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + DatasetEditTpl = require( "plugins/text!app/templates/dataset-edit.tpl" ), + CodeMirror = require( "lib/codemirror" ), + CodeMirrorTurtle = require( "mode/turtle/turtle" ); + + var MAX_EDITABLE_SIZE = 10000; + + var DatasetEdit = Backbone.Marionette.ItemView.extend( { + + initialize: function() { + _.bindAll( this, "onCountGraphs", "onModelChanged", "onSelectDataset", + "onShownTab", "onShownEditTab", "onGraphContent", + "onSaveEdit", "onCancelEdit" ); + + this.model.on( "change", this.onModelChanged ); + this._editor = null; + + fui.vent.on( "shown.bs.tab", this.onShownTab ); + }, + + template: _.template( DatasetEditTpl ), + + ui: { + listGraphs: ".action.list-graphs", + editor: "#graph-editor", + graphName: "input.graph-name", + saveButton: "button.save-edit", + cancelButton: "button.cancel-edit" + }, + + el: "#edit .with-dataset", + + events: { + "click .list-graphs": "onCountGraphs", + "click .select-dataset": "onSelectDataset", + "click .save-edit": "onSaveEdit", + "click .cancel-edit": "onCancelEdit" + }, + + templateHelpers: { + }, + + serializeData: function() { + return this.model; + }, + + /** Alias for the model */ + dataset: function() { + return this.model; + }, + + // event handlers + + onModelChanged: function() { + if (!this.model.counting) { + this.render(); + } + }, + + onCountGraphs: function( e ) { + e.preventDefault(); + this.model.count(); + }, + + /** Event that triggers when any tab is shown */ + onShownTab: function( tab ) { + if (tab.attr("href") === "#edit") { + this.onShownEditTab(); + } + }, + + /** When the tab is show, ensure the editor element is initialised */ + onShownEditTab: function() { + this.showEditor(); + }, + + /** When rendering, only show the code mirror editor if the tab is visible */ + onRender: function() { + this.showEditor(); + }, + + /** Ensure the code mirror element is visible */ + showEditor: function() { + if (this.editorElementVisible() && this.editorNotYetInitialised()) { + this._editor = null; + this.editorElement(); + } + }, + + /** Return true if the editor container element is visible */ + editorElementVisible: function() { + return this.ui.editor.is( ":visible" ); + }, + + /** Return true if the CodeMirror element has not yet been initialised */ + editorNotYetInitialised: function() { + return this.ui.editor.is( ":not(:has(.CodeMirror))" ); + }, + + /** User has (attempted to) select a dataset */ + onSelectDataset: function( e ) { + e.preventDefault(); + var self = this; + var elem = $(e.currentTarget); + var graphName = elem.data( "graph-name" ); + var graphSize = parseInt( elem.data( "graph-size" )); + + if (graphSize > MAX_EDITABLE_SIZE) { + alert( "Sorry, that dataset is too large to load into the editor" ); + } + else { + if (this.dirtyCheck()) { + $(".nav.graphs").find( ".active" ).removeClass( "active" ); + elem.parent().addClass( "active" ); + + var gn = this.setGraphName( graphName ); + this.dataset() + .fetchGraph( gn ) + .done( self.onGraphContent ); + } + } + }, + + /** Return true if the edit buffer is not dirty, or if the user says OK */ + dirtyCheck: function() { + return true; // TODO + }, + + /** Return the DOM node representing the query editor */ + editorElement: function() { + if (!this._editor) { + this._editor = new CodeMirror( this.ui.editor.get(0), { + lineNumbers: true, + mode: "turtle" + } ); + } + return this._editor; + }, + + /** Set the graph name, return the actual name used */ + setGraphName: function( name ) { + var text = (name === "default" || name === "default graph") ? "default" : name; + + this.ui.graphName.val( text ); + + return text; + }, + + /** Get the graph name */ + graphName: function() { + return this.ui.graphName.val(); + }, + + /** Server has sent the content of the graph encoded as turtle */ + onGraphContent: function( data ) { + this.editorElement().setValue( data ); + }, + + /** User wants to save changes */ + onSaveEdit: function( e ) { + e.preventDefault(); + var self = this; + + var turtle = this.editorElement().getValue(); + this.dataset() + .putGraph( turtle, this.graphName() ) + .done( function( data ) { + var nq = parseInt( data.quadCount ); + var nt = parseInt( data.tripleCount ); + var typ = (nq > nt) ? "quad" : "triple"; + var s = (nq + nt) === 1 ? "" : "s"; + var msg = sprintf( "Added %d %s%s", nq + nt, typ, s ); + + self.showFeedback( msg, "" ); + } ) + .error( function( jqhxr, msg, err ) { + self.showFeedback( err || msg, "text-warning" ); + } ); + }, + + /** User wants to discard changes */ + onCancelEdit: function( e ) { + e.preventDefault(); + this.ui.graphName.val( "" ); + this.editorElement().setValue( "" ); + }, + + /** Show feedback from operations */ + showFeedback: function( msg, cls ) { + $(".feedback").html( sprintf( "<span class='%s'>%s</span>", cls, msg ) ); + } + + + }); + + + return DatasetEdit; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-info.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-info.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-info.js new file mode 100644 index 0000000..665725f --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-info.js @@ -0,0 +1,76 @@ +/** Component for showing detailed information about a dataset */ + +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + DatasetInfoTpl = require( "plugins/text!app/templates/dataset-info.tpl" ), + DatasetStatsView = require( "app/views/dataset-stats" ), + DatasetStatsModel = require( "app/models/dataset-stats" ); + + var DatasetInfo = Backbone.Marionette.ItemView.extend( { + + initialize: function() { + _.bindAll( this, "onModelChanged", "onCountGraphs" ); + + this.showStatistics( true ); + this.model.on( "change", this.onModelChanged ); + }, + + template: _.template( DatasetInfoTpl ), + + ui: { + stats: "#statistics", + count: ".count-graphs" + }, + + el: "#info .with-dataset", + + events: { + "click .count-graphs": "onCountGraphs" + }, + + templateHelpers: { + }, + + serializeData: function() { + return this.model; + }, + + /** Alias for the model */ + dataset: function() { + return this.model; + }, + + // event handlers + + onModelChanged: function() { + if (!this.model.counting) { + this.render(); + this.showStatistics( false ); + } + }, + + onCountGraphs: function( e ) { + e.preventDefault(); + this.model.count(); + }, + + showStatistics: function( keep ) { + var self = this; + + this.model + .statistics( keep ) + .done( function( data ) { + var statsModel = new DatasetStatsModel( self.dataset(), data ); + new DatasetStatsView( {model: statsModel} ).render(); + } ); + } + + }); + + + return DatasetInfo; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-management.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-management.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-management.js new file mode 100644 index 0000000..7f5e7d8 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-management.js @@ -0,0 +1,160 @@ +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + datasetManagementViewTpl = require( "plugins/text!app/templates/dataset-management.tpl" ); + + var DataManagementView = Backbone.Marionette.ItemView.extend( { + + initialize: function(){ + _.bindAll( this, "onRemoveDataset", "onConfirmAction", + "onDatasetRemoveSuccess", "onDatasetRemoveFail", + "onTaskStatus", "onTaskFailed" ); + + this.listenTo( this.model, "change", this.onModelChange, this ); + + fui.vent.on( "action.delete.confirm", this.onConfirmRemoveDataset ); + fui.vent.on( "action.backup.confirm", this.onConfirmBackupDataset ); + + fui.vent.on( "task.status", this.onTaskStatus ); + fui.vent.on( "task.failed", this.onTaskFailed ); + }, + + template: _.template( datasetManagementViewTpl ), + + ui: { + actionConfirmModal: "#actionConfirmModal" + }, + + el: "#dataset-management", + + events: { + "click .action.remove": "onRemoveDataset", + "click .action.confirm": "onConfirmAction", + "click .action.backup": "onBackupDataset" + }, + + templateHelpers: { + }, + + + /** If the model changes, update the summary */ + onModelChange: function( event ) { + this.render(); + }, + + /** User has requested a dataset be removed */ + onRemoveDataset: function( e ) { + e.preventDefault(); + var elem = $(e.currentTarget); + var dsId = elem.data( "ds-id" ); + var msg = sprintf( "Are you sure you want to delete dataset <code>%s</code>? This action cannot be reversed.", dsId ); + + this.showConfirmationModal( msg, dsId, "action.delete.confirm" ); + }, + + /** User has requested a dataset be backed up */ + onBackupDataset: function( e ) { + e.preventDefault(); + var elem = $(e.currentTarget); + var dsId = elem.data( "ds-id" ); + var msg = sprintf( "Are you sure you want to create a backup of dataset <code>%s</code>? This action may take some time to complete", dsId ); + + this.showConfirmationModal( msg, dsId, "action.backup.confirm" ); + }, + + /** Show a generic modal confirmation */ + showConfirmationModal: function( msg, dsId, eventId ) { + this.ui.actionConfirmModal + .find( ".modal-body p" ) + .html( msg ); + + this.ui.actionConfirmModal + .find( ".action.confirm" ) + .data( "ds-id", dsId ) + .data( "event-id", eventId ); + + this.clearFeedback(); + this.ui.actionConfirmModal.modal( 'show' ); + }, + + /** Generic response to confirming the current modal dialogue */ + onConfirmAction: function( e ) { + e.preventDefault(); + var elem = $(e.currentTarget); + var dsId = elem.data( "ds-id" ); + var eventId = elem.data( "event-id" ); + + fui.vent.trigger( eventId, dsId ); + this.ui.actionConfirmModal.modal( 'hide' ); + }, + + /** User has confirmed that the dataset can be deleted */ + onConfirmRemoveDataset: function( dsId ) { + var self = this; + + fui.models + .fusekiServer + .dataset( dsId ) + .deleteDataset() + .done( function( data ) {self.onDatasetRemoveSuccess( data, dsId );} ) + .error( function( jqxhr, msg, err ) {self.onDatasetRemoveFail( jqxhr, msg, err, dsId );} ); + }, + + onDatasetRemoveSuccess: function( data, dsId ) { + console.log( "deleted dataset" ); + }, + + /** Removing the dataset did not work: notify the user */ + onDatasetRemoveFail: function( jqxhr, msg, err, dsId ) { + this.feedbackArea( dsId ) + .html( sprintf( "<p class='text-warning'>Sorry, removing dataset %s did not work, because: '%s'</p>", dsId, err || msg ) ); + }, + + /** User has confirmed backing up the dataset */ + onConfirmBackupDataset: function( dsId ) { + var self = this; + + fui.models + .fusekiServer + .dataset( dsId ) + .backupDataset(); + }, + + /** Remove any current feedback content */ + clearFeedback: function() { + $(".action.feedback").empty(); + }, + + /** Long running task has updated status */ + onTaskStatus: function( status ) { + var task = status.task; + var msg = sprintf( "<p>Task <strong>%s</strong> started at %s%s</p>", + task.operationType, + task.taskDescription.started, + status.finished ? sprintf( ", finished at %s", status.finished ) : " – ongoing" ); + + this.feedbackArea( status.dsId ) + .html( msg ); + }, + + /** Long running task has failed to start */ + onTaskFailed: function( status ) { + this.feedbackArea( status.dsId ) + .html( sprintf( "<p class='text-danger'>Task %s failed: '%s'</p>", task.operationType, task.errorMessage )); + }, + + /** Find the feedback area for a particular dataset */ + feedbackArea: function( dsId ) { + return $(sprintf( ".action[data-ds-id='%s']", dsId ) ) + .parent() + .siblings(".action.feedback"); + } + + }); + + + return DataManagementView; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selection-list.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-selection-list.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selection-list.js new file mode 100644 index 0000000..ccad19d --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selection-list.js @@ -0,0 +1,58 @@ +/** + * This view presents a list of the available datasets for the user to interact + * with. + */ + +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + datasetSelectionListTemplate = require( "plugins/text!app/templates/dataset-selection-list.tpl" ); + + var DatasetSelectionListView = Backbone.Marionette.ItemView.extend( { + initialize: function(){ +// _.bindAll(this, "onFilter", "onModelChange"); + this.listenTo( this.model, "change", this.onModelChange, this ); + }, + + template: _.template( datasetSelectionListTemplate ), + + el: "#dataset-selection-list", + + ui: { + }, + + events: { +// "change #independent-variable-selection": "selectVariable", +// "click a.action.filter": "onFilter" + }, + + templateHelpers: { + }, + +// /** Update the model when the user changes the selection */ +// selectVariable: function( event ) { +// this.model.set( "independentVarSelection", this.ui.variableSelection.val() ); +// }, +// +// /** User wants to open the filter dialog */ +// onFilter: function( event ) { +// var varModel = bgViz.models.variablesConfig.independentVar(); +// var rangeType = varModel.component.range().rangeType(); +// var viewName = rangeType.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); +// +// bgViz.layouts.filterDialog.showFilter( viewName, varModel ); +// }, + + /** If the model changes, update the summary */ + onModelChange: function( event ) { +// this.ui.summary.html( this.model.independentVar().component.range().summarise() ); + } + + }); + + + return DatasetSelectionListView; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selector.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-selector.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selector.js new file mode 100644 index 0000000..f14a747 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-selector.js @@ -0,0 +1,84 @@ +/** + * Reusable component that encapsulates selecting a dataset to work on in a given page. + * Takes the FusekiServer as a model, and populates a select control to choose one of the + * current datasets. If the dataset changes, this view will update the `selectedDatasetName` + * on the model, and trigger the event `dataset.changed`. + **/ + +define( + function( require ) { + "use strict"; + + var Marionette = require( "marionette" ), + Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + sprintf = require( "sprintf" ), + datasetSelectorTemplate = require( "plugins/text!app/templates/dataset-selector.tpl" ); + + var DatasetSelectorView = Backbone.Marionette.ItemView.extend( { + + initialize: function(){ + this.listenTo( this.model, "change", this.render, this ); + }, + + template: _.template( datasetSelectorTemplate ), + + el: ".dataset-selector-container", + + ui: { + select: ".dataset-selector select" + }, + + events: { + "change .dataset-selector select": "onChangeDataset" + }, + + /** + * After rendering, set up the dataset picker and notify the rest of the + * app if the default dataset name is known. + */ + onRender: function() { + var selector = $('.selectpicker'); + selector.selectpicker('refresh'); + + if (selector.val()) { + this.unHideDatasetElements(); + this.onChangeDataset(); + } + }, + + /** + * Respond to a change in the dataset name selection by updating + * the underlying model. TODO: should also update the application + * URL. + */ + onChangeDataset: function( e ) { + var newDatasetName = this.ui.select.val(); + this.model.set( "selectedDatasetName", newDatasetName ); + this.notifyDatasetName( newDatasetName ); + }, + + /** + * Ensure that elements that should be visible when a dataset is known + * are not hidden, and vice-versa. + */ + unHideDatasetElements: function() { + $(".no-dataset").addClass( "hidden" ); + $(".with-dataset").removeClass( "hidden" ); + }, + + /** Trigger an event to notify other components that the dataset + * name has been selected. + */ + notifyDatasetName: function( dsName ) { + fui.vent.trigger( "dataset.changed", dsName || this.ui.select.val() ); + } + + + }); + + + return DatasetSelectorView; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-simple-create.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-simple-create.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-simple-create.js new file mode 100644 index 0000000..dd189c1 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-simple-create.js @@ -0,0 +1,100 @@ +/** Component for creating a new dataset with a few simple options */ + +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + DatasetSimpleCreateTpl = require( "plugins/text!app/templates/dataset-simple-create.tpl" ); + + var DatasetSimpleCreate = Backbone.Marionette.ItemView.extend( { + + initialize: function() { + _.bindAll( this, "onCommitSimple", "clearWarnings" ); + }, + + template: _.template( DatasetSimpleCreateTpl ), + + ui: { + }, + + el: "#dataset-simple-create", + + events: { + "click a.action.commit.simple": "onCommitSimple", + "submit form": "onCommitSimple", + "keydown input[name=dbName]": "clearWarnings" + }, + + templateHelpers: { + }, + + serializeData: function() { + return this.model; + }, + + // event handlers + + onCommitSimple: function( e ) { + e.preventDefault(); + + if (this.validateSimpleForm()) { + var options = $("#simple-edit form").serializeArray(); + fui.models.fusekiServer.updateOrCreateDataset( null, options ) + .done( this.showDataManagementPage ) + .fail( this.showFailureMessage ); + } + }, + +// onCommitUpload: function( e ) { +// e.preventDefault(); +// +// if (this.validateUploadForm()) { +// $("#uploadForm").ajaxSubmit( { +// success: this.showDataManagementPage, +// error: this.showFailureMessage +// }); +// } +// }, +// + showDataManagementPage: function( e ) { + location = "?tab=datasets"; + }, + + /** Todo: need to do a better job of responding to errors */ + showFailureMessage: function( jqXHR, textStatus, errorThrown ) { + $(".errorOutput").html( sprintf( "<p class='has-error'>Sorry, that didn't work because:</p><pre>%s</pre>", errorThrown || textStatus ) ); + }, + + /** Clear current warning states */ + clearWarnings: function() { + this.clearValidation(); + $(".errorOutput").empty(); + }, + + // validation + + validateSimpleForm: function() { + this.clearValidation(); + + if (! $("input[name=dbName]").val()) { + $(".dbNameValidation").removeClass("hidden") + .parents(".form-group" ) + .addClass( "has-error" ); + return false; + } + + return true; + }, + + clearValidation: function() { + $(".has-error").removeClass( "has-error" ); + $(".has-warning").removeClass( "has-warning" ); + } + + }); + + + return DatasetSimpleCreate; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/dataset-stats.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/dataset-stats.js b/jena-fuseki2/src/main/webapp/js/app/views/dataset-stats.js new file mode 100644 index 0000000..f2dc198 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/dataset-stats.js @@ -0,0 +1,41 @@ +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ), + datasetStatsViewTpl = require( "plugins/text!app/templates/dataset-stats.tpl" ); + + var DatasetStatsView = Backbone.Marionette.ItemView.extend( { + initialize: function() { + _.bindAll( this, "onShowTab" ); + + fui.vent.on( "shown.bs.tab", this.onShowTab ); + }, + + template: _.template( datasetStatsViewTpl ), + + ui: { + }, + + el: "#statistics", + + modelEvents: { + 'change': "modelChanged" + }, + + modelChanged: function() { + this.render(); + }, + + onShowTab: function( tab ) { + if (tab.attr("href") === "#info") { + this.model.refresh(); + } + } + + }); + + + return DatasetStatsView; + } +); http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/webapp/js/app/views/datasets-dropdown-list.js ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/webapp/js/app/views/datasets-dropdown-list.js b/jena-fuseki2/src/main/webapp/js/app/views/datasets-dropdown-list.js new file mode 100644 index 0000000..92b2bf4 --- /dev/null +++ b/jena-fuseki2/src/main/webapp/js/app/views/datasets-dropdown-list.js @@ -0,0 +1,43 @@ +define( + function( require ) { + var Backbone = require( "backbone" ), + _ = require( "underscore" ), + fui = require( "app/fui" ); + + var DatasetDropDownListView = Backbone.Marionette.ItemView.extend( { + initialize: function(){ + }, + + template:"", + + el: "ul.dropdown-menu.dataset-list", + + ui: { + }, + + events: { + }, + + render: function() { + var e = $(this.el).empty(); + _.each( this.model, function( ds ) { + e.append( sprintf( "<li><a class='action select-dataset'href='?ds=%s'>%s</a></li>", ds.name(), ds.name() )); + } ); + }, + + /** Change the currently selected dataset name. If required, notify other units via an event */ + setCurrentDatasetName: function( dsName, notify ) { + if (dsName) { + $(".current-dataset").text( dsName ); + } + + if (notify) { + fui.vent.trigger( "views.datasets-dropdown-list.dataset-changed", dsName ) + } + } + }); + + + return DatasetDropDownListView; + } +);
