--- a
+++ b/qiita_pet/handlers/software.py
@@ -0,0 +1,205 @@
+# -----------------------------------------------------------------------------
+# Copyright (c) 2014--, The Qiita Development Team.
+#
+# Distributed under the terms of the BSD 3-clause License.
+#
+# The full license is in the file LICENSE, distributed with this software.
+# -----------------------------------------------------------------------------
+from tornado.gen import coroutine
+
+from qiita_core.util import execute_as_transaction
+from qiita_db.software import Software, DefaultWorkflow
+from .base_handlers import BaseHandler
+from copy import deepcopy
+
+
+class SoftwareHandler(BaseHandler):
+    @coroutine
+    @execute_as_transaction
+    def get(self):
+        # active True will only show active software
+        active = True
+        user = self.current_user
+        if user is not None and user.level in {'admin', 'dev'}:
+            active = False
+
+        software = Software.iter(active=active)
+        self.render("software.html", software=software)
+
+
+def _retrive_workflows(active):
+    # helper method to avoid duplication of code
+    def _default_parameters_parsing(node):
+        dp = node.default_parameter
+        cmd = dp.command
+        cmd_name = 'params_%d' % node.id
+        rp = deepcopy(cmd.required_parameters)
+        op = deepcopy(cmd.optional_parameters)
+        params = dict()
+        for param, value in dp.values.items():
+            if param in rp:
+                del rp[param]
+            if param in op:
+                del op[param]
+            params[param] = str(value)
+
+        inputs = []
+        outputs = []
+        for input in rp.values():
+            accepted_values = ' | '.join(input[1])
+            inputs.append([cmd.id, accepted_values])
+        for output in cmd.outputs:
+            outputs.append([cmd.id, ' | '.join(output)])
+        fcmd_name = cmd.name if not cmd.naming_order else \
+            f'{cmd.name} | {dp.name}'
+
+        return ([cmd_name, cmd.id, fcmd_name, dp.name, params],
+                inputs, outputs)
+
+    workflows = []
+    for w in DefaultWorkflow.iter(active=active):
+        # getting the main default parameters
+        nodes = []
+        edges = []
+        at = w.artifact_type
+
+        # first get edges as this will give us the main connected commands
+        # and their order
+        graph = w.graph
+        # inputs is {input_type: node_name, ...} for easy look up of
+        # raw_inputs and reuse of the node_name
+        inputs = dict()
+        # main_nodes is {main_node_name: {
+        #                   output_type: output_node_name}, ...}
+        # for easy look up and merge of output_names
+        main_nodes = dict()
+        not_used_nodes = {n.id: n for n in graph.nodes}
+        for i, (x, y) in enumerate(graph.edges):
+            if x.id in not_used_nodes:
+                del not_used_nodes[x.id]
+            if y.id in not_used_nodes:
+                del not_used_nodes[y.id]
+            vals_x, input_x, output_x = _default_parameters_parsing(x)
+            vals_y, input_y, output_y = _default_parameters_parsing(y)
+
+            connections = []
+            for a, _, c in graph[x][y]['connections'].connections:
+                connections.append("%s | %s" % (a, c))
+
+            if i == 0:
+                # we are in the first element so we can specifically select
+                # the type we are looking for
+                if at in input_x[0][1]:
+                    input_x[0][1] = at
+                else:
+                    input_x[0][1] = '** WARNING, NOT DEFINED **'
+
+            name_x = vals_x[0]
+            name_y = vals_y[0]
+            if vals_x not in (nodes):
+                nodes.append(vals_x)
+                if name_x not in main_nodes:
+                    main_nodes[name_x] = dict()
+                for a, b in input_x:
+                    name = 'input_%s_%s' % (name_x, b)
+                    if b in inputs:
+                        name = inputs[b]
+                    else:
+                        name = 'input_%s_%s' % (name_x, b)
+                    vals = [name, a, b]
+                    if vals not in nodes:
+                        inputs[b] = name
+                        nodes.append(vals)
+                    edges.append([name, vals_x[0]])
+                for a, b in output_x:
+                    name = 'output_%s_%s' % (name_x, b)
+                    vals = [name, a, b]
+                    if vals not in nodes:
+                        nodes.append(vals)
+                    edges.append([name_x, name])
+                    main_nodes[name_x][b] = name
+
+            if vals_y not in (nodes):
+                nodes.append(vals_y)
+                if name_y not in main_nodes:
+                    main_nodes[name_y] = dict()
+            for a, b in input_y:
+                # checking if there is an overlap between the parameter
+                # and the connections; if there is, use the connection
+                overlap = set(main_nodes[name_x]) & set(connections)
+                if overlap:
+                    # use the first hit
+                    b = list(overlap)[0]
+
+                if b in main_nodes[name_x]:
+                    name = main_nodes[name_x][b]
+                else:
+                    name = 'input_%s_%s' % (name_y, b)
+                    vals = [name, a, b]
+                    if vals not in nodes:
+                        nodes.append(vals)
+                edges.append([name, name_y])
+            for a, b in output_y:
+                name = 'output_%s_%s' % (name_y, b)
+                vals = [name, a, b]
+                if vals not in nodes:
+                    nodes.append(vals)
+                edges.append([name_y, name])
+                main_nodes[name_y][b] = name
+
+        wparams = w.parameters
+
+        # adding nodes without edges
+        # as a first step if not_used_nodes is not empty we'll confirm that
+        # nodes/edges are empty; in theory we should never hit this
+        if not_used_nodes and (nodes or edges):
+            raise ValueError(
+                'Error, please check your workflow configuration')
+
+        # note that this block is similar but not identical to adding connected
+        # nodes
+        for i, (_, x) in enumerate(not_used_nodes.items()):
+            vals_x, input_x, output_x = _default_parameters_parsing(x)
+            if at in input_x[0][1]:
+                input_x[0][1] = at
+            else:
+                input_x[0][1] = '** WARNING, NOT DEFINED **'
+
+            name_x = vals_x[0]
+            if vals_x not in (nodes):
+                nodes.append(vals_x)
+                for a, b in input_x:
+                    if b in inputs:
+                        name = inputs[b]
+                    else:
+                        name = 'input_%s_%s' % (name_x, b)
+                    nodes.append([name, a, b])
+                    edges.append([name, vals_x[0]])
+                for a, b in output_x:
+                    name = 'output_%s_%s' % (name_x, b)
+                    nodes.append([name, a, b])
+                    edges.append([name_x, name])
+
+        workflows.append(
+            {'name': w.name, 'id': w.id, 'data_types': w.data_type,
+             'description': w.description, 'active': w.active,
+             'parameters_sample': wparams['sample'],
+             'parameters_prep': wparams['prep'],
+             'nodes': nodes, 'edges': edges})
+
+    return workflows
+
+
+class WorkflowsHandler(BaseHandler):
+    @coroutine
+    @execute_as_transaction
+    def get(self):
+        # active True will only show active workflows
+        active = True
+        user = self.current_user
+        if user is not None and user.level in {'admin', 'dev'}:
+            active = False
+
+        workflows = _retrive_workflows(active)
+
+        self.render("workflows.html", workflows=workflows)