vx-aptly 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/Dockerfile +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +36 -0
- data/bin/vx-aptly +4 -0
- data/doc/aptly.example +16 -0
- data/doc/aptly.upstart +27 -0
- data/doc/cloud-files.example +9 -0
- data/docker/install.sh +47 -0
- data/docker/my_init +57 -0
- data/docker/supervisord.conf +17 -0
- data/lib/vx/aptly.rb +33 -0
- data/lib/vx/aptly/app.rb +194 -0
- data/lib/vx/aptly/cli.sh +116 -0
- data/lib/vx/aptly/config.ru +3 -0
- data/lib/vx/aptly/mixin/capture.rb +37 -0
- data/lib/vx/aptly/package.rb +110 -0
- data/lib/vx/aptly/public/404.html +153 -0
- data/lib/vx/aptly/public/css/animate.css +3158 -0
- data/lib/vx/aptly/public/css/bootstrap.min.css +5 -0
- data/lib/vx/aptly/public/css/flat-ui.css +6007 -0
- data/lib/vx/aptly/public/css/theme.css +131 -0
- data/lib/vx/aptly/public/fonts/glyphicons/flat-ui-icons-regular.eot +0 -0
- data/lib/vx/aptly/public/fonts/glyphicons/flat-ui-icons-regular.svg +126 -0
- data/lib/vx/aptly/public/fonts/glyphicons/flat-ui-icons-regular.ttf +0 -0
- data/lib/vx/aptly/public/fonts/glyphicons/flat-ui-icons-regular.woff +0 -0
- data/lib/vx/aptly/public/fonts/glyphicons/selection.json +2106 -0
- data/lib/vx/aptly/public/fonts/lato/lato-black.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-black.svg +4691 -0
- data/lib/vx/aptly/public/fonts/lato/lato-black.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-black.woff +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bold.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bold.svg +5085 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bold.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bold.woff +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bolditalic.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bolditalic.svg +4514 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bolditalic.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-bolditalic.woff +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-italic.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-italic.svg +4514 -0
- data/lib/vx/aptly/public/fonts/lato/lato-italic.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-italic.woff +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-light.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-light.svg +4691 -0
- data/lib/vx/aptly/public/fonts/lato/lato-light.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-light.woff +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-regular.eot +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-regular.svg +4691 -0
- data/lib/vx/aptly/public/fonts/lato/lato-regular.ttf +0 -0
- data/lib/vx/aptly/public/fonts/lato/lato-regular.woff +0 -0
- data/lib/vx/aptly/public/img/favicon.ico +0 -0
- data/lib/vx/aptly/public/js/app.js +12 -0
- data/lib/vx/aptly/public/js/jquery_ujs.js +446 -0
- data/lib/vx/aptly/repo.rb +161 -0
- data/lib/vx/aptly/repo_publish.rb +165 -0
- data/lib/vx/aptly/version.rb +5 -0
- data/lib/vx/aptly/views/index.erb +94 -0
- data/lib/vx/aptly/views/layout.erb +54 -0
- data/lib/vx/aptly/views/repo_publishes/new.erb +49 -0
- data/lib/vx/aptly/views/repos/edit.erb +17 -0
- data/lib/vx/aptly/views/repos/errors.erb +5 -0
- data/lib/vx/aptly/views/repos/form.erb +26 -0
- data/lib/vx/aptly/views/repos/new.erb +17 -0
- data/lib/vx/aptly/views/repos/packages.erb +51 -0
- data/lib/vx/aptly/views/repos/show.erb +66 -0
- data/spec/lib/repo_publish_spec.rb +60 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/aptly.conf +0 -0
- data/spec/support/home.rb +39 -0
- data/vx-aptly.gemspec +32 -0
- metadata +263 -0
@@ -0,0 +1,161 @@
|
|
1
|
+
module Vx
|
2
|
+
module Aptly
|
3
|
+
class Repo
|
4
|
+
|
5
|
+
include Mixin::Capture
|
6
|
+
extend Mixin::Capture
|
7
|
+
|
8
|
+
attr_accessor :error, :attributes, :notice
|
9
|
+
|
10
|
+
def initialize(params = {})
|
11
|
+
@attributes = {}
|
12
|
+
params.each do |k,v|
|
13
|
+
if self.respond_to?(:"#{k}=")
|
14
|
+
self.public_send(:"#{k}=", v)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def persisted!
|
20
|
+
@persisted = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_record?
|
24
|
+
!@persisted
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
!@error
|
29
|
+
end
|
30
|
+
|
31
|
+
def packages(reload = false)
|
32
|
+
@packages = nil if reload
|
33
|
+
@packages ||= Package.all(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
%w{ name comment default_component default_distribution number_of_packages}.each do |m|
|
37
|
+
define_method :"#{m}=" do |value|
|
38
|
+
if value.is_a?(String) && value == ""
|
39
|
+
@attributes[m.to_sym] = nil
|
40
|
+
else
|
41
|
+
@attributes[m.to_sym] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
define_method :"#{m}" do
|
46
|
+
value = @attributes[m.to_sym]
|
47
|
+
if value == nil
|
48
|
+
case m
|
49
|
+
when 'default_distribution'
|
50
|
+
value = 'ubuntu'
|
51
|
+
when 'default_component'
|
52
|
+
value = 'main'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create
|
60
|
+
line = "aptly repo create"
|
61
|
+
line << " -comment=\"#{comment}\"" if comment
|
62
|
+
line << " -component=\"#{default_component}\"" if default_component
|
63
|
+
line << " -distribution=\"#{default_distribution}\"" if default_distribution
|
64
|
+
line << " #{name}" if name
|
65
|
+
|
66
|
+
begin
|
67
|
+
capture line do |re|
|
68
|
+
@notice = "The command '#{line}' was successfuly finished with output:\n"
|
69
|
+
@notice << re
|
70
|
+
end
|
71
|
+
persisted!
|
72
|
+
rescue AptlyCommandError => e
|
73
|
+
@error = e
|
74
|
+
end
|
75
|
+
|
76
|
+
valid?
|
77
|
+
end
|
78
|
+
|
79
|
+
def update
|
80
|
+
line = "aptly repo edit"
|
81
|
+
line << " -comment=\"#{comment}\"" if comment
|
82
|
+
line << " -component=\"#{default_component}\"" if default_component
|
83
|
+
line << " -distribution=\"#{default_distribution}\"" if default_distribution
|
84
|
+
line << " #{name}" if name
|
85
|
+
|
86
|
+
begin
|
87
|
+
capture line do |re|
|
88
|
+
@notice = "The command '#{line}' was successfuly finished with output:\n"
|
89
|
+
@notice << re
|
90
|
+
end
|
91
|
+
rescue AptlyCommandError => e
|
92
|
+
@error = e
|
93
|
+
end
|
94
|
+
|
95
|
+
valid?
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy
|
99
|
+
cmd = "aptly repo drop"
|
100
|
+
cmd << " #{name}" if name
|
101
|
+
capture cmd do |re|
|
102
|
+
@notice = "The command '#{cmd}' was successfuly finished with output:\n"
|
103
|
+
@notice << re
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def publishes(reload = false)
|
108
|
+
@publishes = nil if reload
|
109
|
+
@publishes ||= RepoPublish.for_repo(self)
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_publish(options = {})
|
113
|
+
options[:repo_name] = self.name
|
114
|
+
options = {
|
115
|
+
component: default_component,
|
116
|
+
distribution: default_distribution
|
117
|
+
}.merge(options)
|
118
|
+
|
119
|
+
RepoPublish.new(options)
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
|
124
|
+
def find(name)
|
125
|
+
capture "aptly repo show #{name}" do |re|
|
126
|
+
repo = new(name: name)
|
127
|
+
repo.persisted!
|
128
|
+
|
129
|
+
re.lines.each do |line|
|
130
|
+
key, value = line.split(":").map(&:strip)
|
131
|
+
case key
|
132
|
+
when 'Comment'
|
133
|
+
repo.comment = value
|
134
|
+
when 'Default Distribution'
|
135
|
+
repo.default_distribution = value
|
136
|
+
when 'Default Component'
|
137
|
+
repo.default_component = value
|
138
|
+
when 'Number of packages'
|
139
|
+
repo.number_of_packages = value.to_i
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
repo
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def all
|
148
|
+
capture "aptly repo list" do |re|
|
149
|
+
re.lines.inject([]) do |a, line|
|
150
|
+
if line =~ / \* \[(.*)\]/
|
151
|
+
a << find($1)
|
152
|
+
end
|
153
|
+
a
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Vx
|
2
|
+
module Aptly
|
3
|
+
class RepoPublish
|
4
|
+
|
5
|
+
PublishedRepo = Struct.new(:name, :component)
|
6
|
+
|
7
|
+
include Mixin::Capture
|
8
|
+
extend Mixin::Capture
|
9
|
+
|
10
|
+
attr_reader :attributes, :error, :notice
|
11
|
+
|
12
|
+
def initialize(params = {})
|
13
|
+
@attributes = {}
|
14
|
+
params.each do |k,v|
|
15
|
+
if self.respond_to?(:"#{k}=")
|
16
|
+
self.public_send(:"#{k}=", v)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def id
|
22
|
+
[prefix, distribution].join("/")
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
!@error
|
27
|
+
end
|
28
|
+
|
29
|
+
def create
|
30
|
+
line = "aptly publish repo"
|
31
|
+
line << " -architectures=\"#{architectures}\"" if architectures
|
32
|
+
|
33
|
+
components = repos.map{|i| "" }.join(",") # all components
|
34
|
+
line << " -component=\"#{components}\""
|
35
|
+
|
36
|
+
line << " -distribution=\"#{distribution}\"" if distribution
|
37
|
+
|
38
|
+
repos.each do |repo|
|
39
|
+
line << " #{repo.name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
if repos.any? && prefix
|
43
|
+
line << " #{prefix}"
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
capture line do |re|
|
48
|
+
@notice = "The command '#{line}' was successfuly finished with output:\n"
|
49
|
+
@notice << re
|
50
|
+
end
|
51
|
+
rescue AptlyCommandError => e
|
52
|
+
@error = e
|
53
|
+
end
|
54
|
+
|
55
|
+
valid?
|
56
|
+
end
|
57
|
+
|
58
|
+
def update
|
59
|
+
begin
|
60
|
+
line = update_command_line
|
61
|
+
capture line do |re|
|
62
|
+
@notice = "The command '#{line}' was successfuly finished with output:\n"
|
63
|
+
@notice << re
|
64
|
+
end
|
65
|
+
rescue AptlyCommandError => e
|
66
|
+
@error = e
|
67
|
+
end
|
68
|
+
|
69
|
+
valid?
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_command_line
|
73
|
+
line = "aptly publish update"
|
74
|
+
line << " -architectures=\"#{architectures}\"" if architectures
|
75
|
+
line << " #{distribution}" if distribution
|
76
|
+
line << " #{prefix}" if distribution && prefix
|
77
|
+
line
|
78
|
+
end
|
79
|
+
|
80
|
+
def destroy
|
81
|
+
line = "aptly publish drop "
|
82
|
+
line << " -architectures=\"#{architectures}\"" if architectures
|
83
|
+
line << " #{distribution}" if distribution
|
84
|
+
line << " #{prefix}" if distribution && prefix
|
85
|
+
begin
|
86
|
+
capture line do |re|
|
87
|
+
@notice = "The command '#{line}' was successfuly finished with output:\n"
|
88
|
+
@notice << re
|
89
|
+
end
|
90
|
+
rescue AptlyCommandError => e
|
91
|
+
@error = e
|
92
|
+
end
|
93
|
+
valid?
|
94
|
+
end
|
95
|
+
|
96
|
+
%w{ prefix component architectures distribution }.each do |m|
|
97
|
+
define_method :"#{m}=" do |value|
|
98
|
+
if value.to_s.strip == ''
|
99
|
+
value = nil
|
100
|
+
end
|
101
|
+
@attributes[m.to_sym] = value
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method :"#{m}" do
|
105
|
+
@attributes[m.to_sym]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def repos=(val)
|
110
|
+
@attributes[:repos] = []
|
111
|
+
if val.is_a?(Array)
|
112
|
+
val.each do |repo|
|
113
|
+
if repo.is_a?(Hash)
|
114
|
+
@attributes[:repos].push PublishedRepo.new(repo[:name], repo[:component])
|
115
|
+
else
|
116
|
+
@attributes[:repos].push PublishedRepo.new(repo.to_s)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
@attributes[:repos]
|
121
|
+
end
|
122
|
+
|
123
|
+
def repos
|
124
|
+
@attributes[:repos] ||= []
|
125
|
+
@attributes[:repos]
|
126
|
+
end
|
127
|
+
|
128
|
+
class << self
|
129
|
+
def all(options = {})
|
130
|
+
capture 'aptly publish list' do |re|
|
131
|
+
re.lines.inject([]) do |a,line|
|
132
|
+
if line =~ /\* (.+)\/(.+) \[(.+)\] publishes (.*)$/
|
133
|
+
it = new(prefix: $1, distribution: $2, architectures: $3)
|
134
|
+
repos = $4
|
135
|
+
published_repos = []
|
136
|
+
|
137
|
+
repos.split(",").map(&:strip).map do |repo|
|
138
|
+
if repo =~ /\{(.*): \[(.*)\]/
|
139
|
+
published_repos << { name: $2, component: $1 }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it.repos = published_repos
|
144
|
+
|
145
|
+
a << it
|
146
|
+
end
|
147
|
+
a
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def for_repo(repo)
|
153
|
+
all.select{|pub| pub.repos.map(&:name).include?(repo.name) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def find(id)
|
157
|
+
if id.split("/").size == 1
|
158
|
+
id = "./#{id}"
|
159
|
+
end
|
160
|
+
all.find{|pub| pub.id == id } or raise(PublishNotFoundError, "The publish with id #{id} was not found")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<% @menu = [['Dashboard', '/', true]] %>
|
2
|
+
|
3
|
+
<% if @repos.any? %>
|
4
|
+
<div class="panel panel-default">
|
5
|
+
<div class="panel-heading">
|
6
|
+
<div class="pull-right">
|
7
|
+
<a href="/repos/new" class="btn btn-default btn-sm">
|
8
|
+
<%= ico 'plus' %>
|
9
|
+
Add a repo
|
10
|
+
</a>
|
11
|
+
</div>
|
12
|
+
<h6>Repos</h6>
|
13
|
+
</div>
|
14
|
+
<table class="table table-hover">
|
15
|
+
<% for repo in @repos %>
|
16
|
+
<tr>
|
17
|
+
<td>
|
18
|
+
<a href="/repos/<%= repo.name %>">
|
19
|
+
<small>
|
20
|
+
<%= repo.name %> (<%= repo.number_of_packages %>)
|
21
|
+
</small>
|
22
|
+
</a>
|
23
|
+
</td>
|
24
|
+
<td><small class="text-muted"><%= repo.comment %></small></td>
|
25
|
+
<td><small class=""><%= repo.default_distribution %></small></td>
|
26
|
+
<td><small class=""><%= repo.default_component %></small></td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
</table>
|
30
|
+
</div>
|
31
|
+
<% else %>
|
32
|
+
<div class="jumbotron">
|
33
|
+
<p class="lead">You have no repositories</p>
|
34
|
+
<p><a class="btn btn-lg btn-success" href="/repos/new" role="button">
|
35
|
+
<%= ico 'plus' %>
|
36
|
+
Create one
|
37
|
+
</a></p>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
|
41
|
+
<% if @pubs.any? %>
|
42
|
+
<div class="panel panel-default">
|
43
|
+
<div class="panel-heading">
|
44
|
+
<div class="pull-right">
|
45
|
+
<a href="/repo/publishes/new" class="btn btn-default btn-sm">
|
46
|
+
<%= ico 'plus' %>
|
47
|
+
Add a publish
|
48
|
+
</a>
|
49
|
+
</div>
|
50
|
+
<h6>Published Repos</h6>
|
51
|
+
</div>
|
52
|
+
<table class="table table-hover table-btn-group">
|
53
|
+
<% for pub in @pubs %>
|
54
|
+
<tr>
|
55
|
+
<td>
|
56
|
+
<small><%= pub.id %></small>
|
57
|
+
</td>
|
58
|
+
<td>
|
59
|
+
<small><%= pub.architectures %></small>
|
60
|
+
</td>
|
61
|
+
<td>
|
62
|
+
<% for repo in pub.repos %>
|
63
|
+
<a href="/repos/<%= repo.name %>" style="display:block">
|
64
|
+
<small class=""><%= repo.name %> (<%= repo.component %>)</small>
|
65
|
+
</a>
|
66
|
+
<% end %>
|
67
|
+
</td>
|
68
|
+
<td>
|
69
|
+
<div class="btn-group">
|
70
|
+
<a class="btn btn-xs btn-primary" href="/repo/publishes/<%= pub.id %>" data-method="patch" data-confirm="Are you sure?">
|
71
|
+
<%= ico 'upload' %>
|
72
|
+
Update
|
73
|
+
</a>
|
74
|
+
<a class="btn btn-xs btn-danger" href="/repo/publishes/<%= pub.id %>" data-method="delete" data-confirm="Are you sure?">
|
75
|
+
<%= ico 'cross' %>
|
76
|
+
Remove
|
77
|
+
</a>
|
78
|
+
</div>
|
79
|
+
</td>
|
80
|
+
</tr>
|
81
|
+
<% end %>
|
82
|
+
</table>
|
83
|
+
</div>
|
84
|
+
<% else %>
|
85
|
+
<% if @repos.any? %>
|
86
|
+
<div class="jumbotron">
|
87
|
+
<p class="lead">You have no published repos</p>
|
88
|
+
<p><a class="btn btn-lg btn-success" href="/repo/publishes/new" role="button">
|
89
|
+
<%= ico 'plus' %>
|
90
|
+
Create one
|
91
|
+
</a></p>
|
92
|
+
</div>
|
93
|
+
<% end %>
|
94
|
+
<% end %>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
|
8
|
+
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
9
|
+
<link rel="stylesheet" href="/css/flat-ui.css">
|
10
|
+
<link rel="stylesheet" href="/css/animate.css">
|
11
|
+
<link rel="stylesheet" href="/css/theme.css">
|
12
|
+
|
13
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
14
|
+
<script src="/js/jquery_ujs.js"></script>
|
15
|
+
<script src="/js/app.js"></script>
|
16
|
+
|
17
|
+
<link rel="shortcut icon" href="img/favicon.ico">
|
18
|
+
|
19
|
+
<title>Aptly Web</title>
|
20
|
+
</head>
|
21
|
+
|
22
|
+
<body>
|
23
|
+
|
24
|
+
<a href="https://github.com/vexor/vx-aptly" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/a6677b08c955af8400f44c6298f40e7d19cc5b2d/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f677261795f3664366436642e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png"></a>
|
25
|
+
|
26
|
+
<div class="container">
|
27
|
+
<div class="header">
|
28
|
+
<% if @menu %>
|
29
|
+
<ul class="nav nav-pills " role="tablist">
|
30
|
+
<% for name, url, active in @menu %>
|
31
|
+
<li role="presentation" class="<%= 'active' if active %>"><a href="<%= url %>"><%= name %></a></li>
|
32
|
+
<% end %>
|
33
|
+
</ul>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<% if flash[:notice] %>
|
38
|
+
<pre class="notice"><%= flash[:notice] %></pre>
|
39
|
+
<% end %>
|
40
|
+
<% if flash[:error] %>
|
41
|
+
<pre class="error"><%= flash[:error] %></pre>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<%= yield %>
|
45
|
+
<!--
|
46
|
+
<div class="footer">
|
47
|
+
<p>© Vexor 2014</p>
|
48
|
+
</div>
|
49
|
+
-->
|
50
|
+
|
51
|
+
</div> <!-- /container -->
|
52
|
+
</body>
|
53
|
+
</html>
|
54
|
+
|