[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