Jeremy Stretch 7 лет назад
Родитель
Сommit
e06dece00c

+ 24 - 2
netbox/dcim/api/nested_serializers.py

@@ -3,8 +3,8 @@ from rest_framework import serializers
 from dcim.constants import CONNECTION_STATUS_CHOICES
 from dcim.models import (
     Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate,
-    Interface, Manufacturer, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, RearPort, RearPortTemplate,
-    Region, Site, VirtualChassis,
+    Interface, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerPanel, PowerPort, Rack, RackGroup, RackRole,
+    RearPort, RearPortTemplate, Region, Site, VirtualChassis,
 )
 from utilities.api import ChoiceField, WritableNestedSerializer
 
@@ -21,7 +21,9 @@ __all__ = [
     'NestedInterfaceSerializer',
     'NestedManufacturerSerializer',
     'NestedPlatformSerializer',
+    'NestedPowerFeedSerializer',
     'NestedPowerOutletSerializer',
+    'NestedPowerPanelSerializer',
     'NestedPowerPortSerializer',
     'NestedRackGroupSerializer',
     'NestedRackRoleSerializer',
@@ -247,3 +249,23 @@ class NestedVirtualChassisSerializer(WritableNestedSerializer):
     class Meta:
         model = VirtualChassis
         fields = ['id', 'url', 'master']
+
+
+#
+# Power panels/feeds
+#
+
+class NestedPowerPanelSerializer(serializers.ModelSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerpanel-detail')
+
+    class Meta:
+        model = PowerPanel
+        fields = ['id', 'url', 'name']
+
+
+class NestedPowerFeedSerializer(serializers.ModelSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail')
+
+    class Meta:
+        model = PowerFeed
+        fields = ['id', 'url', 'name']

+ 56 - 2
netbox/dcim/api/serializers.py

@@ -7,8 +7,9 @@ from dcim.constants import *
 from dcim.models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
-    Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
-    RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
+    Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
+    PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
+    VirtualChassis,
 )
 from extras.api.customfields import CustomFieldModelSerializer
 from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
@@ -587,3 +588,56 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
     class Meta:
         model = VirtualChassis
         fields = ['id', 'master', 'domain', 'tags']
+
+
+#
+# Power panels
+#
+
+
+class PowerPanelSerializer(ValidatedModelSerializer):
+    site = NestedSiteSerializer()
+    rack_group = NestedRackGroupSerializer(
+        required=False,
+        allow_null=True,
+        default=None
+    )
+
+    class Meta:
+        model = PowerPanel
+        fields = ['id', 'site', 'rack_group', 'name']
+
+
+class PowerFeedSerializer(TaggitSerializer, CustomFieldModelSerializer):
+    power_panel = NestedPowerPanelSerializer()
+    rack = NestedRackSerializer(
+        required=False,
+        allow_null=True,
+        default=None
+    )
+    type = ChoiceField(
+        choices=POWERFEED_TYPE_CHOICES,
+        default=POWERFEED_TYPE_PRIMARY
+    )
+    status = ChoiceField(
+        choices=POWERFEED_STATUS_CHOICES,
+        default=POWERFEED_STATUS_ACTIVE
+    )
+    supply = ChoiceField(
+        choices=POWERFEED_SUPPLY_CHOICES,
+        default=POWERFEED_SUPPLY_AC
+    )
+    phase = ChoiceField(
+        choices=POWERFEED_PHASE_CHOICES,
+        default=POWERFEED_PHASE_SINGLE
+    )
+    tags = TagListSerializerField(
+        required=False
+    )
+
+    class Meta:
+        model = PowerFeed
+        fields = [
+            'id', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
+            'max_utilization', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
+        ]

+ 4 - 0
netbox/dcim/api/urls.py

@@ -68,6 +68,10 @@ router.register(r'cables', views.CableViewSet)
 # Virtual chassis
 router.register(r'virtual-chassis', views.VirtualChassisViewSet)
 
+# Power
+router.register(r'power-panels', views.PowerPanelViewSet)
+router.register(r'power-feeds', views.PowerFeedViewSet)
+
 # Miscellaneous
 router.register(r'connected-device', views.ConnectedDeviceViewSet, basename='connected-device')
 

+ 23 - 2
netbox/dcim/api/views.py

@@ -16,8 +16,9 @@ from dcim import filters
 from dcim.models import (
     Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
     DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
-    Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
-    RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
+    Manufacturer, InventoryItem, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
+    PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
+    VirtualChassis,
 )
 from extras.api.serializers import RenderedGraphSerializer
 from extras.api.views import CustomFieldModelViewSet
@@ -534,6 +535,26 @@ class VirtualChassisViewSet(ModelViewSet):
     serializer_class = serializers.VirtualChassisSerializer
 
 
+#
+# Power panels
+#
+
+class PowerPanelViewSet(ModelViewSet):
+    queryset = PowerPanel.objects.all()
+    serializer_class = serializers.PowerPanelSerializer
+    # filterset_class = filters.PowerPanelFilter
+
+
+#
+# Power feeds
+#
+
+class PowerFeedViewSet(ModelViewSet):
+    queryset = PowerFeed.objects.all()
+    serializer_class = serializers.PowerFeedSerializer
+    # filterset_class = filters.PowerFeedFilter
+
+
 #
 # Miscellaneous
 #

+ 2 - 2
netbox/dcim/constants.py

@@ -448,8 +448,8 @@ RACK_DIMENSION_UNIT_CHOICES = (
 POWERFEED_TYPE_PRIMARY = 1
 POWERFEED_TYPE_REDUNDANT = 2
 POWERFEED_TYPE_CHOICES = (
-    (POWERFEED_TYPE_PRIMARY, 'AC'),
-    (POWERFEED_TYPE_REDUNDANT, 'DC'),
+    (POWERFEED_TYPE_PRIMARY, 'Primary'),
+    (POWERFEED_TYPE_REDUNDANT, 'Redundant'),
 )
 POWERFEED_SUPPLY_AC = 1
 POWERFEED_SUPPLY_DC = 2

+ 18 - 18
netbox/dcim/forms.py

@@ -3183,7 +3183,7 @@ class PowerPanelForm(BootstrapMixin, forms.ModelForm):
             'site': APISelect(
                 api_url="/api/dcim/sites/",
                 filter_for={
-                    'rackgroup': 'site_id',
+                    'rack_group': 'site_id',
                 }
             ),
         }
@@ -3231,7 +3231,7 @@ class PowerFeedForm(BootstrapMixin, CustomFieldForm):
     class Meta:
         model = PowerFeed
         fields = [
-            'site', 'power_panel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase',
+            'site', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage',
             'max_utilization', 'comments', 'tags',
         ]
         widgets = {
@@ -3241,24 +3241,24 @@ class PowerFeedForm(BootstrapMixin, CustomFieldForm):
             'rack': APISelect(
                 api_url="/api/dcim/racks/"
             ),
-            'type': StaticSelect2(),
             'status': StaticSelect2(),
+            'type': StaticSelect2(),
             'supply': StaticSelect2(),
             'phase': StaticSelect2(),
         }
 
 
 class PowerFeedCSVForm(forms.ModelForm):
-    type = CSVChoiceField(
-        choices=POWERFEED_TYPE_CHOICES,
-        required=False,
-        help_text='Primary or redundant'
-    )
     status = CSVChoiceField(
         choices=POWERFEED_STATUS_CHOICES,
         required=False,
         help_text='Operational status'
     )
+    type = CSVChoiceField(
+        choices=POWERFEED_TYPE_CHOICES,
+        required=False,
+        help_text='Primary or redundant'
+    )
     supply = CSVChoiceField(
         choices=POWERFEED_SUPPLY_CHOICES,
         required=False,
@@ -3292,14 +3292,14 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
             api_url="/api/dcim/rack-groups",
         )
     )
-    type = forms.ChoiceField(
-        choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
+    status = forms.ChoiceField(
+        choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
         required=False,
         initial='',
         widget=StaticSelect2()
     )
-    status = forms.ChoiceField(
-        choices=add_blank_choice(POWERFEED_STATUS_CHOICES),
+    type = forms.ChoiceField(
+        choices=add_blank_choice(POWERFEED_TYPE_CHOICES),
         required=False,
         initial='',
         widget=StaticSelect2()
@@ -3310,18 +3310,18 @@ class PowerFeedBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
         initial='',
         widget=StaticSelect2()
     )
-    voltage = forms.IntegerField(
-        required=False
-    )
-    amperage = forms.IntegerField(
-        required=False
-    )
     phase = forms.ChoiceField(
         choices=add_blank_choice(POWERFEED_PHASE_CHOICES),
         required=False,
         initial='',
         widget=StaticSelect2()
     )
+    voltage = forms.IntegerField(
+        required=False
+    )
+    amperage = forms.IntegerField(
+        required=False
+    )
     max_utilization = forms.IntegerField(
         required=False
     )

+ 17 - 11
netbox/dcim/models.py

@@ -2730,18 +2730,22 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
     name = models.CharField(
         max_length=50
     )
-    type = models.PositiveSmallIntegerField(
-        choices=POWERFEED_TYPE_CHOICES,
-        default=POWERFEED_TYPE_PRIMARY
-    )
     status = models.PositiveSmallIntegerField(
         choices=POWERFEED_STATUS_CHOICES,
         default=POWERFEED_STATUS_ACTIVE
     )
+    type = models.PositiveSmallIntegerField(
+        choices=POWERFEED_TYPE_CHOICES,
+        default=POWERFEED_TYPE_PRIMARY
+    )
     supply = models.PositiveSmallIntegerField(
         choices=POWERFEED_SUPPLY_CHOICES,
         default=POWERFEED_SUPPLY_AC
     )
+    phase = models.PositiveSmallIntegerField(
+        choices=POWERFEED_PHASE_CHOICES,
+        default=POWERFEED_PHASE_SINGLE
+    )
     voltage = models.PositiveSmallIntegerField(
         validators=[MinValueValidator(1)],
         default=120
@@ -2750,10 +2754,6 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
         validators=[MinValueValidator(1)],
         default=20
     )
-    phase = models.PositiveSmallIntegerField(
-        choices=POWERFEED_PHASE_CHOICES,
-        default=POWERFEED_PHASE_SINGLE
-    )
     max_utilization = models.PositiveSmallIntegerField(
         validators=[MinValueValidator(1), MaxValueValidator(100)],
         default=80,
@@ -2771,7 +2771,7 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
     tags = TaggableManager(through=TaggedItem)
 
     csv_headers = [
-        'power_panel', 'rack', 'name', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase', 'max_utilization',
+        'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization',
         'comments',
     ]
 
@@ -2790,12 +2790,18 @@ class PowerFeed(ChangeLoggedModel, CustomFieldModel):
             self.power_panel.name,
             self.rack.name if self.rack else None,
             self.name,
-            self.get_type_display(),
             self.get_status_display(),
+            self.get_type_display(),
             self.get_supply_display(),
+            self.get_phase_display(),
             self.voltage,
             self.amperage,
-            self.get_phase_display(),
             self.max_utilization,
             self.comments,
         )
+
+    def get_type_class(self):
+        return STATUS_CLASSES[self.type]
+
+    def get_status_class(self):
+        return STATUS_CLASSES[self.status]

+ 13 - 14
netbox/dcim/tables.py

@@ -145,6 +145,10 @@ STATUS_LABEL = """
 <span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
 """
 
+TYPE_LABEL = """
+<span class="label label-{{ record.get_type_class }}">{{ record.get_type_display }}</span>
+"""
+
 DEVICE_PRIMARY_IP = """
 {{ record.primary_ip6.address.ip|default:"" }}
 {% if record.primary_ip6 and record.primary_ip4 %}<br />{% endif %}
@@ -799,17 +803,10 @@ class PowerPanelTable(BaseTable):
     powerfeed_count = tables.Column(
         verbose_name='Feeds'
     )
-    actions = tables.TemplateColumn(
-        template_code=RACKROLE_ACTIONS,
-        attrs={
-            'td': {'class': 'text-right noprint'}
-        },
-        verbose_name=''
-    )
 
     class Meta(BaseTable.Meta):
         model = PowerPanel
-        fields = ('pk', 'name', 'site', 'rackgroup', 'powerfeed_count', 'actions')
+        fields = ('pk', 'name', 'site', 'rack_group', 'powerfeed_count')
 
 
 #
@@ -819,16 +816,18 @@ class PowerPanelTable(BaseTable):
 class PowerFeedTable(BaseTable):
     pk = ToggleColumn()
     name = tables.LinkColumn()
-    powerpanel = tables.LinkColumn(
+    power_panel = tables.LinkColumn(
         viewname='dcim:powerpanel',
-        args=[Accessor('powerpanel.pk')],
+        args=[Accessor('power_panel.pk')],
 
     )
-    rack = tables.LinkColumn(
-        viewname='dcim:rack',
-        accessor=Accessor('rack.pk')
+    status = tables.TemplateColumn(
+        template_code=STATUS_LABEL
+    )
+    type = tables.TemplateColumn(
+        template_code=TYPE_LABEL
     )
 
     class Meta(BaseTable.Meta):
         model = PowerFeed
-        fields = ('pk', 'name', 'powerpanel', 'rack', 'type', 'status', 'supply', 'voltage', 'amperage', 'phase')
+        fields = ('pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase')

+ 7 - 5
netbox/dcim/views.py

@@ -392,10 +392,12 @@ class RackView(View):
         prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
 
         reservations = RackReservation.objects.filter(rack=rack)
+        power_feeds = PowerFeed.objects.filter(rack=rack).select_related('power_panel')
 
         return render(request, 'dcim/rack.html', {
             'rack': rack,
             'reservations': reservations,
+            'power_feeds': power_feeds,
             'nonracked_devices': nonracked_devices,
             'next_rack': next_rack,
             'prev_rack': prev_rack,
@@ -2123,9 +2125,9 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
 
 class PowerPanelListView(ObjectListView):
     queryset = PowerPanel.objects.select_related(
-        'site', 'rackgroup'
+        'site', 'rack_group'
     ).annotate(
-        rack_count=Count('powerfeeds')
+        powerfeed_count=Count('powerfeeds')
     )
     table = tables.PowerPanelTable
     template_name = 'dcim/powerpanel_list.html'
@@ -2183,7 +2185,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 
 class PowerFeedListView(ObjectListView):
     queryset = PowerFeed.objects.select_related(
-        'powerpanel', 'rack'
+        'power_panel', 'rack'
     )
     # filter = filters.PowerFeedFilter
     # filter_form = forms.PowerFeedFilterForm
@@ -2229,7 +2231,7 @@ class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
 
 class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
     permission_required = 'dcim.change_powerfeed'
-    queryset = PowerFeed.objects.select_related('powerpanel', 'rack')
+    queryset = PowerFeed.objects.select_related('power_panel', 'rack')
     # filter = filters.PowerFeedFilter
     table = tables.PowerFeedTable
     form = forms.PowerFeedBulkEditForm
@@ -2238,7 +2240,7 @@ class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
 
 class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_powerfeed'
-    queryset = PowerFeed.objects.select_related('powerpanel', 'rack')
+    queryset = PowerFeed.objects.select_related('power_panel', 'rack')
     # filter = filters.PowerFeedFilter
     table = tables.PowerFeedTable
     default_return_url = 'dcim:powerfeed_list'

+ 1 - 1
netbox/templates/dcim/powerfeed_edit.html

@@ -9,13 +9,13 @@
             {% render_field form.power_panel %}
             {% render_field form.rack %}
             {% render_field form.name %}
-            {% render_field form.type %}
             {% render_field form.status %}
         </div>
     </div>
     <div class="panel panel-default">
         <div class="panel-heading"><strong>Characteristics</strong></div>
         <div class="panel-body">
+            {% render_field form.type %}
             {% render_field form.supply %}
             {% render_field form.voltage %}
             {% render_field form.amperage %}

+ 65 - 34
netbox/templates/dcim/rack.html

@@ -190,47 +190,37 @@
                 {% endif %}
             </div>
         </div>
-        <div class="panel panel-default">
-            <div class="panel-heading">
-                <strong>Non-Racked Devices</strong>
-            </div>
-            {% if nonracked_devices %}
-                <table class="table table-hover panel-body">
+        {% if power_feeds %}
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <strong>Power Feeds</strong>
+                </div>
+                <table class="table panel-body">
                     <tr>
-                        <th>Name</th>
-                        <th>Role</th>
+                        <th>Panel</th>
+                        <th>Feed</th>
+                        <th>Status</th>
                         <th>Type</th>
-                        <th>Parent</th>
                     </tr>
-                    {% for device in nonracked_devices %}
-                        <tr{% if device.device_type.u_height %} class="warning"{% endif %}>
+                    {% for powerfeed in power_feeds %}
+                        <tr>
                             <td>
-                                <a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a>
+                                <a href="{{ powerfeed.power_panel.get_absolute_url }}">{{ powerfeed.power_panel.name }}</a>
+
+                            <td>
+                                <a href="{{ powerfeed.get_absolute_url }}">{{ powerfeed.name }}</a>
                             </td>
-                            <td>{{ device.device_role }}</td>
-                            <td>{{ device.device_type.display_name }}</td>
                             <td>
-                                {% if device.parent_bay %}
-                                    <a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>
-                                {% else %}
-                                    <span class="text-muted">&mdash;</span>
-                                {% endif %}
+                                <span class="label label-{{ powerfeed.get_status_class }}">{{ powerfeed.get_status_display }}</span>
+                            </td>
+                            <td>
+                                <span class="label label-{{ powerfeed.get_type_class }}">{{ powerfeed.get_type_display }}</span>
                             </td>
                         </tr>
                     {% endfor %}
                 </table>
-            {% else %}
-                <div class="panel-body text-muted">None</div>
-            {% endif %}
-            {% if perms.dcim.add_device %}
-                <div class="panel-footer text-right noprint">
-                    <a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
-                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
-                        Add a non-racked device
-                    </a>
-                </div>
-            {% endif %}
-        </div>
+            </div>
+        {% endif %}
         <div class="panel panel-default">
             <div class="panel-heading">
                 <strong>Images</strong>
@@ -307,11 +297,52 @@
           {% include 'dcim/inc/rack_elevation.html' with primary_face=front_elevation secondary_face=rear_elevation face_id=0 reserved_units=rack.get_reserved_units %}
       </div>
       <div class="col-md-6 col-sm-6 col-xs-12">
-        <div class="rack_header">
+         <div class="rack_header">
             <h4>Rear</h4>
-        </div>
-        {% include 'dcim/inc/rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 reserved_units=rack.get_reserved_units %}
+         </div>
+         {% include 'dcim/inc/rack_elevation.html' with primary_face=rear_elevation secondary_face=front_elevation face_id=1 reserved_units=rack.get_reserved_units %}
       </div>
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <strong>Non-Racked Devices</strong>
+            </div>
+            {% if nonracked_devices %}
+                <table class="table table-hover panel-body">
+                    <tr>
+                        <th>Name</th>
+                        <th>Role</th>
+                        <th>Type</th>
+                        <th>Parent</th>
+                    </tr>
+                    {% for device in nonracked_devices %}
+                        <tr{% if device.device_type.u_height %} class="warning"{% endif %}>
+                            <td>
+                                <a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a>
+                            </td>
+                            <td>{{ device.device_role }}</td>
+                            <td>{{ device.device_type.display_name }}</td>
+                            <td>
+                                {% if device.parent_bay %}
+                                    <a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>
+                                {% else %}
+                                    <span class="text-muted">&mdash;</span>
+                                {% endif %}
+                            </td>
+                        </tr>
+                    {% endfor %}
+                </table>
+            {% else %}
+                <div class="panel-body text-muted">None</div>
+            {% endif %}
+            {% if perms.dcim.add_device %}
+                <div class="panel-footer text-right noprint">
+                    <a href="{% url 'dcim:device_add' %}?site={{ rack.site.pk }}&rack={{ rack.pk }}" class="btn btn-primary btn-xs">
+                        <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+                        Add a non-racked device
+                    </a>
+                </div>
+            {% endif %}
+        </div>
     </div>
 </div>
 {% endblock %}