[Pcsclite-git-commit] [website] 03/03: Reader selection: web application to select readers

Ludovic Rousseau rousseau at moszumanska.debian.org
Sun Aug 9 15:53:39 UTC 2015


This is an automated email from the git hooks/post-receive script.

rousseau pushed a commit to branch master
in repository website.

commit 56509db3ddddf6cb6aad91779989187e98ef1de3
Author: Ludovic Rousseau <ludovic.rousseau at free.fr>
Date:   Sun Aug 9 17:52:23 2015 +0200

    Reader selection: web application to select readers
    
    Initial commit with a beta version.
---
 convert2json.py                  | 112 ++++++++++++
 select_readers/css/jumbotron.css |  15 ++
 select_readers/index.html        | 147 +++++++++++++++
 select_readers/js/main.js        | 376 +++++++++++++++++++++++++++++++++++++++
 select_readers/update.sh         |  15 ++
 5 files changed, 665 insertions(+)

diff --git a/convert2json.py b/convert2json.py
new file mode 100755
index 0000000..cb8f4f2
--- /dev/null
+++ b/convert2json.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""
+#    convert2json.py generate a JSON list of readers
+#    Copyright (C) 2015  Ludovic Rousseau
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License along
+#    with this program; if not, write to the Free Software Foundation, Inc.,
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+
+# $Id$
+
+from matrix import parse_ini
+import json
+
+path = "ccid/readers/"
+debug = False
+
+
+def convert_reader(reader):
+    """ Return a reader with the values in decimal """
+
+    new_reader = dict()
+
+    for f in reader:
+        v = reader[f]
+
+        new_reader[f] = v
+
+        # convert simple HEX values
+        if f in ["dwSynchProtocols", "bMaxSlotIndex", "bPINSupport",
+                 "dwFeatures", "idProduct", "wLcdLayout",
+                 "bDescriptorType", "bLength", "bClassEnvelope",
+                 "dwMechanical", "bVoltageSupport",
+                 "bClassGetResponse", "idVendor"]:
+            new_reader[f] = int(v, 16)
+
+        # convert special HEX value:
+        # bInterfaceClass: 0x0B [Chip Card Interface Device Class (CCID)]
+        if f in ["bInterfaceClass"]:
+            new_reader[f] = int(v.split(" ")[0], 16)
+
+        # Very special case
+        if f in ["dwProtocols"]:
+            new_reader[f] = (int(v.split(" ")[0], 16) * 2**16) + int(v.split(" ")[1], 16)
+
+        # convert INT values
+        if f in ["bAlternateSetting", "bInterfaceNumber",
+                 "bInterfaceProtocol", "bInterfaceSubClass",
+                 "bMaxCCIDBusySlots", "bNumEndpoints", "dwMaxIFSD"]:
+            new_reader[f] = int(v)
+
+        # convert special INT values
+        if f in ["bNumClockSupported", "bNumDataRatesSupported",
+                 "dwDataRate", "dwMaxDataRate", "dwMaxCCIDMessageLength"]:
+            new_reader[f] = int(v.split(" ")[0])
+
+        # convert MHz values
+        if f in ["dwDefaultClock", "dwMaximumClock"]:
+            new_reader[f] = int(float(v.split(" ")[0]) * 1000 * 1000)
+
+    # default image if needed
+    if "image" not in reader:
+        new_reader["image"] = "no_image.png"
+
+    return new_reader
+
+
+def convert_readers(section, readers, features_set):
+    r = parse_ini(path, section)
+    for reader in r:
+        readers[reader] = convert_reader(r[reader])
+        readers[reader]["section"] = section
+        if 'features' in r[reader]:
+            features = r[reader]['features']
+            for feature in features:
+                # Do not include the "Multi interface reader"
+                if "interface" in feature:
+                    continue
+                features_set.add(feature)
+
+
+def convert_all_readers():
+    readers = dict()
+    features = set()
+
+    if (debug):
+        convert_readers("foo", readers, features)
+        print "var readers = " + json.dumps(readers, sort_keys=True, indent=4) + ";"
+        print "var readers_features = " + json.dumps(list(features), indent=4) + ";"
+    else:
+        convert_readers("supported", readers, features)
+        convert_readers("shouldwork", readers, features)
+        convert_readers("unsupported", readers, features)
+        convert_readers("disabled", readers, features)
+
+        print "var readers = " + json.dumps(readers, sort_keys=True) + ";"
+        print "var readers_features = " + json.dumps(sorted(list(features))) + ";"
+
+if __name__ == "__main__":
+    convert_all_readers()
diff --git a/select_readers/css/jumbotron.css b/select_readers/css/jumbotron.css
new file mode 100644
index 0000000..ed13e08
--- /dev/null
+++ b/select_readers/css/jumbotron.css
@@ -0,0 +1,15 @@
+/* http://sass-lang.com/guide */
+/* Move down content because we have a fixed navbar that is 50px tall */
+body {
+  padding-top: 50px;
+  padding-bottom: 20px; }
+
+div.section-shouldwork {
+  background: #eff; }
+
+div.section-unsupported {
+  background: #fee; }
+
+div.section-disabled {
+  background: #555;
+  color: #fff; }
diff --git a/select_readers/index.html b/select_readers/index.html
new file mode 100644
index 0000000..7dfcc4a
--- /dev/null
+++ b/select_readers/index.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Reader Selection</title>
+
+        <!-- Bootstrap -->
+        <link href="css/bootstrap.min.css" rel="stylesheet">
+
+        <!-- Custom styles for this template -->
+        <link href="css/jumbotron.css" rel="stylesheet">
+    </head>
+    <body>
+        <nav class="navbar navbar-inverse navbar-fixed-top">
+            <div class="container">
+                <div class="navbar-header">
+                    <a class="navbar-brand" href="../">MUSCLE project</a>
+                </div>
+                <div id="navbar" class="navbar-collapse collapse">
+                </div><!--/.navbar-collapse -->
+            </div>
+        </nav>
+
+        <div class="jumbotron">
+            <div class="container">
+                <h1>Reader selection</h1>
+                <p>Select the properties you want for your smart card reader and
+                    you will get the readers that match your selection.</p>
+            </div>
+        </div>
+
+        <div class="container">
+
+            <h1>Filters</h1>
+            <form id="target">
+                <select id="selector">
+                    <option value="Add" disabled>Add a match on...</option>
+                    <option value="bClassEnvelope">bClassEnvelope</option>
+                    <option value="bClassGetResponse">bClassGetResponse</option>
+                    <option value="bMaxCCIDBusySlots">bMaxCCIDBusySlots</option>
+                    <option value="bInterfaceClass">bInterfaceClass</option>
+                    <option value="bInterfaceNumber">bInterfaceNumber</option>
+                    <option value="bInterfaceProtocol">bInterfaceProtocol</option>
+                    <option value="bInterfaceSubClass">bInterfaceSubClass</option>
+                    <option value="bMaxSlotIndex">bMaxSlotIndex</option>
+                    <option value="bNumClockSupported">bNumClockSupported</option>
+                    <option value="bNumDataRatesSupported">bNumDataRatesSupported</option>
+                    <option value="bNumEndpoints">bNumEndpoints</option>
+                    <option value="bPINSupport">bPINSupport</option>
+                    <option value="bVoltageSupport">bVoltageSupport</option>
+                    <option value="bcdCCID">bcdCCID</option>
+                    <option value="bcdDevice">bcdDevice</option>
+                    <option value="dwDataRate">dwDataRate</option>
+                    <option value="dwDefaultClock">dwDefaultClock</option>
+                    <option value="dwFeatures">dwFeatures</option>
+                    <option value="dwMaxCCIDMessageLength">dwMaxCCIDMessageLength</option>
+                    <option value="dwMaxDataRate">dwMaxDataRate</option>
+                    <option value="dwMaxIFSD">dwMaxIFSD</option>
+                    <option value="dwMaximumClock">dwMaximumClock</option>
+                    <option value="dwMechanical">dwMechanical</option>
+                    <option value="dwProtocols">dwProtocols</option>
+                    <option value="dwSynchProtocols">dwSynchProtocols</option>
+                    <option value="features">features</option>
+                    <option value="iManufacturer">iManufacturer</option>
+                    <option value="iInterface">iInterface</option>
+                    <option value="iProduct">iProduct</option>
+                    <option value="idProduct">idProduct</option>
+                    <option value="idVendor">idVendor</option>
+                    <option value="wLcdLayout">wLcdLayout</option>
+                </select>
+                <div id="filters">
+                    <div class="input-group">
+                        <span class="input-group-addon" id="basic-addon1">iManufacturer</span>
+                        <div class="input-group-btn">
+                            <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" id="relation_1">≈ <span class="caret"></span></button>
+                            <ul class="dropdown-menu" role="menu">
+                                <li><a href="#">~ </a></li>
+                                <li><a href="#">= </a></li>
+                                <li><a href="#">≤ </a></li>
+                                <li><a href="#">≥ </a></li>
+                                <li class="divider"></li>
+                                <li><a href="#">disabled</a></li>
+                            </ul>
+                        </div><!-- /btn-group -->
+                        <input type="text" class="form-control" placeholder="iManufacturer" aria-describedby="basic-addon1" id="value_1">
+                    </div>
+                    <div class="input-group">
+                        <span class="input-group-addon" id="basic-addon2">features</span>
+                        <div class="input-group-btn">
+                            <button type="button" class="btn btn-default" aria-expanded="false" id="relation_2">= </button>
+                        </div><!-- /btn-group -->
+                        <select class="form-control" id="value_2">
+                          <option>toto</option>
+                          <option>tata</option>
+                          <option>pouet</option>
+                        </select>
+                    </div>
+                </div>
+                <button type="button" class="btn btn-primary" id="get_url">Get URL</button>
+                <div id="new_url"></div>
+            </form>
+
+            <hr>
+
+            <h1>Results</h1>
+            <div id="nb_readers">0 readers</div>
+
+            <div id="header_supported">
+            <h2>Supported</h2>
+            <div id="results_supported" class="row">
+            </div>
+            </div>
+
+            <div id="header_shouldwork">
+            <h2>Should work</h2>
+            <div id="results_shouldwork" class="row">
+            </div>
+            </div>
+
+            <div id="header_unsupported">
+            <h2>Unsupported</h2>
+            <div id="results_unsupported" class="row">
+            </div>
+            </div>
+
+            <div id="header_disabled">
+            <h2>Disabled</h2>
+            <div id="results_disabled" class="row">
+            </div>
+            </div>
+
+            <hr>
+
+            <footer>
+                <p>© Ludovic Rousseau 2015i, <a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License version 3</a></p>
+            </footer>
+        </div> <!-- /container -->
+
+        <script src="js/jquery-1.11.3.min.js"></script>
+        <script src="js/url.js"></script>
+        <script src="js/readers.js"></script>
+        <script src="js/bootstrap.min.js"></script>
+        <script src="js/main.js"></script>
+    </body>
+</html>
diff --git a/select_readers/js/main.js b/select_readers/js/main.js
new file mode 100644
index 0000000..7b1c9b2
--- /dev/null
+++ b/select_readers/js/main.js
@@ -0,0 +1,376 @@
+/**
+ @file Reader Selection
+ @author Ludovic Rousseau
+ @version 1.0
+
+ @description
+
+    Web application to display readers matching search criteria
+    Copyright (C) 2015  Ludovic Rousseau
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* To make JSlint happy with $ defined by jquery */
+/*global $ */
+/*global console */
+/*global document */
+/*global readers */
+/*global url */
+/*global readers_features */
+
+var Filter, Filtered_readers, Results;
+
+function reader_url(reader) {
+    "use strict";
+
+    return "../ccid/" + reader.section + ".html#0x" + (reader.idVendor + 0x10000).toString(16).substr(-4).toUpperCase() + "0x" + (reader.idProduct + 0x10000).toString(16).substr(-4).toUpperCase();
+}
+
+// Affiche la liste des lecteurs correspondant aux filtres
+function update_list() {
+    "use strict";
+
+    var i, output, reader, txt, show;
+
+    // remove old Results
+    for (i in Results) {
+        if (Results.hasOwnProperty(i)) {
+            Results[i].html("");
+        }
+    }
+
+    // hide all the sections by default
+    show = [];
+    for (i in Results) {
+        if (Results.hasOwnProperty(i)) {
+            console.log(i);
+            $("#header_" + i).hide();
+            show[i] = false;
+        }
+    }
+
+    // add Results
+    for (i = 0; i < Filtered_readers.length; i += 1) {
+        reader = Filtered_readers[i];
+        output = '<div class="col-sm-6 col-md-4">';
+        output += '<div class="thumbnail section-' + reader.section + '">';
+        output += '<h2><a href="' + reader_url(reader) + '">' + reader.iManufacturer + "</a></h2>";
+        output += "<p>" + reader.iProduct + "</p>";
+        output += '<img src="http://pcsclite.alioth.debian.org/ccid/img/' + reader.image + '">';
+        output += "</div>";
+        output += "</div>";
+        Results[reader.section].append(output);
+        show[reader.section] = true;
+    }
+
+    // show the sections with results
+    for (i in Results) {
+        if (Results.hasOwnProperty(i)) {
+            if (show[i]) {
+                $("#header_" + i).show();
+            }
+        }
+    }
+
+    if (Filtered_readers.length === 0) {
+        txt = "None found";
+    } else {
+        if (Filtered_readers.length === 1) {
+            txt = "1 reader";
+        } else {
+            txt = Filtered_readers.length + " readers";
+        }
+    }
+
+    $("#nb_readers").html(txt);
+}
+
+// Teste si il lecteur correspond aux filtres
+function match(filters, reader) {
+    "use strict";
+
+    var a, b, i, l, f, field, value, relation;
+
+    for (f = 0; f < filters.length; f += 1) {
+        field = filters[f][0];
+        relation = filters[f][1];
+        value = filters[f][2];
+
+        switch (relation[0]) {
+        case "=":
+            if (reader[field] !== value) {
+                return false;
+            }
+            break;
+        case '~':
+            a = reader[field].toLocaleUpperCase();
+            b = value.toLocaleUpperCase();
+            l = Math.min(a.length, b.length);
+            for (i = 0; i < l; i += 1) {
+                if (a[i] !== b[i]) {
+                    return false;
+                }
+            }
+            break;
+        case '≤':
+            if (reader[field] > value) {
+                return false;
+            }
+            break;
+        case '≥':
+            if (reader[field] < value) {
+                return false;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
+// Filtre les lecteurs et rempli le tableau Filtered_readers
+function function_filter() {
+    "use strict";
+
+    var section, iManufacturer, reader, work_reader, f, value, field;
+
+    Filtered_readers = [];
+
+    console.log("function_filter " + Filter);
+
+    /* fisrt reader in the array used as an example */
+    reader = readers["Teo.txt"];
+
+    // get the values from the interface
+    for (f = 0; f < Filter.length; f += 1) {
+        value = $("#value_" + f).val();
+        field = Filter[f][0];
+
+        Filter[f][1] = $("#relation_" + f).html().split(' ')[0];
+
+        /* if the reader value is an integer then we convert the user value */
+        if (typeof reader[field] === 'number') {
+            value = parseInt(value, 10);
+//            console.log(value);
+
+            if (Filter[f][1][0] === "~") {
+                Filter[f][1] = "=";
+            }
+        }
+
+        Filter[f][2] = value;
+
+//        console.log(Filter[f]);
+    }
+
+    for (reader in readers) {
+        if (readers.hasOwnProperty(reader)) {
+            work_reader = readers[reader];
+//            console.log(work_reader);
+
+            if (match(Filter, work_reader)) {
+                Filtered_readers[Filtered_readers.length] = work_reader;
+            }
+        }
+    }
+
+    update_list();
+}
+
+// Caclule l'URL correspondant aux filtres actuels
+function get_url() {
+    "use strict";
+
+    var f, txt, home_url, pos, args;
+
+//    console.log("get URL");
+    args = [];
+    for (f = 0; f < Filter.length; f += 1) {
+        // if not disabled
+        if (Filter[f][1].indexOf("disabled") < 0) {
+            args.push(Filter[f][0] + Filter[f][1][0] + Filter[f][2]);
+        }
+    }
+    txt = args.join("&");
+
+    // Nothing to add
+//    if (txt === "") {
+//        $("#new_url").html('No change');
+//        return;
+//    }
+
+//    console.log(txt);
+    home_url = url();
+    pos = home_url.search("\\?");
+    if (pos > 0) {
+        home_url = home_url.substr(0, pos);
+    }
+
+    // complete URL
+    txt = home_url + '?' + txt;
+    $("#new_url").html('<a href="' + txt + '">' + txt + '</a>');
+}
+
+function menu_clicked(e) {
+    "use strict";
+
+    var div, btn;
+
+    console.log($(e.target).text());
+    div = $(e.target).parent().parent().parent();
+    btn = div.find("button");
+    btn.html($(e.target).text() + ' <span class="caret"></span>');
+
+    function_filter();
+
+    e.preventDefault();// prevent the default anchor functionality
+}
+
+// Recalcule le code HTML des filtres
+function update_filters_ihm() {
+    "use strict";
+
+    var f, html, feature;
+
+    $("#filters").html("");
+    for (f = 0; f < Filter.length; f += 1) {
+        html = '<div class="input-group">';
+        html += '<span class="input-group-addon" id="basic-addon1">' + Filter[f][0] + '</span>';
+        if (Filter[f][0] === 'features') {
+            // features: une liste prédéfinie
+            html += '<div class="input-group-btn">';
+            html += '<button type="button" class="btn btn-default" aria-expanded="false" id="relation_' + f + '">= </button>';
+            html += '</div>';
+
+            html += '<select class="form-control update-ihm" id="value_' + f + '">';
+            for (feature in readers_features) {
+                if (readers_features.hasOwnProperty(feature)) {
+                    html += '<option>' + readers_features[feature] + '</option>';
+                }
+            }
+            html += '</select>';
+        } else {
+            html += '<div class="input-group-btn">';
+            html += '<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" id="relation_' + f + '">AAA</button>';
+            html += '<ul class="dropdown-menu" role="menu">';
+            if (Filter[f][0] === 'iManufacturer') {
+                // iManufacturer: un match approximatif
+                html += ' <li><a href="#">~ </a></li>';
+            }
+            html += ' <li><a href="#">= </a></li>';
+            html += ' <li><a href="#">≤ </a></li>';
+            html += ' <li><a href="#">≥ </a></li>';
+            html += ' <li class="divider"></li>';
+            html += ' <li><a href="#">disabled</a></li>';
+            html += '</ul></div>';
+            html += '<input type="text" class="form-control" placeholder="value" aria-describedby="basic-addon1" id="value_' + f + '">';
+        }
+        html += '</div>';
+
+        $("#filters").append(html);
+        console.log(Filter[f][1]);
+        $("#relation_" + f).html(Filter[f][1] + ' <span class="caret"></span>');
+        $("#value_" + f).val(Filter[f][2]);
+    }
+
+    // activate filters menu
+    $(".dropdown-menu").click(menu_clicked);
+    $(".update-ihm").click(function_filter);
+
+    // enable live update on key released
+    $("input").keyup(function_filter);
+}
+
+// Ajoute un nouveau filtre
+function add_filter() {
+    "use strict";
+
+    var f, field, html;
+
+    field = $("#selector").val();
+//    console.log("add filter: " + field);
+    Filter[Filter.length] = [field, "=", ""];
+//    console.log(Filter);
+
+    update_filters_ihm();
+}
+
+// Parse l'URL de la page pour récupérer les paramètres
+function parse_url() {
+    "use strict";
+
+    var args, f, index, expr, m, relation, relations, pair;
+
+    // https://github.com/websanova/js-url#url
+    console.log(url('?'));
+
+    args = url('?').split('&');
+    relations = {"=": "=", "~": "~", "%E2%89%A4": "≤", "%E2%89%A5": "≥"};
+
+    index = 0;
+    for (f = 0; f < args.length; f += 1) {
+        expr = args[f];
+        for (relation in relations) {
+            if (relations.hasOwnProperty(relation)) {
+                m = expr.search(relation);
+                if (m > 0) {
+                    pair = expr.split(relation);
+
+                    Filter[index] = [];
+                    Filter[index][0] = pair[0];
+                    Filter[index][1] = relations[relation];
+                    Filter[index][2] = pair[1];
+                    index += 1;
+
+                    // Add a new filter to the interface
+                }
+            }
+        }
+    }
+}
+
+// Initialisation générale
+function init() {
+    "use strict";
+
+    var i;
+
+    $("#filters").html("");
+
+    /* set the filter according to the URL */
+    Filter = [];
+    parse_url();
+
+    update_filters_ihm();
+
+    $("#selector").change(add_filter);
+    $("#selector").val("Add");
+
+    $("#get_url").on("click", get_url);
+
+    Results = {"supported": "", "shouldwork": "", "unsupported": "", "disabled": ""};
+    for (i in Results) {
+        if (Results.hasOwnProperty(i)) {
+            Results[i] = $("#results_" + i);
+        }
+    }
+
+    function_filter();
+}
+
+$(document).ready(function () {
+    "use strict";
+
+    init();
+});
diff --git a/select_readers/update.sh b/select_readers/update.sh
new file mode 100755
index 0000000..7355a3c
--- /dev/null
+++ b/select_readers/update.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# files to upload
+FILES="index.html \
+	js/bootstrap.min.js js/main.js js/url.js js/jquery-1.11.3.min.js \
+	js/readers.js css/bootstrap.min.css \
+	css/jumbotron.css"
+
+# regenerate the reader list
+(cd .. ; ./convert2json.py > select_readers/js/readers.js)
+
+# update webs site
+echo $FILES
+exit
+rsync --recursive --verbose --update --rsh=ssh . anonscm.debian.org:pcsclite_htdocs/select_readers/

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pcsclite/website.git



More information about the Pcsclite-cvs-commit mailing list