modules/admin-full, modules/admin-core, themes/base: add port status indicators to...
[project/luci.git] / modules / admin-full / luasrc / model / cbi / admin_network / vlan.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2010-2011 Jo-Philipp Wich <xm@subsignal.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 ]]--
14
15 m = Map("network", translate("Switch"), translate("The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network."))
16
17 m.uci:foreach("network", "switch",
18 function(x)
19 local sid = x['.name']
20 local switch_name = x.name or sid
21 local has_vlan = nil
22 local has_learn = nil
23 local has_vlan4k = nil
24 local has_jumbo3 = nil
25 local min_vid = 0
26 local max_vid = 16
27 local num_vlans = 16
28 local num_ports = 6
29 local cpu_port = 5
30
31 local switch_title
32 local enable_vlan4k = false
33
34 -- Parse some common switch properties from swconfig help output.
35 local swc = io.popen("swconfig dev %q help 2>/dev/null" % switch_name)
36 if swc then
37
38 local is_port_attr = false
39 local is_vlan_attr = false
40
41 while true do
42 local line = swc:read("*l")
43 if not line then break end
44
45 if line:match("^%s+%-%-vlan") then
46 is_vlan_attr = true
47
48 elseif line:match("^%s+%-%-port") then
49 is_vlan_attr = false
50 is_port_attr = true
51
52 elseif line:match("cpu @") then
53 switch_title = line:match("^switch%d: %w+%((.-)%)")
54 num_ports, cpu_port, num_vlans =
55 line:match("ports: (%d+) %(cpu @ (%d+)%), vlans: (%d+)")
56
57 num_ports = tonumber(num_ports) or 6
58 num_vlans = tonumber(num_vlans) or 16
59 cpu_port = tonumber(cpu_port) or 5
60 min_vid = 1
61
62 elseif line:match(": pvid") or line:match(": tag") or line:match(": vid") then
63 if is_vlan_attr then has_vlan4k = line:match(": (%w+)") end
64
65 elseif line:match(": enable_vlan4k") then
66 enable_vlan4k = true
67
68 elseif line:match(": enable_vlan") then
69 has_vlan = "enable_vlan"
70
71 elseif line:match(": enable_learning") then
72 has_learn = "enable_learning"
73
74 elseif line:match(": max_length") then
75 has_jumbo3 = "max_length"
76 end
77 end
78
79 swc:close()
80 end
81
82
83 -- Switch properties
84 s = m:section(NamedSection, x['.name'], "switch",
85 switch_title and translatef("Switch %q (%s)", switch_name, switch_title)
86 or translatef("Switch %q", switch_name))
87
88 s.addremove = false
89
90 if has_vlan then
91 s:option(Flag, has_vlan, translate("Enable VLAN functionality"))
92 end
93
94 if has_learn then
95 x = s:option(Flag, has_learn, translate("Enable learning and aging"))
96 x.default = x.enabled
97 end
98
99 if has_jumbo3 then
100 x = s:option(Flag, has_jumbo3, translate("Enable Jumbo Frame passthrough"))
101 x.enabled = "3"
102 x.rmempty = true
103 end
104
105
106 -- VLAN table
107 s = m:section(TypedSection, "switch_vlan",
108 switch_title and translatef("VLANs on %q (%s)", switch_name, switch_title)
109 or translatef("VLANs on %q", switch_name))
110
111 s.template = "cbi/tblsection"
112 s.addremove = true
113 s.anonymous = true
114
115 -- Filter by switch
116 s.filter = function(self, section)
117 local device = m:get(section, "device")
118 return (device and device == switch_name)
119 end
120
121 -- Override cfgsections callback to enforce row ordering by vlan id.
122 s.cfgsections = function(self)
123 local osections = TypedSection.cfgsections(self)
124 local sections = { }
125 local section
126
127 for _, section in luci.util.spairs(
128 osections,
129 function(a, b)
130 return (tonumber(m:get(osections[a], has_vlan4k or "vlan")) or 9999)
131 < (tonumber(m:get(osections[b], has_vlan4k or "vlan")) or 9999)
132 end
133 ) do
134 sections[#sections+1] = section
135 end
136
137 return sections
138 end
139
140 -- When creating a new vlan, preset it with the highest found vid + 1.
141 s.create = function(self, section, origin)
142 -- Filter by switch
143 if m:get(origin, "device") ~= switch_name then
144 return
145 end
146
147 local sid = TypedSection.create(self, section)
148
149 local max_nr = 0
150 local max_id = 0
151
152 m.uci:foreach("network", "switch_vlan",
153 function(s)
154 if s.device == switch_name then
155 local nr = tonumber(s.vlan)
156 local id = has_vlan4k and tonumber(s[has_vlan4k])
157 if nr ~= nil and nr > max_nr then max_nr = nr end
158 if id ~= nil and id > max_id then max_id = id end
159 end
160 end)
161
162 m:set(sid, "device", switch_name)
163 m:set(sid, "vlan", max_nr + 1)
164
165 if has_vlan4k then
166 m:set(sid, has_vlan4k, max_id + 1)
167 end
168
169 return sid
170 end
171
172
173 local port_opts = { }
174 local untagged = { }
175
176 -- Parse current tagging state from the "ports" option.
177 local portvalue = function(self, section)
178 local pt
179 for pt in (m:get(section, "ports") or ""):gmatch("%w+") do
180 local pc, tu = pt:match("^(%d+)([tu]*)")
181 if pc == self.option then return (#tu > 0) and tu or "u" end
182 end
183 return ""
184 end
185
186 -- Validate port tagging. Ensure that a port is only untagged once,
187 -- bail out if not.
188 local portvalidate = function(self, value, section)
189 -- ensure that the ports appears untagged only once
190 if value == "u" then
191 if not untagged[self.option] then
192 untagged[self.option] = true
193 elseif min_vid > 0 or tonumber(self.option) ~= cpu_port then -- enable multiple untagged cpu ports due to weird broadcom default setup
194 return nil,
195 translatef("Port %d is untagged in multiple VLANs!", tonumber(self.option) + 1)
196 end
197 end
198 return value
199 end
200
201
202 local vid = s:option(Value, has_vlan4k or "vlan", "VLAN ID")
203
204 vid.rmempty = false
205 vid.forcewrite = true
206 vid.vlan_used = { }
207
208 -- Validate user provided VLAN ID, make sure its within the bounds
209 -- allowed by the switch.
210 vid.validate = function(self, value, section)
211 local v = tonumber(value)
212 local m = has_vlan4k and 4094 or (num_vlans - 1)
213 if v ~= nil and v >= min_vid and v <= m then
214 if not self.vlan_used[v] then
215 self.vlan_used[v] = true
216 return value
217 else
218 return nil,
219 translatef("Invalid VLAN ID given! Only unique IDs are allowed")
220 end
221 else
222 return nil,
223 translatef("Invalid VLAN ID given! Only IDs between %d and %d are allowed.", min_vid, m)
224 end
225 end
226
227 -- When writing the "vid" or "vlan" option, serialize the port states
228 -- as well and write them as "ports" option to uci.
229 vid.write = function(self, section, value)
230 local o
231 local p = { }
232
233 for _, o in ipairs(port_opts) do
234 local v = o:formvalue(section)
235 if v == "t" then
236 p[#p+1] = o.option .. v
237 elseif v == "u" then
238 p[#p+1] = o.option
239 end
240 end
241
242 if enable_vlan4k then
243 m:set(sid, "enable_vlan4k", "1")
244 end
245
246 m:set(section, "ports", table.concat(p, " "))
247 return Value.write(self, section, value)
248 end
249
250 -- Fallback to "vlan" option if "vid" option is supported but unset.
251 vid.cfgvalue = function(self, section)
252 return m:get(section, has_vlan4k or "vlan")
253 or m:get(section, "vlan")
254 end
255
256 -- Build per-port off/untagged/tagged choice lists.
257 local pt
258 for pt = 0, num_ports - 1 do
259 local title
260 if pt == cpu_port then
261 title = translate("CPU")
262 else
263 title = translatef("Port %d", pt)
264 end
265
266 local po = s:option(ListValue, tostring(pt), title)
267
268 po:value("", translate("off"))
269 po:value("u", translate("untagged"))
270 po:value("t", translate("tagged"))
271
272 po.cfgvalue = portvalue
273 po.validate = portvalidate
274 po.write = function() end
275
276 port_opts[#port_opts+1] = po
277 end
278
279
280 -- Switch status template
281 s = m:section(SimpleSection)
282 s.template = "admin_network/switch_status"
283 s.switch = switch_name
284 end
285 )
286
287 return m