source: SRUAggregator/trunk/src/main/resources/assets/js/main.jsx @ 6181

Last change on this file since 6181 was 6181, checked in by emanuel.dima@uni-tuebingen.de, 9 years ago
  1. beta-46: no more tables in clarinservices module, icon included
File size: 18.8 KB
Line 
1/** @jsx React.DOM */
2(function() {
3"use strict";
4
5var VERSION = window.MyAggregator.VERSION = "v.2.0.0-beta-46";
6
7var URLROOT = window.MyAggregator.URLROOT =
8        window.location.pathname.substring(0, window.location.pathname.indexOf("/",2)) ||
9        "/Aggregator";
10
11var PT = React.PropTypes;
12
13var ErrorPane = window.MyReact.ErrorPane;
14var AggregatorPage = window.MyAggregator.AggregatorPage;
15
16/**
17The FCS Aggregator UI is based on reactjs.
18- index.html: describes the general page structure, with a push-down footer;
19  on that structure the Main and Footer components are plugged.
20- main.jsx: defines the simple top components (Main, HelpPage, AboutPage, StatisticsPage)
21- search.jsx: defines
22        - the Corpora store of collections
23        - the AggregatorPage component which deals with search and displays the search results
24- corpora.jsx: defines the CorpusView, rendered when the user views the available collections
25- components.jsx: various general usage React components
26
27The top-most component, Main, tracks of the window's location URL and, depending on the value,
28  renders various components inside its frame:
29        - AggregatorPage is the view corresponding to the normal search UI (search bar and all)
30          This is the most complex component.
31        - HelpPage renders the help page
32        - About renders the about page
33        - Statistics renders the stats page
34        - another URL, /Aggregator/embed, determines Main and AggregatorPage to render just the search bar.
35          The embedded view is supposed to work like a YouTube embedded clip.
36*/
37
38var Main = React.createClass({
39        componentWillMount: function() {
40                routeFromLocation.bind(this)();
41        },
42
43        getInitialState: function () {
44                return {
45                        navbarCollapse: false,
46                        navbarPageFn: this.renderAggregator,
47                        // navbarPageFn: this.renderStatistics,
48                        errorMessages: [],
49                };
50        },
51
52        error: function(errObj) {
53                var err = "";
54                if (typeof errObj === 'string' || errObj instanceof String) {
55                        err = errObj;
56                } else if (typeof errObj === 'object' && errObj.statusText) {
57                        console.log("ERROR: jqXHR = ", errObj);
58                        err = errObj.statusText;
59                } else {
60                        return;
61                }
62
63                var that = this;
64                var errs = this.state.errorMessages.slice();
65                errs.push(err);
66                this.setState({errorMessages: errs});
67
68                setTimeout(function() {
69                        var errs = that.state.errorMessages.slice();
70                        errs.shift();
71                        that.setState({errorMessages: errs});
72                }, 10000);
73        },
74
75        ajax: function(ajaxObject) {
76                var that = this;
77                if (!ajaxObject.error) {
78                        ajaxObject.error = function(jqXHR, textStatus, error) {
79                                if (jqXHR.readyState === 0) {
80                                        that.error("Network error, please check your internet connection");
81                                } else if (jqXHR.responseText) {
82                                        that.error(jqXHR.responseText + " ("+error+")");
83                                } else  {
84                                        that.error(error + " ("+textStatus+")");
85                                }
86                                console.log("ajax error, jqXHR: ", jqXHR);
87                        };
88                }
89                // console.log("ajax", ajaxObject);
90                jQuery.ajax(ajaxObject);
91        },
92
93        toggleCollapse: function() {
94                this.setState({navbarCollapse: !this.state.navbarCollapse});
95        },
96
97        renderAggregator: function() {
98                return <AggregatorPage ajax={this.ajax} error={this.error} />;
99        },
100
101        renderHelp: function() {
102                return <HelpPage />;
103        },
104
105        renderAbout: function() {
106                return <AboutPage/>;
107        },
108
109        renderStatistics: function() {
110                return <StatisticsPage ajax={this.ajax} />;
111        },
112
113        renderEmbedded: function() {
114                return <AggregatorPage ajax={this.ajax} embedded={true}/>;
115        },
116
117        getPageFns: function() {
118                return {
119                        '': this.renderAggregator,
120                        'help': this.renderHelp,
121                        'about': this.renderAbout,
122                        'stats': this.renderStatistics,
123                        'embed': this.renderEmbedded,
124                };
125        },
126
127        gotoPage: function(doPushHistory, pageFnName) {
128                var pageFn = this.getPageFns()[pageFnName];
129                if (this.state.navbarPageFn !== pageFn) {
130                        if (doPushHistory) {
131                                window.history.pushState({page:pageFnName}, '', URLROOT+"/"+pageFnName);
132                        }
133                        this.setState({navbarPageFn: pageFn});
134                        // console.log("new page: " + document.location + ", name: " + pageFnName);
135                }
136        },
137
138        toAggregator: function(doPushHistory) { this.gotoPage(doPushHistory, ''); },
139        toHelp: function(doPushHistory) { this.gotoPage(doPushHistory, 'help'); },
140        toAbout: function(doPushHistory) { this.gotoPage(doPushHistory, 'about'); },
141        toStatistics: function(doPushHistory) { this.gotoPage(doPushHistory, 'stats'); },
142        toEmbedded: function(doPushHistory) { this.gotoPage(doPushHistory, 'embed'); },
143
144        renderLogin: function() {
145                return false;
146                // return  <li className="unauthenticated">
147                //                      <a href="login" tabIndex="-1"><span className="glyphicon glyphicon-log-in"></span> LOGIN</a>
148                //              </li>;
149        },
150
151        renderCollapsible: function() {
152                var classname = "navbar-collapse collapse " + (this.state.navbarCollapse?"in":"");
153                return (
154                        <div className={classname}>
155                                <ul className="nav navbar-nav">
156                                        <li className={this.state.navbarPageFn === this.renderAggregator ? "active":""}>
157                                                <a className="link" tabIndex="-1" onClick={this.toAggregator.bind(this, true)}>Aggregator</a>
158                                        </li>
159                                        <li className={this.state.navbarPageFn === this.renderHelp ? "active":""}>
160                                                <a className="link" tabIndex="-1" onClick={this.toHelp.bind(this, true)}>Help</a>
161                                        </li>
162                                </ul>
163                                <ul className="nav navbar-nav navbar-right">
164                                        <li> <div id="clarinservices" style={{padding:4}}/> </li>
165                                        {this.renderLogin()}
166                                </ul>
167                        </div>
168                );
169        },
170
171        renderTop: function() {
172                if (this.state.navbarPageFn === this.renderEmbedded) {
173                        return false;
174                }
175                return  (
176                        <div>
177                                <div className="navbar navbar-default navbar-static-top" role="navigation">
178                                        <div className="container">
179                                                <div className="navbar-header">
180                                                        <button type="button" className="navbar-toggle" onClick={this.toggleCollapse}>
181                                                                <span className="sr-only">Toggle navigation</span>
182                                                                <span className="icon-bar"></span>
183                                                                <span className="icon-bar"></span>
184                                                                <span className="icon-bar"></span>
185                                                        </button>
186                                                        <a className="navbar-brand" href={URLROOT} tabIndex="-1">
187                                                                <img width="28px" height="28px" src="img/magglass1.png"/>
188                                                                <header className="inline"> Content Search </header>
189                                                        </a>
190                                                </div>
191                                                {this.renderCollapsible()}
192                                        </div>
193                                </div>
194
195                                <ErrorPane errorMessages={this.state.errorMessages} />
196
197                        </div>
198                );
199        },
200
201        render: function() {
202                return  (
203                        <div>
204                                <div> { this.renderTop() } </div>
205
206                                <div id="push">
207                                        <div className="container">
208                                                {this.state.navbarPageFn()}
209                                        </div>
210                                        <div className="top-gap" />
211                                </div>
212                        </div>
213                );
214        }
215});
216
217
218var StatisticsPage = React.createClass({
219        propTypes: {
220                ajax: PT.func.isRequired,
221        },
222
223        getInitialState: function () {
224                return {
225                        stats: {},
226                        activeTab: 0,
227                        // searchStats: {},
228                        // lastScanStats: {},
229                };
230        },
231
232        componentDidMount: function() {
233                this.refreshStats();
234        },
235
236        refreshStats: function() {
237                this.props.ajax({
238                        url: 'rest/statistics',
239                        success: function(json, textStatus, jqXHR) {
240                                this.setState({stats: json});
241                                // console.log("stats:", json);
242                        }.bind(this),
243                });
244        },
245
246        renderWaitTimeSecs: function(t) {
247                var hue = t * 4;
248                if (hue > 120) {
249                        hue = 120;
250                }
251                var a = hue/120;
252                hue = 120 - hue;
253                var shue = "hsla("+hue+",100%,80%,"+a+")";
254                return  <span className="badge" style={{backgroundColor:shue, color:"black"}}>
255                                        {t.toFixed(3)}s
256                                </span>;
257        },
258
259        renderCollections: function(colls) {
260                return  <div style={{marginLeft:40}}>
261                                        { colls.length === 0 ?
262                                                <div style={{color:"#a94442"}}>NO collections found</div>
263                                                :
264                                                <div>
265                                                        {colls.length} root collection(s):
266                                                        <ul className='list-unstyled' style={{marginLeft:40}}>
267                                                                { colls.map(function(name, i) { return <div key={i}>{name}</div>; }) }
268                                                        </ul>
269                                                </div>
270                                        }
271                                </div>;
272        },
273
274        renderDiagnostic: function(d) {
275                var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : "");
276                return  <div key={d.diagnostic.uri}>
277                                        <div className={classes} >
278                                                <div>
279                                                        { d.counter <= 1 ? false :
280                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
281                                                                        <span className="badge" style={{backgroundColor:'#ae7241'}}>x {d.counter}</span>
282                                                                </div>
283                                                        }
284                                                        Diagnostic: {d.diagnostic.message}: {d.diagnostic.diagnostic}
285                                                </div>
286                                                <div>Context: <a href={d.context}>{d.context}</a></div>
287                                        </div>
288                                </div>;
289        },
290
291        renderError: function(e) {
292                var xc = e.exception;
293                return  <div key={xc.message}>
294                                        <div className="inline alert alert-danger" role="alert">
295                                                <div>
296                                                        { e.counter <= 1 ? false :
297                                                                <div className="inline" style={{margin:"5px 5px 5px 5px"}}>
298                                                                        <span className="badge" style={{backgroundColor:'#c94442'}}>x {e.counter} </span>
299                                                                </div>
300                                                        }
301                                                        Exception: {xc.message}
302                                                </div>
303                                                <div>Context: <a href={e.context}>{e.context}</a></div>
304                                                { xc.cause ? <div>Caused by: {xc.cause}</div> : false}
305                                        </div>
306                                </div>;
307        },
308
309        renderEndpoint: function(isScan, endpoint) {
310                var stat = endpoint[1];
311                var errors = _.values(stat.errors);
312                var diagnostics = _.values(stat.diagnostics);
313                return <div style={{marginTop:10}} key={endpoint[0]}>
314                                        <ul className='list-inline list-unstyled' style={{marginBottom:0}}>
315                                                <li>
316                                                        { stat.version == "LEGACY" ?
317                                                                <span style={{color:'#a94442'}}>legacy <i className="glyphicon glyphicon-thumbs-down"></i> </span>
318                                                                : <span style={{color:'#3c763d'}}><i className="glyphicon glyphicon-thumbs-up"></i> </span>
319                                                        }
320                                                        { " "+endpoint[0] }
321                                                </li>
322                                        </ul>
323                                        <div style={{marginLeft:40}}>
324                                        { isScan ?
325                                                <div>Max concurrent scan requests:{" "} {stat.maxConcurrentRequests} </div> :
326                                                <div>Max concurrent search requests:{" "} {stat.maxConcurrentRequests} </div>
327                                        }
328                                        </div>
329                                        <div style={{marginLeft:40}}>
330                                                <span>{stat.numberOfRequests}</span> request(s),
331                                                average:{this.renderWaitTimeSecs(stat.avgExecutionTime)},
332                                                max: {this.renderWaitTimeSecs(stat.maxExecutionTime)}
333                                        </div>
334                                        { isScan ? this.renderCollections(stat.rootCollections) : false }
335                                        {       (errors && errors.length) ?
336                                                <div className='inline' style={{marginLeft:40}}>
337                                                        { errors.map(this.renderError) }
338                                                </div> : false
339                                        }
340                                        {       (diagnostics && diagnostics.length) ?
341                                                <div className='inline' style={{marginLeft:40}}>
342                                                        { diagnostics.map(this.renderDiagnostic) }
343                                                </div> : false
344                                        }
345                                </div>;
346        },
347
348        renderInstitution: function(isScan, inst) {
349                return  <div style={{marginTop:30}} key={inst[0]}>
350                                        <h4>{inst[0]}</h4>
351                                        <div style={{marginLeft:20}}> {_.pairs(inst[1]).map(this.renderEndpoint.bind(this, isScan)) }</div>
352                                </div>;
353        },
354
355        renderStatistics: function(stats) {
356                return  <div className="container statistics" style={{marginTop:20}}>
357                                        <div>
358                                                <div>Start date: {new Date(stats.date).toLocaleString()}</div>
359                                                <div>Timeout: {" "}<kbd>{stats.timeout} seconds</kbd></div>
360                                        </div>
361                                        <div> { _.pairs(stats.institutions).map(this.renderInstitution.bind(this, stats.isScan)) } </div>
362                                </div>
363                                 ;
364        },
365
366        setTab: function(idx) {
367                this.setState({activeTab:idx});
368        },
369
370        render: function() {
371                return  (
372                        <div>
373                                <div className="top-gap">
374                                        <h1>Statistics</h1>
375                                        <p/>
376                                        <div role="tabpanel">
377                                                <ul className="nav nav-tabs" role="tablist">
378                                                        { _.pairs(this.state.stats).map(function(st, idx){
379                                                                        var classname = idx === this.state.activeTab ? "active":"";
380                                                                        return  <li role="presentation" className={classname} key={st[0]}>
381                                                                                                <a href="#" role="tab" onClick={this.setTab.bind(this, idx)}>{st[0]}</a>
382                                                                                        </li>;
383                                                                }.bind(this))
384                                                        }
385                                                </ul>
386
387                                                <div className="tab-content">
388                                                        { _.pairs(this.state.stats).map(function(st, idx){
389                                                                        var classname = idx === this.state.activeTab ? "tab-pane active" : "tab-pane";
390                                                                        return  <div role="tabpanel" className={classname} key={st[0]}>
391                                                                                                {this.renderStatistics(st[1])}
392                                                                                        </div>;
393                                                                }.bind(this))
394                                                        }
395                                                </div>
396                                        </div>
397                                </div>
398                        </div>
399                        );
400        },
401});
402
403var HelpPage = React.createClass({
404        openHelpDesk: function() {
405                window.open('http://support.clarin-d.de/mail/form.php?queue=Aggregator&lang=en',
406                        '_blank', 'height=560,width=370');
407        },
408
409        render: function() {
410                return  (
411                        <div>
412                                <div className="top-gap">
413                                        <h1>Help</h1>
414                                        <h3>Performing search in FCS corpora</h3>
415                                        <p>To perform simple keyword search in all CLARIN-D Federated Content Search centres
416                                        and their corpora, go to the search field at the top of the page,
417                                        enter your query, and click 'search' button or press the 'Enter' key.</p>
418
419                                        <p>When the search starts, the page will start filling in with the corpora responses.
420                                        After the entire search process has ended you have the option to download the results
421                                        in various formats.
422                                        </p>
423
424                                        <p>If you are particularly interested in the results returned by a corpus, you have
425                                        the option to focus only on the results of that corpus, by clicking on the 'Watch' button.
426                                        In this view mode you can also download the results of use the WebLicht processing services
427                                        to further analyse the results.</p>
428
429
430                                        <h3>Adjusting search criteria</h3>
431                                        <p>The FCS Aggregator makes possible to select specific corpora based on their name
432                                        or language and to specify the number of search results (hits) per corpus per page.
433                                        The user interface controls that allows to change these options are located
434                                        right below the search fiels on the main page. The current options are
435                                        to filter resources based on their language, to select specific resources, and
436                                        to set the maximum number of hits.</p>
437
438
439                                        <h3>More help</h3>
440                                        <p>More detailed information on using FCS Aggregator is available at the &nbsp;
441                                        <a href="http://weblicht.sfs.uni-tuebingen.de/weblichtwiki/index.php/FCS_Aggregator">
442                                                Aggregator wiki page
443                                        </a>.
444                                        If you still cannot find an answer to your question,
445                                        or if want to send a feedback, you can write to Clarin-D helpdesk: </p>
446                                        <button type="button" className="btn btn-default btn-lg" onClick={this.openHelpDesk} >
447                                                <span className="glyphicon glyphicon-question-sign" aria-hidden="true"></span>
448                                                &nbsp;HelpDesk
449                                        </button>
450                                </div>
451                        </div>
452                );
453        }
454});
455
456var AboutPage = React.createClass({
457        render: function() {
458                return  <div>
459                                        <div className="top-gap">
460                                                <h1 style={{padding:15}}>About</h1>
461
462                                                <div className="col-md-6">
463                                                <h3>Technology</h3>
464
465                                                <p>The Aggregator uses the following software components:</p>
466
467                                                <ul>
468                                                        <li>
469                                                                <a href="http://dropwizard.io/">Dropwizard</a>{" "}
470                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
471                                                        </li>
472                                                        <li>
473                                                                <a href="http://eclipse.org/jetty/">Jetty</a>{" "}
474                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
475                                                        </li>
476                                                        <li>
477                                                                <a href="http://jackson.codehaus.org/">Jackson</a>{" "}
478                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
479                                                        </li>
480                                                        <li>
481                                                                <a href="https://jersey.java.net/">Jersey</a>{" "}
482                                                                (<a href="https://jersey.java.net/license.html#/cddl">CCDL 1.1</a>)
483                                                        </li>
484                                                        <li>
485                                                                <a href="https://github.com/optimaize/language-detector">Optimaize Language Detector</a>{" "}
486                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
487                                                        </li>
488                                                        <li>
489                                                                <a href="http://poi.apache.org/">Apache POI</a>{" "}
490                                                                (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>)
491                                                        </li>
492                                                </ul>
493
494                                                <ul>
495                                                        <li>
496                                                                <a href="http://facebook.github.io/react/">React</a>{" "}
497                                                                (<a href="https://github.com/facebook/react/blob/master/LICENSE">BSD license</a>)
498                                                        </li>
499                                                        <li>
500                                                                <a href="http://getbootstrap.com/">Bootstrap</a>{" "}
501                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
502                                                        </li>
503                                                        <li>
504                                                                <a href="http://jquery.com/">jQuery</a>{" "}
505                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT license</a>)
506                                                        </li>
507                                                        <li>
508                                                                <a href="http://glyphicons.com/">GLYPHICONS free</a>{" "}
509                                                                (<a href="https://creativecommons.org/licenses/by/3.0/">CC-BY 3.0</a>)
510                                                        </li>
511                                                        <li>
512                                                                <a href="http://fortawesome.github.io/Font-Awesome/">FontAwesome</a>{" "}
513                                                                (<a href="http://opensource.org/licenses/mit-license.html">MIT</a>, <a href="http://scripts.sil.org/OFL">SIL Open Font License</a>)
514                                                        </li>
515                                                </ul>
516
517                                                <p>The content search icon is made by
518                                                        <a href="http://www.freepik.com" title="Freepik"> Freepik </a>
519                                                        from
520                                                        <a href="http://www.flaticon.com" title="Flaticon"> www.flaticon.com </a>
521                                                        and licensed under
522                                                        <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0"> CC BY 3.0 </a>
523                                                </p>
524                                                </div>
525
526                                                <div className="col-md-6">
527                                                <h3>Statistics</h3>
528                                                <button type="button" className="btn btn-default btn-lg" onClick={function() {main.toStatistics(true);}} >
529                                                        <span className="glyphicon glyphicon-cog" aria-hidden="true"> </span>
530                                                        View server log
531                                                </button>
532                                                </div>
533                                        </div>
534                                </div>;
535        }
536});
537
538var Footer = React.createClass({
539        toAbout: function(e) {
540                main.toAbout(true);
541                e.preventDefault();
542                e.stopPropagation();
543        },
544
545        render: function() {
546                return (
547                        <div className="container" style={{textAlign:'center'}}>
548                                <div className="row">
549                                        <div style={{position:'relative', float:'left'}}>
550                                                <div className="leftist" style={{position:'absolute'}}>
551                                                        <div>
552                                                                <a title="about" href="about" onClick={this.toAbout}>About</a>
553                                                        </div>
554                                                        <div style={{color:'#777'}}>{VERSION}</div>
555                                                </div>
556                                        </div>
557                                        <a title="CLARIN ERIC" href="https://www.clarin.eu/">
558                                                <img src="img/clarindLogo.png" alt="CLARIN ERIC logo" style={{height:60}}/>
559                                        </a>
560                                        <div style={{position:'relative', float:'right'}}>
561                                                <div className="rightist" style={{position:'absolute', right:'0'}}>
562                                                        <a title="contact" href="mailto:fcs@clarin.eu">Contact</a>
563                                                </div>
564                                        </div>
565                                </div>
566                        </div>
567                );
568        }
569});
570
571function isEmbeddedView() {
572        var path = window.location.pathname.split('/');
573        return (path.length >= 3 && path[2] === 'embed');
574}
575
576function endsWith(str, suffix) {
577    return str.indexOf(suffix, str.length - suffix.length) !== -1;
578}
579
580var routeFromLocation = function() {
581        // console.log("routeFromLocation: " + document.location);
582        if (!this) throw "routeFromLocation must be bound to main";
583        var path = window.location.pathname.split('/');
584        if (path.length === 3) {
585                var p = path[2];
586                if (p === 'help') {
587                        this.toHelp(false);
588                } else if (p === 'about') {
589                        this.toAbout(false);
590                } else if (p === 'stats') {
591                        this.toStatistics(false);
592                } else if (p === 'embed') {
593                        this.toEmbedded(false);
594                } else {
595                        this.toAggregator(false);
596                }
597        } else {
598                this.toAggregator(false);
599        }
600};
601
602var main = React.render(<Main />,  document.getElementById('body'));
603if (!isEmbeddedView()) {
604        React.render(<Footer />, document.getElementById('footer') );
605} else if (jQuery) {
606        jQuery("#footer").remove();
607}
608
609window.onpopstate = routeFromLocation.bind(main);
610window.MyAggregator.main = main;
611
612})();
Note: See TracBrowser for help on using the repository browser.