diff --git a/app/core/Router.coffee b/app/core/Router.coffee
index 17c5a1853..1ad136f59 100644
--- a/app/core/Router.coffee
+++ b/app/core/Router.coffee
@@ -43,6 +43,12 @@ module.exports = class CocoRouter extends Backbone.Router
     'admin/pending-patches': go('admin/PendingPatchesView')
     'admin/codelogs': go('admin/CodeLogsView')
 
+    'artisans': go('artisans/ArtisansView')
+
+    'artisans/level-analytics': go('artisans/LevelAnalyticsView')
+    'artisans/level-tasks': go('artisans/LevelTasksView')
+    'artisans/thang-tasks': go('artisans/ThangTasksView')
+
     'beta': go('HomeView')
 
     'careers': => window.location.href = 'https://jobs.lever.co/codecombat'
diff --git a/app/styles/artisans/artisansView.sass b/app/styles/artisans/artisansView.sass
new file mode 100644
index 000000000..1a56401f8
--- /dev/null
+++ b/app/styles/artisans/artisansView.sass
@@ -0,0 +1,6 @@
+#artisans-view
+  text-align: center
+  a
+    font-size: xx-large
+  img
+    border-radius: 8px
\ No newline at end of file
diff --git a/app/styles/artisans/levelAnalyticsView.sass b/app/styles/artisans/levelAnalyticsView.sass
new file mode 100644
index 000000000..a1327ed63
--- /dev/null
+++ b/app/styles/artisans/levelAnalyticsView.sass
@@ -0,0 +1 @@
+#level-analytics-view
\ No newline at end of file
diff --git a/app/styles/artisans/levelTasksView.sass b/app/styles/artisans/levelTasksView.sass
new file mode 100644
index 000000000..a5585bb76
--- /dev/null
+++ b/app/styles/artisans/levelTasksView.sass
@@ -0,0 +1,12 @@
+#level-tasks-view
+  #levelTable
+    width: 100%
+
+  .tasksTable
+    width: 100%
+
+  .tasks
+    width: 87.5%
+    
+  .taskOwner
+    width: 12.5%
\ No newline at end of file
diff --git a/app/styles/artisans/thangTasksView.sass b/app/styles/artisans/thangTasksView.sass
new file mode 100644
index 000000000..599a6a6ab
--- /dev/null
+++ b/app/styles/artisans/thangTasksView.sass
@@ -0,0 +1,12 @@
+#thang-tasks-view
+  #thangTable
+    width: 100%
+
+  .tasksTable
+    width: 100%
+
+  .tasks
+    width: 87.5%
+    
+  .taskOwner
+    width: 12.5%
\ No newline at end of file
diff --git a/app/templates/artisans/artisansView.jade b/app/templates/artisans/artisansView.jade
new file mode 100644
index 000000000..0cf81943f
--- /dev/null
+++ b/app/templates/artisans/artisansView.jade
@@ -0,0 +1,10 @@
+extends /templates/base
+
+block content
+  img(src='/images/pages/user/artisan.png')
+  div
+    a(href='artisans/thang-tasks')
+      |Thang Tasks
+  div
+    a(href="artisans/level-tasks")
+      |Level Tasks
\ No newline at end of file
diff --git a/app/templates/artisans/levelAnalyticsView.jade b/app/templates/artisans/levelAnalyticsView.jade
new file mode 100644
index 000000000..d733d9169
--- /dev/null
+++ b/app/templates/artisans/levelAnalyticsView.jade
@@ -0,0 +1,4 @@
+extends /templates/base
+
+block content
+  table#levelTable
\ No newline at end of file
diff --git a/app/templates/artisans/levelTasksView.jade b/app/templates/artisans/levelTasksView.jade
new file mode 100644
index 000000000..e44ae7dbd
--- /dev/null
+++ b/app/templates/artisans/levelTasksView.jade
@@ -0,0 +1,34 @@
+extends /templates/base
+
+block content
+  #level-tasks-view
+    div
+      a(href='./')
+        | < Artisans Home
+    input#nameSearch(placeholder='Filter: Level Name')
+    br
+    input#descSearch(placeholder='Filter: Task Description')
+    hr
+    if view.processedLevels
+      table.table.table-striped#levelTable
+        tr
+          th Level Name
+          th Task List
+        for level in view.processedLevels
+          if view.hasIncompleteTasks(level)
+            +levelRow(level)
+    else
+      div ole?
+
+
+
+mixin levelRow(level)
+  tr
+    td.taskOwner
+      a(href= 'level/' + level.slug)= level.name
+    td.tasks
+      table.table-striped.table-hover.tasksTable
+        for task in (level.tasks2 || [])
+          if !task.complete
+            tr
+              td= task.name
diff --git a/app/templates/artisans/thangTasksView.jade b/app/templates/artisans/thangTasksView.jade
new file mode 100644
index 000000000..dc9c30c08
--- /dev/null
+++ b/app/templates/artisans/thangTasksView.jade
@@ -0,0 +1,32 @@
+extends /templates/base
+
+block content
+  #thang-tasks-view
+    div
+      a(href='./')
+        | < Artisans Home
+    input#nameSearch(placeholder='Filter: Thang Name')
+    br
+    input#descSearch(placeholder='Filter: Task Description')
+    hr
+    if view.processedThangs
+      table.table.table-striped#thangTable
+        tr
+          th Thang Name
+          th Task List
+        for thang in view.processedThangs
+          if view.hasIncompleteTasks(thang)
+            +thangRow(thang)
+    else
+      span No view.processedThangs
+
+mixin thangRow(thang)
+  tr
+    td.taskOwner
+      a(href= 'thang/' + thang.get('slug'))= thang.get('name')
+    td.tasks
+      table.table-striped.table-hover.tasksTable
+        for task in (thang.tasks || [])
+          if !task.complete
+            tr
+              td= task.name
diff --git a/app/views/artisans/ArtisansView.coffee b/app/views/artisans/ArtisansView.coffee
new file mode 100644
index 000000000..76b4110e9
--- /dev/null
+++ b/app/views/artisans/ArtisansView.coffee
@@ -0,0 +1,8 @@
+RootView = require 'views/core/RootView'
+template = require 'templates/artisans/artisansView'
+
+module.exports = class ArtisansView extends RootView
+  template: template
+  id: 'artisans-view'
+  constructor: (options) ->
+    super options
\ No newline at end of file
diff --git a/app/views/artisans/LevelAnalyticsView.coffee b/app/views/artisans/LevelAnalyticsView.coffee
new file mode 100644
index 000000000..4a35daf7f
--- /dev/null
+++ b/app/views/artisans/LevelAnalyticsView.coffee
@@ -0,0 +1,35 @@
+RootView = require 'views/core/RootView'
+template = require 'templates/artisans/levelAnalyticsView'
+Level = require 'models/Level'
+Campaign = require 'models/Campaign'
+CocoCollection = require 'collections/CocoCollection'
+
+module.exports = class LevelAnalyticsView extends RootView
+  template: template
+  id: 'level-analytics-view'
+  constructor: (options) ->
+    super options
+    @campaigns = new CocoCollection([], 
+      url: '/db/campaign?project=name,slug,tasks'
+      model: Campaign
+    )
+    @campaigns.fetch()
+    @listenTo(@campaigns, 'sync', @onCampaignsLoaded)
+    @supermodel.loadCollection(@campaigns, 'campaigns')
+  onCampaignsLoaded: ->
+    @levels = []
+    for campaign in @campaigns.models
+      continue unless campaign.get('slug') is 'dungeon'
+      levels = campaign.get('levels')
+      for key, level of levels
+        @levels.push level.slug
+    @stats = new CocoCollection([],
+        url: '/db/analytics_perday/-/level_completions?slug=dungeons-of-kithgard&startDay=20151022&endDay=20151104'
+        model: {}
+    )
+    @stats.fetch()
+    @listenTo(@stats, 'sync', @onStatsLoaded)
+    @supermodel.loadCollection(@stats, 'stats')
+    @renderSelectors '#levelTable'
+  onStatsLoaded: ->
+    console.log @stats
\ No newline at end of file
diff --git a/app/views/artisans/LevelTasksView.coffee b/app/views/artisans/LevelTasksView.coffee
new file mode 100644
index 000000000..a898e52d4
--- /dev/null
+++ b/app/views/artisans/LevelTasksView.coffee
@@ -0,0 +1,57 @@
+RootView = require 'views/core/RootView'
+template = require 'templates/artisans/levelTasksView'
+#ThangType = require 'models/ThangType'
+Level = require 'models/Level'
+Campaign = require 'models/Campaign'
+CocoCollection = require 'collections/CocoCollection'
+
+module.exports = class LevelTasksView extends RootView
+  template: template
+  id: 'level-tasks-view'
+  events:
+    'input input': 'searchUpdate'
+    'change input': 'searchUpdate'
+  excludedCampaigns = [
+    "picoctf"
+    "auditions"
+  ]
+  constructor: (options) ->
+    super options
+    @campaigns = new CocoCollection([], 
+      url: '/db/campaign?project=name,slug,tasks'
+      model: Campaign
+    )
+    @campaigns.fetch()
+    @listenTo(@campaigns, 'sync', @onCampaignsLoaded)
+    @supermodel.loadCollection(@campaigns, 'campaigns')
+
+  onCampaignsLoaded: ->
+    @levels = {}
+    sum = 0
+    for campaign in @campaigns.models
+      continue unless excludedCampaigns.indexOf(campaign.get 'slug') is -1
+      levels = campaign.get('levels')
+      sum += Object.keys(levels).length
+      for key, level of levels
+        continue unless ///#{$('#nameSearch')[0].value}///i.test level.name
+        levelSlug = level.slug
+        @levels[levelSlug] = level
+    @processedLevels = @levels
+    for key, level of @processedLevels
+      level.tasks2 = _.filter level.tasks, (_elem) ->
+        return ///#{$('#descSearch')[0].value}///i.test _elem.name
+    @renderSelectors '#levelTable'
+
+  searchUpdate: ->
+    if not @lastLoad? or (new Date()).getTime() - @lastLoad > 60 * 1000 * 1 # Update only after a minute from last update.
+      @campaigns.fetch()
+      @listenTo(@campaigns, 'sync', @onCampaignsLoaded)
+      @supermodel.loadCollection(@campaigns, 'campaigns')
+      @lastLoad = (new Date()).getTime()
+    else
+      @onCampaignsLoaded()
+    
+
+  # Jade helper
+  hasIncompleteTasks: (level) ->
+    return level.tasks2 and level.tasks2.filter((_elem) -> return not _elem.complete).length > 0
\ No newline at end of file
diff --git a/app/views/artisans/ThangTasksView.coffee b/app/views/artisans/ThangTasksView.coffee
new file mode 100644
index 000000000..d17bef021
--- /dev/null
+++ b/app/views/artisans/ThangTasksView.coffee
@@ -0,0 +1,48 @@
+RootView = require 'views/core/RootView'
+template = require 'templates/artisans/thangTasksView'
+ThangType = require 'models/ThangType'
+CocoCollection = require 'collections/CocoCollection'
+
+module.exports = class ThangTasksView extends RootView
+  template: template
+  id: 'thang-tasks-view'
+  events:
+    'input input': 'searchUpdate'
+    'change input': 'searchUpdate'
+
+  constructor: (options) ->
+    super options
+    @thangs = new CocoCollection([],
+      url: '/db/thang.type?project=name,tasks,slug'
+      model: ThangType
+      comparator: @sortThangs
+    )
+    @lastLoad = (new Date()).getTime()
+    @listenTo(@thangs, 'sync', @onThangsLoaded)
+    @supermodel.loadCollection(@thangs, 'thangs')
+
+  searchUpdate: ->
+    if not @lastLoad? or (new Date()).getTime() - @lastLoad > 60 * 1000 * 1 # Update only after a minute from last update.
+      @thangs.fetch()
+      @listenTo(@thangs, 'sync', @onThangsLoaded)
+      @supermodel.loadCollection(@thangs, 'thangs')
+      @lastLoad = (new Date()).getTime()
+    else
+      @onThangsLoaded()
+      
+  onThangsLoaded: ->
+    @processedThangs = @thangs.filter (_elem) ->
+      # Case-insensitive search of input vs name.
+      return ///#{$('#nameSearch')[0].value}///i.test _elem.get('name')
+    for thang in @processedThangs
+      thang.tasks = _.filter thang.attributes.tasks, (_elem) ->
+        # Similar case-insensitive search of input vs description (name).
+        return ///#{$('#descSearch')[0].value}///i.test _elem.name
+    @renderSelectors '#thangTable'
+
+  sortThangs: (a, b) ->
+    a.get('name').localeCompare(b.get('name'))
+
+  # Jade helper
+  hasIncompleteTasks: (thang) ->
+    return thang.tasks and thang.tasks.filter((_elem) -> return not _elem.complete).length > 0
\ No newline at end of file