Kaynağa Gözat

Merge branch 'groups'

Andrea Fazzi 5 yıl önce
ebeveyn
işleme
080a3f2e23

+ 40 - 0
Godeps/Godeps.json

@@ -57,6 +57,46 @@
 			"Comment": "v0.2.3-4-ge7a84e9",
 			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
 		},
+		{
+			"ImportPath": "github.com/gobwas/glob",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/compiler",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/match",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/syntax",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/syntax/ast",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/syntax/lexer",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/util/runes",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
+		{
+			"ImportPath": "github.com/gobwas/glob/util/strings",
+			"Comment": "v0.2.3-4-ge7a84e9",
+			"Rev": "e7a84e9525fe90abcda167b604e483cc959ad4aa"
+		},
 		{
 			"ImportPath": "github.com/gocarina/gocsv",
 			"Rev": "7099e67763c29f812fa2ed7083f32e38be60125a"

+ 20 - 0
compose/karmen/ldap/bootstrap/ldif/foo.org.ldif

@@ -85,6 +85,26 @@ gidNumber: 6001
 objectclass: posixGroup
 objectclass: top
 
+dn: ou=Segreterie,ou=Groups,dc=foo,dc=org
+changetype: add
+ou: Segreterie
+objectclass: organizationalUnit
+objectclass: top
+
+dn: cn=Segreteria del personale,ou=Segreterie,ou=Groups,dc=foo,dc=org
+changetype: add
+cn: Segreteria del personale
+gidNumber: 6015
+objectclass: posixGroup
+objectclass: top
+
+dn: cn=Segreteria didattica,ou=Segreterie,ou=Groups,dc=foo,dc=org
+changetype: add
+cn: Segreteria didattica
+gidNumber: 6016
+objectclass: posixGroup
+objectclass: top
+
 dn: ou=Dipartimenti,ou=Groups,dc=foo,dc=org
 changetype: add
 ou: Dipartimenti

Dosya farkı çok büyük olduğundan ihmal edildi
+ 12 - 0
compose/karmen/sql/karmen_test.sql


+ 3 - 0
handlers/handlers.go

@@ -60,8 +60,11 @@ var (
 		"students",
 		"offices",
 		"administratives",
+
 		"documents",
 		"jobs",
+
+		"groups",
 	}
 )
 

Dosya farkı çok büyük olduğundan ihmal edildi
+ 518 - 464
orm/compose/sql/karmen_test.sql


+ 284 - 0
orm/group.go

@@ -0,0 +1,284 @@
+package orm
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/gobwas/glob"
+	"github.com/jinzhu/gorm"
+	"gogs.carducci-dante.gov.it/karmen/core/renderer"
+)
+
+type Group struct {
+	gorm.Model
+
+	Name string
+
+	Query string
+
+	TeacherIDs []uint `schema:"teacher_ids" gorm:"-"`
+
+	Teachers []*Teacher `gorm:"many2many:group_teachers"`
+}
+
+type GroupForAdd struct {
+	AllTeachers []*Teacher
+}
+
+type GroupForUpdate struct {
+	Group Group
+
+	AllTeachers []*Teacher
+
+	SelectedTeacher map[uint]string
+}
+
+func (g *Group) GetID() uint { return g.ID }
+
+func GetGroups(args map[string]string) (interface{}, error) {
+	return GetGroupsAll(args)
+}
+
+func GetGroupsAll(args map[string]string) (interface{}, error) {
+	var groups []*Group
+
+	if err := DB().Preload("Teachers").Order("name").Find(&groups).Error; err != nil {
+		return nil, err
+	}
+
+	for _, group := range groups {
+		if group.Query != "" {
+			err := group.updateTeachersAssociation()
+			if err != nil {
+				return nil, err
+			}
+		}
+
+	}
+
+	return groups, nil
+}
+
+func GetGroupAll(args map[string]string) (interface{}, error) {
+	var group Group
+
+	id := args["id"]
+
+	if err := DB().Preload("Teachers").Where("id = ?", id).Find(&group).Error; err != nil {
+		return nil, err
+	}
+
+	if group.Query != "" {
+		err := group.updateTeachersAssociation()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return group, nil
+}
+
+func GetGroupForUpdate(args map[string]string) (interface{}, error) {
+	var data GroupForUpdate
+
+	id := args["id"]
+
+	if err := DB().Preload("Teachers").First(&data.Group, id).Error; err != nil {
+		return nil, err
+	}
+
+	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+		return nil, err
+	}
+
+	data.SelectedTeacher = make(map[uint]string)
+	for _, t := range data.Group.Teachers {
+		data.SelectedTeacher[t.ID] = "selected"
+	}
+
+	return data, nil
+}
+
+func GetGroupForAdd(args map[string]string) (interface{}, error) {
+	var data GroupForAdd
+
+	if err := DB().Find(&data.AllTeachers).Error; err != nil {
+		return nil, err
+	}
+
+	return data, nil
+}
+
+func GetGroup(args map[string]string) (interface{}, error) {
+	var group Group
+
+	id := args["id"]
+
+	if err := DB().Preload("Teachers").Where("id = ?", id).Find(&group).Error; err != nil {
+		return nil, err
+	}
+
+	return &group, nil
+
+}
+
+func UpdateGroup(args map[string]string, r *http.Request) (IDer, error) {
+	group, err := GetGroup(args)
+	if err != nil {
+		return nil, err
+	}
+	err = renderer.Decode(group, r)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := DB().Where(group.(*Group).TeacherIDs).Find(&group.(*Group).Teachers).Error; err != nil {
+		return nil, err
+	}
+
+	teachers, err := group.(*Group).executeTeacherQuery()
+
+	group.(*Group).Teachers = append(group.(*Group).Teachers, teachers...)
+
+	_, err = SaveGroup(group)
+	if err != nil {
+		return nil, err
+	}
+	if err := DB().Model(group).Association("Teachers").Replace(group.(*Group).Teachers).Error; err != nil {
+		return nil, err
+	}
+
+	group, err = GetGroup(args)
+	if err != nil {
+		return nil, err
+	}
+	return group.(*Group), nil
+}
+
+func SaveGroup(group interface{}) (interface{}, error) {
+	if err := DB().Save(group).Error; err != nil {
+		return nil, err
+	}
+	return group, nil
+}
+
+func DeleteGroup(args map[string]string, r *http.Request) (IDer, error) {
+	group, err := GetGroup(args)
+	if err != nil {
+		return nil, err
+	}
+	if err := DB().Unscoped().Delete(group.(*Group)).Error; err != nil {
+		return nil, err
+	}
+	return group.(*Group), nil
+}
+
+func AddGroup(args map[string]string, r *http.Request) (IDer, error) {
+	group := new(Group)
+	err := renderer.Decode(group, r)
+	if err != nil {
+		return nil, err
+	}
+	group, err = CreateGroup(group)
+	if err != nil {
+		return nil, err
+	}
+	return group, nil
+}
+
+func CreateGroup(group *Group) (*Group, error) {
+	var groups []*Group
+	if err := DB().Where("name=?", group.Name).Find(&groups).Error; err != nil {
+		return nil, err
+	}
+	if len(groups) > 0 {
+		return nil, fmt.Errorf("Group %s already exists!", group.Name)
+	}
+
+	if err := DB().Where(group.TeacherIDs).Find(&group.Teachers).Error; err != nil {
+		return nil, err
+	}
+
+	teachers, err := group.executeTeacherQuery()
+	if err != nil {
+		return nil, err
+	}
+	group.Teachers = append(group.Teachers, teachers...)
+	if err := DB().Create(group).Error; err != nil {
+		return nil, err
+	}
+	return group, nil
+}
+
+func (group *Group) RestAPIPath() string {
+	return "groupes"
+}
+
+func (group *Group) updateTeachersAssociation() error {
+	teachers, err := group.executeTeacherQuery()
+	if err != nil {
+		return err
+	}
+	if err := DB().Model(group).Association("Teachers").Replace(teachers).Error; err != nil {
+		return err
+	}
+	return nil
+}
+
+func (group *Group) executeTeacherQuery() ([]*Teacher, error) {
+	var (
+		result      []*Teacher
+		allTeachers []*Teacher
+	)
+
+	fields := strings.Split(group.Query, " ")
+
+	if len(fields) > 0 {
+
+		if err := DB().Find(&allTeachers).Error; err != nil {
+			return nil, err
+		}
+
+		for _, field := range fields {
+
+			nameValue := strings.Split(field, ":")
+
+			if len(nameValue) < 2 {
+				return nil, fmt.Errorf("Error parsing search query %s on field %s", group.Query, field)
+			}
+
+			name := nameValue[0]
+			value := nameValue[1]
+
+			switch name {
+			case "surname":
+				g, err := glob.Compile(value)
+				if err != nil {
+					return nil, err
+				}
+
+				for _, t := range allTeachers {
+					if g.Match(t.Surname) {
+						result = append(result, t)
+					}
+				}
+
+			case "coordinator":
+				for _, t := range allTeachers {
+					c, err := t.CoordinatorClasses()
+					if err != nil {
+						return nil, err
+					}
+					if len(c) > 0 {
+						result = append(result, t)
+					}
+				}
+
+			}
+		}
+
+	}
+
+	return result, nil
+}

+ 24 - 0
orm/mappings.go

@@ -91,6 +91,7 @@ var (
 		"/api/administratives/add/":        GetNothing,
 
 		// Documents
+		
 		"/documents":              GetDocumentsAll,
 		"/documents/{id}":         GetDocumentAll,
 		"/documents/{id}/update":  GetDocumentForUpdate,
@@ -110,6 +111,19 @@ var (
 
 		"/api/jobs/{id}": GetJob,
 		"/api/jobs/add/": GetNothing,
+
+		// Groups
+		
+		"/groups":             GetGroupsAll,
+		"/groups/{id}":        GetGroupAll,
+		"/groups/{id}/update": GetGroupForUpdate,
+		"/groups/add/":        GetGroupForAdd,
+
+		"/api/groups":             GetGroupsAll,
+		"/api/groups/{id}":        GetGroupAll,
+		"/api/groups/{id}/update": GetGroup,
+		"/api/groups/add/":        GetNothing,
+
 	}
 
 	Post map[string]PostFn = map[string]PostFn{
@@ -202,5 +216,15 @@ var (
 
 		"/api/jobs/{id}/update": UpdateJob,
 		"/api/jobs/{id}/delete": DeleteJob,
+=======
+		// Groups
+		"/groups/{id}/update": UpdateGroup,
+		"/groups/{id}/delete": DeleteGroup,
+		"/groups/add/":        AddGroup,
+
+		"/api/groups/{id}/update": UpdateGroup,
+		"/api/groups/{id}/delete": DeleteGroup,
+		"/api/groups/add/":        AddGroup,
+>>>>>>> groups
 	}
 )

+ 1 - 0
orm/orm.go

@@ -82,6 +82,7 @@ func AutoMigrate() {
 		&File{},
 		&GeneratorType{},
 		&Log{},
+		&Group{},
 	).Error; err != nil {
 		panic(err)
 	}

+ 52 - 0
orm/orm_test.go

@@ -313,3 +313,55 @@ func (t *testSuite) TestSaveStudent() {
 		t.Equal(true, student.(*Student).Handicap)
 	}
 }
+
+// Groups
+
+func (t *testSuite) TestGetGroups() {
+	groups, err := GetGroups(map[string]string{})
+	t.Nil(err)
+	t.Equal(1, len(groups.([]*Group)))
+}
+
+func (t *testSuite) TestGetGroup() {
+	group, err := GetGroup(map[string]string{"id": "1"})
+	t.Nil(err)
+	if !t.Failed() {
+		t.Equal("Gruppo", group.(*Group).Name)
+	}
+
+}
+
+func (t *testSuite) TestTeacherQuery() {
+	group := new(Group)
+
+	// Select all
+
+	group.Query = "surname:*"
+	teachers, err := group.executeTeacherQuery()
+	t.Nil(err)
+
+	if !t.Failed() {
+		t.Equal(11, len(teachers))
+	}
+
+	// Select teacher with surname beginning with the letter A
+
+	group.Query = "surname:A*"
+	teachers, err = group.executeTeacherQuery()
+	t.Nil(err)
+
+	if !t.Failed() {
+		t.Equal(2, len(teachers))
+	}
+
+	// Select teacher with surname beginning with the letter A
+
+	group.Query = "coordinator:true"
+	teachers, err = group.executeTeacherQuery()
+	t.Nil(err)
+
+	if !t.Failed() {
+		t.Equal(1, len(teachers))
+	}
+
+}

+ 2 - 1
orm/teacher.go

@@ -20,6 +20,7 @@ type Teacher struct {
 	Subjects    []*Subject
 	Activities  []*Activity
 	Departments []*Department
+	Groups      []*Group `gorm:"many2many:group_teachers"`
 
 	Coordinator []*Class
 	Minuter     []*Class
@@ -82,7 +83,7 @@ func GetTeacherAll(args map[string]string) (interface{}, error) {
 
 	id := args["id"]
 
-	if err := DB().Where("id = ?", id).Find(&teacher).Error; err != nil {
+	if err := DB().Preload("Groups").Where("id = ?", id).Find(&teacher).Error; err != nil {
 		return nil, err
 	}
 

+ 1 - 1
templates/classes_add_update.html.tpl

@@ -5,7 +5,7 @@
   {{if .Options.Get "update"}}
   <nav aria-label="breadcrumb">
     <ol class="breadcrumb">
-      <li class="breadcrumb-item"><a href="/classes">Classi</a></li>
+      <li class="breadcrumb-item"><a href="/classes?{{query "tpl_layout" "base" "tpl_content" "teachers"}}">Classi</a></li>
       <li class="breadcrumb-item"><a href="/classes/{{.Data.Class.ID}}?{{query "tpl_layout" "base" "tpl_content" "classes"}}">{{.Data.Class.Name}}</a></li>
       <li class="breadcrumb-item active"><a href="#">Aggiorna la classe</a></li>
     </ol>

+ 43 - 0
templates/groups.html.tpl

@@ -0,0 +1,43 @@
+{{ define "content" }}
+
+<div class="container">
+  
+  <div class="karmen-info-header">
+    <div class="row">
+      <div class="col-md-8">
+	<h1>Gruppi ({{len .Data}})</h1>
+      </div>
+      <div class="col-md-4">
+	<a href="/groups/add/?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}" class="btn btn-primary float-right">
+	  <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+	  Crea nuovo gruppo
+	</a>
+      </div>
+    </div>
+  </div>
+
+  <div class="input-group" style="margin-bottom: 20px">
+    <span class="input-group-addon" id="search-query"><span class="glyphicon glyphicon-search"></span></span>
+    <input type="text" id="myInput" class="form-control" aria-describedby="search-query">
+  </div>
+  
+  {{if not .Data}}
+  <p>Non c'è alcun elemento da visualizzare</p>
+  {{else}}
+  <div class="list-group" id="myUL">
+    {{range $group := .Data}}
+    <a class="list-group-item list-group-item-action" href="/groups/{{$group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}">
+      <span class="fa fa-users"></span>
+      {{$group.Name}}
+      <div class="text-right">
+ 	{{range $teacher := $group.Teachers}}
+	<small>{{$teacher.Surname}}</small>
+	{{end}}
+      </div>
+    </a>
+    {{end}}
+  </div>
+  {{end}}
+</div>
+  
+{{ end }}

+ 80 - 0
templates/groups_add_update.html.tpl

@@ -0,0 +1,80 @@
+{{ define "content" }}
+
+<div class="container">
+
+  {{if .Options.Get "update"}}
+  <nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+      <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
+      <li class="breadcrumb-item active"><a href="#">Aggiorna gruppo</a></li>
+    </ol>
+  </nav>
+  {{else}}
+  <nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+      <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
+      <li class="breadcrumb-item active"><a href="#">Aggiungi</a></li>
+    </ol>
+  </nav>
+  {{end}}
+
+  {{if .Options.Get "update"}}
+  <div class="karmen-info-header">
+    <div class="row">
+      <div class="col-md-8">
+	<h1>Aggiorna gruppo</h1>
+      </div>
+    </div>
+  </div>
+  
+  {{else}}
+  <h1 class="karmen-info-header">Crea nuovo gruppo</h1>
+  {{end}}
+
+  {{if .Options.Get "update"}}
+  <form id="form_groups_add_update" action="/groups/{{.Data.Group.ID}}/update" method="POST" role="form" data-toggle="validator">
+    {{else}}
+    <form id="form_groups_add_update" action="/groups/add/" method="POST" role="form" data-toggle="validator">
+      {{end}}
+
+      <div class="form-group has-feedback">
+        <label class="control-label" for="group_name">Nome</label>
+        <input type="text" name="Name" class="form-control" id="group_name" placeholder="Nome" {{if .Options.Get "update"}} value="{{.Data.Group.Name}}" {{end}} required>
+      </div>
+
+      <div class="form-group has-feedback">
+        <label class="control-label" for="group_query">Stringa di popolamento</label>
+        <input type="text" name="Query" class="form-control" id="group_query" placeholder="surname:*" {{if .Options.Get "update"}} value="{{.Data.Group.Query}}" {{end}}>
+      </div>
+
+      <div class="form-group">
+        <label class="control-label" for="teacher_id">Docenti</label>
+        <select name="teacher_ids" class="form-control selectpicker" id="teacher_ids" placeholder="Docenti" data-live-search="true" form="form_groups_add_update" title="Seleziona i nomi dei docenti" data-dropup-auto="false" multiple>
+          <option value="0"></option>
+          {{range $teacher := .Data.AllTeachers}}
+          {{if $.Options.Get "update"}}
+          <option
+	    value="{{$teacher.ID}}"
+	    {{index $.Data.SelectedTeacher $teacher.ID}}>{{$teacher.CompleteName}}
+          </option>
+          {{else}}
+          <option value="{{$teacher.ID}}">{{$teacher.CompleteName}}</option>
+          {{end}}
+          {{end}}
+        </select>
+      </div>
+
+      <div class="form-group">
+        <button type="submit" class="btn btn-primary">Salva</button>
+        {{if .Options.Get "update"}}
+        <a href="/groups/{{.Data.Group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}" class="btn btn-default">Annulla</a>
+        {{else}}
+        <a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}" class="btn btn-default">Annulla</a>
+        {{end}}
+      </div>
+    
+    </form>
+
+</div>
+
+{{ end }}

+ 60 - 0
templates/groups_show.html.tpl

@@ -0,0 +1,60 @@
+{{ define "content" }}
+
+<div class="container">
+
+  <nav aria-label="breadcrumb">
+  <ol class="breadcrumb">
+    <li class="breadcrumb-item"><a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a></li>
+    <li class="breadcrumb-item active"><a href="#">{{.Data.Name}}</a></li>
+  </ol>
+  </nav>
+
+  <div class="karmen-info-header">
+    <div class="row">
+      <div class="col-md-8">
+	<h1>{{.Data.Name}}</h1>
+      </div>
+      <div class="col-md-4">
+	<div class="btn-group float-right" role="group">
+	  <a href="/groups/add/?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}" class="btn btn-success">
+	    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
+	    Crea
+	  </a>
+	  <a href="/groups/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "groups_add_update" "update" "true"}}"  class="btn btn-primary">
+	    <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
+	    Modifica
+	  </a>
+	  <button href="/groups/{{.Data.ID}}/delete"
+		  data-url="/groups/{{.Data.ID}}/delete"
+		  class="btn btn-danger karmen-ajax-delete">
+	    <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
+	    Elimina
+	  </button>
+	</div>
+      </div>
+    </div>
+  </div>
+  
+  <div class="row">
+    <div class="col-md-12">
+      
+      <h2 class="karmen-relation-header">Docenti associati al gruppo</h2>
+      {{if .Data.Teachers}}
+      <div class="list-group" id="students_list_group">
+	{{range $teacher := .Data.Teachers}}
+	<a href="/teachers/{{$teacher.ID}}?{{query "tpl_layout" "base" "tpl_content" "teachers_show"}}" class="list-group-item list-group-item-action">
+	  <span class="fa fa-user"></span>
+	  {{$teacher.CompleteName}}
+	  {{end}}
+	  </a>
+      </div>
+      {{else}}
+      <p>Al gruppo non è associato alcun docente. Clicca <a href="/groups/{{.Data.ID}}/update?{{query "tpl_layout" "base" "tpl_content" "groups_add_update"}}">qui</a> per modificare questo gruppo.</p>
+      {{end}}
+    </div>
+    
+  </div>
+
+</div>    
+
+{{ end }}

+ 3 - 0
templates/layout/base.html.tpl

@@ -45,6 +45,9 @@
            <li class="nav-item">
 	     <a class="nav-item nav-link" href="/documents?{{query "tpl_layout" "base" "tpl_content" "documents"}}">Documenti<span class="badge badge-danger experimental">Sperimentale</span></a>
 	   </li>
+           <li class="nav-item">
+	     <a class="nav-item nav-link" href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">Gruppi</a>
+	   </li>
 	  </ul>
 
 	  <ul class="nav navbar-nav navbar-right">

+ 19 - 0
templates/teachers_show.html.tpl

@@ -97,6 +97,25 @@
     
   </div>
 
+  <div class="row">
+    <div class="col-md-12">
+      
+      <h2 class="karmen-relation-header">Gruppi a cui il docente appartiene</h2>
+      {{if .Data.Groups}}
+      <div class="list-group" id="groups_list_group">
+    	{{range $group := .Data.Groups}}
+    	<a href="/groups/{{$group.ID}}?{{query "tpl_layout" "base" "tpl_content" "groups_show"}}" class="list-group-item list-group-item-action">
+    	  <span class="fa fa-book"></span>
+    	  {{$group.Name}}
+    	  {{end}}
+    	</a>
+      </div>
+      {{else}}
+      <p>Il docente non è associato ad alcun gruppo. Clicca <a href="/groups?{{query "tpl_layout" "base" "tpl_content" "groups"}}">qui</a> per creare un nuovo gruppo.</p>{{end}}
+    </div>
+    
+  </div>
+
   <div class="row">
     <div class="col-md-12">
    

+ 2 - 0
watch.sh

@@ -1,6 +1,8 @@
 #!/bin/bash
 
+
 echo "Executing Makefile..."
+
 make -k
 
 while inotifywait -r -e modify ./; do

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor