diff options
Diffstat (limited to 'scripts/minimaws/lib')
-rw-r--r-- | scripts/minimaws/lib/assets/machine.js | 103 | ||||
-rw-r--r-- | scripts/minimaws/lib/assets/romident.js | 10 | ||||
-rw-r--r-- | scripts/minimaws/lib/dbaccess.py | 10 | ||||
-rw-r--r-- | scripts/minimaws/lib/htmltmpl.py | 49 | ||||
-rw-r--r-- | scripts/minimaws/lib/wsgiserve.py | 188 |
5 files changed, 304 insertions, 56 deletions
diff --git a/scripts/minimaws/lib/assets/machine.js b/scripts/minimaws/lib/assets/machine.js index d40dca12be0..b7f600f04d6 100644 --- a/scripts/minimaws/lib/assets/machine.js +++ b/scripts/minimaws/lib/assets/machine.js @@ -4,6 +4,7 @@ var slot_info = Object.create(null); var bios_sets = Object.create(null); var machine_flags = Object.create(null); +var softwarelist_info = Object.create(null); function make_slot_popup_id(name) { return ('select-slot-choice-' + name).replace(/:/g, '-'); } @@ -197,6 +198,47 @@ var fetch_machine_flags = (function () })(); +var fetch_softwarelist_info = (function () + { + var pending = Object.create(null); + return function (device) + { + if (!Object.prototype.hasOwnProperty.call(softwarelist_info, device) && !Object.prototype.hasOwnProperty.call(pending, device)) + { + pending[device] = true; + var req = new XMLHttpRequest(); + req.open('GET', appurl + 'rpc/softwarelists/' + encodeURIComponent(device), true); + req.responseType = 'json'; + req.onload = + function () + { + delete pending[device]; + if (req.status == 200) + { + softwarelist_info[device] = req.response; + var slotslist = document.getElementById('list-slot-options'); + if (slotslist) + { + for (var item = slotslist.firstChild; item; item = item.nextSibling) + { + if ((item.nodeName == 'DT') && (item.getAttribute('data-slotcard') == device)) + { + var slotname = item.getAttribute('data-slotname'); + add_softwarelist_rows( + device, + slotname, + document.getElementById('select-slot-choice-' + slotname.replace(/:/g, '-')).value); + } + } + } + } + }; + req.send(); + } + }; + })(); + + function add_flag_rows(table, device) { var sorted_features = Object.keys(machine_flags[device].features).sort(); @@ -228,6 +270,51 @@ function add_flag_rows(table, device) } +function add_softwarelist_rows(device, slot, card) +{ + var sorted_softwarelists = Object.keys(softwarelist_info[device]).sort(); + var cardtag = slot + ':' + card; + if (sorted_softwarelists.length) + { + var table = document.getElementById('tbl-softwarelists').tBodies[0]; + sorted_softwarelists.forEach( + function (tag) + { + var cell, link; + var info = softwarelist_info[device][tag]; + var href = appurl + 'softwarelist/' + encodeURIComponent(info.shortname); + var row = table.appendChild(document.createElement('tr')); + row.setAttribute('id', 'row-softwarelists-' + (cardtag + tag).replace(/:/g, '-')) + row.appendChild(document.createElement('td')).textContent = cardtag; + link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a')); + link.setAttribute('href', href); + link.textContent = info.shortname; + link = row.appendChild(document.createElement('td')).appendChild(document.createElement('a')); + link.setAttribute('href', href); + link.textContent = info.description; + row.appendChild(document.createElement('td')).textContent = info.status; + total = info.total; + cell = row.appendChild(document.createElement('td')); + cell.textContent = total.toString(10); + cell.style.textAlign = 'right'; + if (!total) + total = 1; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.supported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.partiallysupported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + cell = row.appendChild(document.createElement('td')); + cell.textContent = (info.unsupported * 100.0 / total).toFixed(1) + '%'; + cell.style.textAlign = 'right'; + }); + document.getElementById('heading-softwarelists').style.removeProperty('display'); + document.getElementById('tbl-softwarelists').style.removeProperty('display'); + } +} + + function add_bios_row(slot, table, device) { var sorted_sets = Object.keys(bios_sets[device]).sort(); @@ -394,6 +481,16 @@ function make_slot_change_handler(name, slot, defaults, dfltbtn) slotslist.removeChild(next); } + // clear out any software lists from previous selection + var softwarelist_rowid_prefix = 'row-softwarelists-' + prefix.replace(/:/g, '-'); + for (var candidate = document.getElementById('tbl-softwarelists').tBodies[0].rows[0]; candidate; ) + { + var next = candidate.nextSibling; + if ((candidate.nodeName == 'TR') && candidate.getAttribute('id').startsWith(softwarelist_rowid_prefix)) + candidate.parentNode.removeChild(candidate); + candidate = next; + } + if (selection === null) { // no selection, remove the slot card details table @@ -427,6 +524,12 @@ function make_slot_change_handler(name, slot, defaults, dfltbtn) else add_bios_row(slotname, tbl, selection.device); + // if we have software list info, populate now, otherweise fetch asynchronously + if (!Object.prototype.hasOwnProperty.call(softwarelist_info, selection.device)) + fetch_softwarelist_info(selection.device); + else + add_softwarelist_rows(selection.device, slotname, choice); + // drop the details table into the list if (def.firstChild) def.replaceChild(tbl, def.firstChild); diff --git a/scripts/minimaws/lib/assets/romident.js b/scripts/minimaws/lib/assets/romident.js index 9ca2c84f54d..e42daf875cf 100644 --- a/scripts/minimaws/lib/assets/romident.js +++ b/scripts/minimaws/lib/assets/romident.js @@ -454,6 +454,14 @@ function add_name(group, name, crc, sha1) if (group.names.indexOf(name) < 0) { group.names.push(name); + + if (group.hasOwnProperty('issues')) + { + var issues = group.issues; + if (issues !== null) + issues.parentNode.parentNode.insertBefore(document.createElement('dt'), issues.parentNode).textContent = name; + } + if (group.hasOwnProperty('machines')) { var machines = group.machines; @@ -575,8 +583,6 @@ function identify_file(file, trychd, progress) else { crcgrp = sha1grp.crc[crc]; - if (crcgrp.issues !== null) - crcgrp.issues.parentNode.parentNode.insertBefore(document.createElement('dl'), crcgrp.issues.parentNode).textContent = file.name; add_name(crcgrp, file.name, crc, sha1); progress.parentNode.removeChild(progress); } diff --git a/scripts/minimaws/lib/dbaccess.py b/scripts/minimaws/lib/dbaccess.py index cc359bfa65c..7ca5ebf243d 100644 --- a/scripts/minimaws/lib/dbaccess.py +++ b/scripts/minimaws/lib/dbaccess.py @@ -771,6 +771,14 @@ class QueryCursor(object): 'ORDER BY ramoption.size', (machine, )) + def get_machine_softwarelists(self, machine): + return self.dbcurs.execute( + 'SELECT machinesoftwarelist.tag AS tag, machinesoftwareliststatustype.value AS status, softwarelist.shortname AS shortname, softwarelist.description AS description, COUNT(software.id) AS total, COUNT(CASE software.supported WHEN 0 THEN 1 ELSE NULL END) AS supported, COUNT(CASE software.supported WHEN 1 THEN 1 ELSE NULL END) AS partiallysupported, COUNT(CASE software.supported WHEN 2 THEN 1 ELSE NULL END) AS unsupported ' \ + 'FROM machinesoftwarelist LEFT JOIN machinesoftwareliststatustype ON machinesoftwarelist.status = machinesoftwareliststatustype.id LEFT JOIN softwarelist ON machinesoftwarelist.softwarelist = softwarelist.id LEFT JOIN software ON softwarelist.id = software.softwarelist ' \ + 'WHERE machinesoftwarelist.machine = ? ' \ + 'GROUP BY machinesoftwarelist.id', + (machine, )) + def get_softwarelist_id(self, shortname): return (self.dbcurs.execute('SELECT id FROM softwarelist WHERE shortname = ?', (shortname, )).fetchone() or (None, ))[0] @@ -1075,6 +1083,8 @@ class UpdateConnection(object): def prepare_for_load(self): # here be dragons - this is a poor man's DROP ALL TABLES etc. self.dbconn.execute('PRAGMA foreign_keys = OFF') + for query in self.dbconn.execute('SELECT \'DROP VIEW \' || name FROM sqlite_master WHERE type = \'view\'').fetchall(): + self.dbconn.execute(query[0]) for query in self.dbconn.execute('SELECT \'DROP INDEX \' || name FROM sqlite_master WHERE type = \'index\' AND NOT name GLOB \'sqlite_autoindex_*\'').fetchall(): self.dbconn.execute(query[0]) for query in self.dbconn.execute('SELECT \'DROP TABLE \' || name FROM sqlite_master WHERE type = \'table\'').fetchall(): diff --git a/scripts/minimaws/lib/htmltmpl.py b/scripts/minimaws/lib/htmltmpl.py index 185771af6ee..7c9246865b1 100644 --- a/scripts/minimaws/lib/htmltmpl.py +++ b/scripts/minimaws/lib/htmltmpl.py @@ -70,6 +70,47 @@ MACHINE_CLONES_ROW = string.Template( ' <td>${manufacturer}</td>\n' \ ' </tr>\n') +MACHINE_SOFTWARELISTS_TABLE_PROLOGUE = string.Template( + '<h2 id="heading-softwarelists">Software Lists</h2>\n' \ + '<table id="tbl-softwarelists">\n' \ + ' <thead>\n' \ + ' <tr>\n' \ + ' <th>Card</th>\n' \ + ' <th>Short name</th>\n' \ + ' <th>Description</th>\n' \ + ' <th>Status</th>\n' \ + ' <th class="numeric">Total</th>\n' \ + ' <th class="numeric">Supported</th>\n' \ + ' <th class="numeric">Partially supported</th>\n' \ + ' <th class="numeric">Unsupported</th>\n' \ + ' </tr>\n' \ + ' </thead>\n' \ + ' <tbody>\n') + +MACHINE_SOFTWARELISTS_TABLE_ROW = string.Template( + ' <tr id="row-softwarelists-${rowid}">\n' \ + ' <td></td>\n' \ + ' <td><a href="${href}">${shortname}</a></td>\n' \ + ' <td><a href="${href}">${description}</a></td>\n' \ + ' <td>${status}</td>\n' \ + ' <td style="text-align: right">${total}</td>\n' \ + ' <td style="text-align: right">${supported}</td>\n' \ + ' <td style="text-align: right">${partiallysupported}</td>\n' \ + ' <td style="text-align: right">${unsupported}</td>\n' \ + ' </tr>\n') + +MACHINE_SOFTWARELISTS_TABLE_EPILOGUE = string.Template( + ' </tbody>\n' \ + '</table>\n' \ + '<script>\n' \ + ' make_table_sortable(document.getElementById("tbl-softwarelists"));\n' \ + ' if (!document.getElementById("tbl-softwarelists").tBodies[0].rows.length)\n' \ + ' {\n' \ + ' document.getElementById("heading-softwarelists").style.display = "none";\n' \ + ' document.getElementById("tbl-softwarelists").style.display = "none";\n' \ + ' }\n' \ + '</script>\n') + MACHINE_OPTIONS_HEADING = string.Template( '<h2>Options</h2>\n' \ '<p>\n' \ @@ -92,10 +133,14 @@ MACHINE_RAM_PROLOGUE = string.Template( MACHINE_RAM_OPTION = string.Template( ' <option value="${name}" data-isdefault="${isdefault}">${name} (${size})</option>\n') -MACHINE_SLOTS_PLACEHOLDER = string.Template( +MACHINE_SLOTS_PLACEHOLDER_PROLOGUE = string.Template( '<h3>Slots</h3>\n' \ '<p id="para-slots-placeholder">Loading slot information…<p>\n' \ - '<script>fetch_slots("${machine}");</script>\n') + '<script>\n') + +MACHINE_SLOTS_PLACEHOLDER_EPILOGUE = string.Template( + ' populate_slots(${machine});\n' + '</script>\n') MACHINE_ROW = string.Template( ' <tr>\n' \ diff --git a/scripts/minimaws/lib/wsgiserve.py b/scripts/minimaws/lib/wsgiserve.py index 8046785b5f1..e77bb3f637f 100644 --- a/scripts/minimaws/lib/wsgiserve.py +++ b/scripts/minimaws/lib/wsgiserve.py @@ -109,6 +109,80 @@ class QueryPageHandler(HandlerBase): def software_href(self, softwarelist, software): return cgi.escape(urlparse.urljoin(self.application_uri, 'softwarelist/%s/%s' % (urlquote(softwarelist), urlquote(software))), True) + def bios_data(self, machine): + result = { } + for name, description, isdefault in self.dbcurs.get_biossets(machine): + result[name] = { 'description': description, 'isdefault': True if isdefault else False } + return result + + def flags_data(self, machine): + result = { 'features': { } } + for feature, status, overall in self.dbcurs.get_feature_flags(machine): + detail = { } + if status == 1: + detail['status'] = 'imperfect' + elif status > 1: + detail['status'] = 'unemulated' + if overall == 1: + detail['overall'] = 'imperfect' + elif overall > 1: + detail['overall'] = 'unemulated' + result['features'][feature] = detail + return result + + def slot_data(self, machine): + result = { 'defaults': { }, 'slots': { } } + + # get slot options + prev = None + for slot, option, shortname, description in self.dbcurs.get_slot_options(machine): + if slot != prev: + if slot in result['slots']: + options = result['slots'][slot] + else: + options = { } + result['slots'][slot] = options + prev = slot + options[option] = { 'device': shortname, 'description': description } + + # if there are any slots, get defaults + if result['slots']: + for slot, default in self.dbcurs.get_slot_defaults(machine): + result['defaults'][slot] = default + + # remove slots that come from default cards in other slots + for slot in tuple(result['slots'].keys()): + slot += ':' + for candidate in tuple(result['slots'].keys()): + if candidate.startswith(slot): + del result['slots'][candidate] + + return result + + def softwarelist_data(self, machine): + result = { } + + # get software lists referenced by machine + for softwarelist in self.dbcurs.get_machine_softwarelists(machine): + result[softwarelist['tag']] = { + 'status': softwarelist['status'], + 'shortname': softwarelist['shortname'], + 'description': softwarelist['description'], + 'total': softwarelist['total'], + 'supported': softwarelist['supported'], + 'partiallysupported': softwarelist['partiallysupported'], + 'unsupported': softwarelist['unsupported'] } + + # remove software lists that come from default cards in slots + if result: + for slot, default in self.dbcurs.get_slot_defaults(machine): + slot += ':' + for candidate in tuple(result.keys()): + if candidate.startswith(slot): + del result[candidate] + + return result + class MachineRpcHandlerBase(QueryPageHandler): def __init__(self, app, application_uri, environ, start_response, **kwargs): @@ -215,6 +289,7 @@ class MachineHandler(QueryPageHandler): tuple(imperfect)).encode('utf-8'); yield '</table>\n'.encode('utf-8') + # make a table of clones first = True for clone, clonedescription, cloneyear, clonemanufacturer in self.dbcurs.get_clones(self.shortname): if first: @@ -229,6 +304,22 @@ class MachineHandler(QueryPageHandler): if not first: yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-clones').encode('utf-8') + # make a table of software lists + yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_PROLOGUE.substitute().encode('utf-8') + for softwarelist in self.dbcurs.get_machine_softwarelists(id): + total = softwarelist['total'] + yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_ROW.substitute( + rowid=cgi.escape(softwarelist['tag'].replace(':', '-'), True), + href=self.softwarelist_href(softwarelist['shortname']), + shortname=cgi.escape(softwarelist['shortname']), + description=cgi.escape(softwarelist['description']), + status=cgi.escape(softwarelist['status']), + total=cgi.escape('%d' % (total, )), + supported=cgi.escape('%.1f%%' % (softwarelist['supported'] * 100.0 / (total or 1), )), + partiallysupported=cgi.escape('%.1f%%' % (softwarelist['partiallysupported'] * 100.0 / (total or 1), )), + unsupported=cgi.escape('%.1f%%' % (softwarelist['unsupported'] * 100.0 / (total or 1), ))).encode('utf-8') + yield htmltmpl.MACHINE_SOFTWARELISTS_TABLE_EPILOGUE.substitute().encode('utf-8') + # allow system BIOS selection haveoptions = False for name, desc, isdef in self.dbcurs.get_biossets(id): @@ -264,8 +355,29 @@ class MachineHandler(QueryPageHandler): if not haveoptions: haveoptions = True yield htmltmpl.MACHINE_OPTIONS_HEADING.substitute().encode('utf-8') - yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER.substitute( - machine=self.js_escape(self.shortname)).encode('utf=8') + yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_PROLOGUE.substitute().encode('utf=8') + pending = set((self.shortname, )) + added = set((self.shortname, )) + haveextra = set() + while pending: + requested = pending.pop() + slots = self.slot_data(self.dbcurs.get_machine_id(requested)) + yield (' slot_info[%s] = %s;\n' % (self.sanitised_json(requested), self.sanitised_json(slots))).encode('utf-8') + for slotname, slot in slots['slots'].items(): + for choice, card in slot.items(): + carddev = card['device'] + if carddev not in added: + pending.add(carddev) + added.add(carddev) + if (carddev not in haveextra) and (slots['defaults'].get(slotname) == choice): + haveextra.add(carddev) + cardid = self.dbcurs.get_machine_id(carddev) + carddev = self.sanitised_json(carddev) + yield ( + ' bios_sets[%s] = %s;\n machine_flags[%s] = %s;\n softwarelist_info[%s] = %s;\n' % + (carddev, self.sanitised_json(self.bios_data(cardid)), carddev, self.sanitised_json(self.flags_data(cardid)), carddev, self.sanitised_json(self.softwarelist_data(cardid)))).encode('utf-8') + yield htmltmpl.MACHINE_SLOTS_PLACEHOLDER_EPILOGUE.substitute( + machine=self.sanitised_json(self.shortname)).encode('utf=8') # list devices referenced by this system/device first = True @@ -332,6 +444,10 @@ class MachineHandler(QueryPageHandler): description=cgi.escape(description or ''), sourcefile=cgi.escape(sourcefile or '')).encode('utf-8') + @staticmethod + def sanitised_json(data): + return json.dumps(data).replace('<', '\\u003c').replace('>', '\\u003e') + class SourceFileHandler(QueryPageHandler): def __init__(self, app, application_uri, environ, start_response, **kwargs): @@ -531,7 +647,13 @@ class SoftwareListHandler(QueryPageHandler): if first: yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_HEADER.substitute().encode('utf-8') first = False - yield self.machine_row(machine_info) + yield htmltmpl.SOFTWARELIST_MACHINE_TABLE_ROW.substitute( + machinehref=self.machine_href(machine_info['shortname']), + shortname=cgi.escape(machine_info['shortname']), + description=cgi.escape(machine_info['description']), + year=cgi.escape(machine_info['year'] or ''), + manufacturer=cgi.escape(machine_info['manufacturer'] or ''), + status=cgi.escape(machine_info['status'])).encode('utf-8') if not first: yield htmltmpl.SORTABLE_TABLE_EPILOGUE.substitute(id='tbl-machines').encode('utf-8') @@ -613,15 +735,6 @@ class SoftwareListHandler(QueryPageHandler): publisher=cgi.escape(clone_info['publisher']), supported=self.format_supported(clone_info['supported'])).encode('utf-8') - def machine_row(self, machine_info): - return htmltmpl.SOFTWARELIST_MACHINE_TABLE_ROW.substitute( - machinehref=self.machine_href(machine_info['shortname']), - shortname=cgi.escape(machine_info['shortname']), - description=cgi.escape(machine_info['description']), - year=cgi.escape(machine_info['year'] or ''), - manufacturer=cgi.escape(machine_info['manufacturer'] or ''), - status=cgi.escape(machine_info['status'])).encode('utf-8') - @staticmethod def format_supported(supported): return 'Yes' if supported == 0 else 'Partial' if supported == 1 else 'No' @@ -659,47 +772,17 @@ class BiosRpcHandler(MachineRpcHandlerBase): class FlagsRpcHandler(MachineRpcHandlerBase): def data_page(self, machine): - result = { 'features': { } } - for feature, status, overall in self.dbcurs.get_feature_flags(machine): - detail = { } - if status == 1: - detail['status'] = 'imperfect' - elif status > 1: - detail['status'] = 'unemulated' - if overall == 1: - detail['overall'] = 'imperfect' - elif overall > 1: - detail['overall'] = 'unemulated' - result['features'][feature] = detail - yield json.dumps(result).encode('utf-8') + yield json.dumps(self.flags_data(machine)).encode('utf-8') class SlotsRpcHandler(MachineRpcHandlerBase): def data_page(self, machine): - result = { 'defaults': { }, 'slots': { } } + yield json.dumps(self.slot_data(machine)).encode('utf-8') - # get defaults and slot options - for slot, default in self.dbcurs.get_slot_defaults(machine): - result['defaults'][slot] = default - prev = None - for slot, option, shortname, description in self.dbcurs.get_slot_options(machine): - if slot != prev: - if slot in result['slots']: - options = result['slots'][slot] - else: - options = { } - result['slots'][slot] = options - prev = slot - options[option] = { 'device': shortname, 'description': description } - - # remove slots that come from default cards in other slots - for slot in tuple(result['slots'].keys()): - slot += ':' - for candidate in tuple(result['slots'].keys()): - if candidate.startswith(slot): - del result['slots'][candidate] - yield json.dumps(result).encode('utf-8') +class SoftwareListsRpcHandler(MachineRpcHandlerBase): + def data_page(self, machine): + yield json.dumps(self.softwarelist_data(machine)).encode('utf-8') class RomDumpsRpcHandler(QueryPageHandler): @@ -817,11 +900,12 @@ class DiskDumpsRpcHandler(QueryPageHandler): class MiniMawsApp(object): JS_ESCAPE = re.compile('([\"\'\\\\])') RPC_SERVICES = { - 'bios': BiosRpcHandler, - 'flags': FlagsRpcHandler, - 'slots': SlotsRpcHandler, - 'romdumps': RomDumpsRpcHandler, - 'diskdumps': DiskDumpsRpcHandler } + 'bios': BiosRpcHandler, + 'flags': FlagsRpcHandler, + 'slots': SlotsRpcHandler, + 'softwarelists': SoftwareListsRpcHandler, + 'romdumps': RomDumpsRpcHandler, + 'diskdumps': DiskDumpsRpcHandler } def __init__(self, dbfile, **kwargs): super(MiniMawsApp, self).__init__(**kwargs) |