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