<?php
namespace App\Controller;
use App\Entity\File;
use App\Entity\FileGroup;
use App\Entity\Rollout;
use App\Entity\User;
use App\Entity\Workspace;
use App\Form\RolloutType;
use App\Repository\FileGroupRepository;
use App\Repository\RolloutRepository;
use App\Repository\DeviceRepository;
use App\Service\BlockChainService;
use App\Service\IotBrokerService;
use App\Service\IpfsService;
use App\Utils\Datatable;
use DateTime;
use Exception;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @Route("/rollout")
*/
class RolloutController extends AbstractController
{
/**
* @Route("/list", name="rollout_index", methods={"GET"})
* @IsGranted("ROLLOUT_VIEW")
* @Security("is_granted('ROLE_ADMIN',user) or is_granted('ROLLOUT_VIEW', user)")
* @param RolloutRepository $rolloutRepository
* @return Response
* @throws Exception
*/
public function index(RolloutRepository $rolloutRepository): Response
{
/** @var User $user */
$user = $this->getUser();
/** @var Workspace $workspace */
$workspace = $user->getWorkspace();
return $this->render(
'rollout/index.html.twig',
[
'planned_rollouts' => $rolloutRepository->getPlanned(
$workspace,
5
),
'running_rollouts' => $rolloutRepository->getRunning(
$workspace,
5
),
]
);
}
/**
* @Route("/fetch/planned/{nonce}", name="fetch_rollout_planned", methods={"GET"})
* @IsGranted("ROLLOUT_VIEW")
* @param Request $request
* @param Datatable $datatable
* @param RolloutRepository $rolloutRepository
* @param $nonce
* @return Response
* @throws Exception
*/
public function fetchRolloutsPlanned(
Request $request,
Datatable $datatable,
RolloutRepository $rolloutRepository,
$nonce
): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
/** @var User $user */
$user = $this->getUser();
$datatables = $datatable
->withColumnType(
[ // list column in order
'id' => ['alias' => 'r'],
'priority' => [
'alias' => 'r',
'twig' => 'components/datatable.rollout.priority.twig',
],
'disabled' => [
'alias' => 'r',
'twig' => 'components/datatable.rollout.disabled.twig',
],
'name' => [
'alias' => 'r',
'twig' => 'rollout/datatable.link.twig',
],
'startDate' => [
'alias' => 'r',
'twig' => 'components/datatable.datetime.twig',
],
'fileName' => [
'alias' => 'f',
],
'deviceGroups' => [
'alias' => 'dg',
],
'devicesInGroups' => [
'alias' => 'dg',
],
'updatedDevices' => [
'alias' => 'dg',
],
]
)
->withOptions(['nonce' => $nonce])
->renderActionView('rollout/datatable.action.planned.twig') // provide custom twig to render action column
->isRollout(true)
->withQueryBuilder($rolloutRepository->queryPlanned($user->getWorkspace()))
->withRequestParams($request->query->all());
return new JsonResponse($datatables->getResponse());
}
/**
* @Route("/fetch/running/{nonce}", name="fetch_rollout_running", methods={"GET"})
* @IsGranted("ROLLOUT_VIEW")
* @param Request $request
* @param Datatable $datatable
* @param RolloutRepository $rolloutRepository
* @param $nonce
* @return Response
* @throws Exception
*/
public function fetchRolloutsRunning(
Request $request,
Datatable $datatable,
RolloutRepository $rolloutRepository,
$nonce
): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
/** @var User $user */
$user = $this->getUser();
$datatables = $datatable
->withColumnType(
[ // list column in order
'id' => ['alias' => 'r'],
'priority' => [
'alias' => 'r',
'twig' => 'components/datatable.rollout.priority.twig',
],
'disabled' => [
'alias' => 'r',
'twig' => 'components/datatable.rollout.disabled.twig',
],
'name' => [
'alias' => 'r',
'twig' => 'rollout/datatable.link.twig',
],
'startDate' => [
'alias' => 'r',
'twig' => 'components/datatable.datetime.twig',
],
// 'realStartDate' => [
// 'alias' => 'r',
// 'twig' => 'components/datatable.datetime.twig',
// ],
'fileName' => [
'alias' => 'f',
],
'deviceGroups' => [
'alias' => 'dg',
],
'devicesInGroups' => [
'alias' => 'dg',
],
'updatedDevices' => [
'alias' => 'dg',
],
'leftDevices' => [
'alias' => 'dg',
],
]
)
->withOptions(['nonce' => $nonce])
->renderActionView('rollout/datatable.action.running.twig') // provide custom twig to render action column
->isRollout(true)
->withQueryBuilder($rolloutRepository->queryRunning($user->getWorkspace()))
->withRequestParams($request->query->all());
return new JsonResponse($datatables->getResponse());
}
/**
* @Route("/list/running", name="rollout_list_running", methods={"GET"})
* @IsGranted("ROLLOUT_VIEW")
* @return Response
* @throws Exception
*/
public function runningList(): Response
{
return $this->render(
'rollout/running.html.twig'
);
}
/**
* @Route("/list/planned", name="rollout_list_planned", methods={"GET"})
* @IsGranted("ROLLOUT_VIEW")
* @return Response
* @throws Exception
*/
public function plannedList(): Response
{
return $this->render(
'rollout/planned.html.twig'
);
}
/**
* @Route("/new/{nonce}/{type}", name="rollout_new", methods={"GET","POST"})
* @IsGranted("ROLLOUT_CREATE")
* @param Request $request
* @param TranslatorInterface $translator
* @param FileGroupRepository $fileGroupRepository
* @param IpfsService $ipfsService
* @param IotBrokerService $iotBrokerService
* @param BlockChainService $blockChainService
* @param $nonce
* @param string $type
* @return Response
* @throws Exception
*/
public function new(Request $request, TranslatorInterface $translator, FileGroupRepository $fileGroupRepository, IpfsService $ipfsService, IotBrokerService $iotBrokerService, BlockChainService $blockChainService, $nonce, string $type = "index"): Response
{
if ($request->isMethod('GET') && !$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
$rollout = new Rollout();
/** @var User $user */
$user = $this->getUser();
/** @var Workspace $workspace */
$workspace = $user->getWorkspace();
$rollout->setWorkspace($workspace);
if (!$this->isGranted('VIEW_PLAN_SCHEDULED_ROLLOUTS', $this->getUser())) {
$rollout->setStartDate(new DateTime());
}
$form = $this->createForm(
RolloutType::class,
$rollout,
[
'action' => $this->generateUrl(
'rollout_new',
[
'type' => $type,
'nonce' => $nonce
]
),
'method' => 'POST',
'dt_trans_format' => $translator->trans('datetime_formtype')
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$configType = $form->get("configType")->getData();
if ($configType == 1) {
$fileGroup = $form->get("fileGroup")->getData();
$fileGroupId = $fileGroup->getId();
$configText = $form->get("configurationText")->getData();
$fileName = time()."_".$fileGroupId;
$requestFile = fopen($fileName, "w") or die("Unable to open file!");
fwrite($requestFile, $configText);
fclose($requestFile);
$uploadedFile = new UploadedFile($fileName, $fileName, null, null, false);
$cid = $ipfsService->upload($uploadedFile);
/** @var File $file */
$file = new File();
$file->addFileGroup($fileGroup);
$file->setWorkspace($workspace);
$file->setName($fileName);
$file->setClientFileName($fileName);
$file->setUrl($cid);
$file->setVersion(1);
$file->addRollout($rollout);
$entityManager->persist($file);
$entityManager->persist($rollout);
$entityManager->flush();
$blockChainService->registerFirmware($file);
}
$disabled = $rollout->getDisabled();
if ($rollout->getPriority() === 1) {
$this->disableRollouts($rollout);
}
$rollout->setDisabled($disabled);
$entityManager->persist($rollout);
$entityManager->flush();
//Lora check start
if (intval($_ENV['ALLOW_LORA_A']) === 1) {
$loraDeviceGroups = [];
foreach ($rollout->getDeviceGroups() as $deviceGroup) {
if ($deviceGroup->isLoraTypeA()) {
$loraDeviceGroups[] = $deviceGroup;
}
}
if (count($loraDeviceGroups) > 0) {
if ($iotBrokerService->send($loraDeviceGroups, $rollout, "CLASS_A") === true) {
$this->addFlash("success", "IOT Broker - request successfully");
}
}
}
if (intval($_ENV['ALLOW_LORA_C']) === 1) {
$loraDeviceGroups = [];
foreach ($rollout->getDeviceGroups() as $deviceGroup) {
if ($deviceGroup->isLoraTypeC()) {
$loraDeviceGroups[] = $deviceGroup;
}
}
if (count($loraDeviceGroups) > 0) {
if ($iotBrokerService->send($loraDeviceGroups, $rollout, "CLASS_C") === true) {
$this->addFlash("success", "IOT Broker - request successfully");
}
}
}
//lora check end
if ($type === "running" || $type === "planned") {
if ($rollout->getStartDate() >= new DateTime('-5 second')) {
return $this->redirectToRoute('rollout_list_planned');
}
return $this->redirectToRoute('rollout_list_running');
}
return $this->redirectToRoute('rollout_index');
}
return $this->render(
'rollout/new.html.twig',
[
'nonce' => urldecode(str_replace('?2F?', '%2F', $nonce)),
'rollout' => $rollout,
'form' => $form->createView(),
]
);
}
/**
* @param Rollout $rollout
*/
private function disableRollouts(Rollout $rollout)
{
$entityManager = $this->getDoctrine()->getManager();
foreach ($rollout->getDeviceGroups() as $deviceGroup) {
foreach ($deviceGroup->getRollouts() as $rollout) {
$rollout->setDisabled(true);
$entityManager->persist($rollout);
}
}
$entityManager->flush();
}
/**
* @Route("/show_popup/{id}", name="rollout_show_popup", methods={"GET"})
* @IsGranted("ROLLOUT_SHOW", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @return Response
*/
public function showPopup(Request $request, Rollout $rollout): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
return $this->render(
'rollout/show.html.popup.twig',
[
'rollout' => $rollout,
]
);
}
/**
* @Route("/show/{id}", name="rollout_show", methods={"GET"})
* @IsGranted("ROLLOUT_SHOW", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @return Response
*/
public function show(Request $request, Rollout $rollout, RolloutRepository $rolloutRepository): Response
{
// if (!$request->isXmlHttpRequest()) {
// throw $this->createAccessDeniedException("Not an ajax request!");
// }
return $this->render(
'rollout/show.html.twig',
[
'rollout' => $rollout,
]
);
}
/**
* @Route("fetch/show/{id}/{nonce}", name="fetch_devices_for_rollout", methods={"POST"})
* @IsGranted("DEVICE_GROUP_VIEW")
* @param Request $request
* @param Rollout $rollout
* @param Datatable $datatable
* @param DeviceGroupRepository $deviceGroupRepository
* @param $nonce
* @return Response
*/
public function fetchDevicesForRollout(Request $request, Rollout $rollout, Datatable $datatable, DeviceRepository $deviceRepository, $nonce): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
$columns = [ // list column in order
'id' => ['alias' => 'd'],
'name' => ['alias' => 'd', 'twig' => 'rollout/datatable_device.link.twig'], // type link, badge or circular status twig
'macAddress' => ['alias' => 'd'],
// 'serialNumber' => ['alias' => 'd'],
'class' => ['alias' => 'd'],
'firmwareVersion' => ['alias' => 'd'],
'created' => ['alias' => 'd', 'twig' => 'components/datatable.datetime.twig'],
'isUsed' => ['alias' => 'd', 'twig' => 'rollout/datatable_is_used.twig'],
// zenner
// 'device_status' => ['alias' => 'd'],
// 'tenant' => ['alias' => 'd'],
// 'downlinkType' => ['alias' => 'd'],
];
$datatables = $datatable
->withColumnType($columns)
->withOptions(['rollout' => $rollout, 'nonce' => $nonce])
->renderActionView('rollout/datatable_device.action.twig') // provide custom twig to render action column
->withQueryBuilder($deviceRepository->getByRollout($rollout))
->isDevice(true)
->withRequestParams($request->request->all());
return new JsonResponse($datatables->getResponse());
}
/**
* @Route("/start/form/{id}", name="rollout_start_form", methods={"GET"})
* @IsGranted("ROLLOUT_EDIT", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @return Response
*/
public function startForm(Request $request, Rollout $rollout): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
return $this->render(
'rollout/start.html.twig',
[
'rollout' => $rollout,
]
);
}
/**
* @Route("/start/{id}", name="rollout_start", methods={"POST"})
* @IsGranted("ROLLOUT_EDIT", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @return Response
* @throws Exception
*/
public function start(Request $request, Rollout $rollout): Response
{
if ($this->isCsrfTokenValid(
'start' . $rollout->getId(),
$request->request->get('_token')
)) {
$entityManager = $this->getDoctrine()->getManager();
$dt = new DateTime();
$rollout->setStartDate($dt->modify("-1 minutes"));
$entityManager->persist($rollout);
$entityManager->flush();
}
return $this->redirectToRoute('rollout_index');
}
/**
* @Route("/clone/{id}/{type}", name="rollout_clone", methods={"GET"})
* @IsGranted("ROLLOUT_CLONE", subject="rollout")
* @param Rollout $rollout
* @param string $type
* @return Response
*/
public function clone(Rollout $rollout, string $type = "index"): Response
{
/** @var User $user */
$user = $this->getUser();
$entityManager = $this->getDoctrine()->getManager();
$newRollout = new Rollout();
$newRollout->setName($rollout->getName() . '-Clone');
$newRollout->setFile($rollout->getFile());
$newRollout->setMaintainer($rollout->getMaintainer());
$newRollout->setPriority($rollout->getPriority());
$newRollout->setStartDate($rollout->getStartDate());
if (count($rollout->getDeviceGroups()) > 0) {
foreach ($rollout->getDeviceGroups() as $deviceGroup) {
$newRollout->addDeviceGroup($deviceGroup);
}
}
/** @var Workspace $workspace */
$workspace = $user->getWorkspace();
$newRollout->setWorkspace($workspace);
$newRollout->setDisabled(true);
$entityManager->persist($newRollout);
$entityManager->flush();
if ($type === "running") {
return $this->redirectToRoute('rollout_list_running');
}
if ($type === "planned") {
return $this->redirectToRoute('rollout_list_planned');
}
return $this->redirectToRoute('rollout_index');
}
/**
* @Route("/edit/{id}/{nonce}/{type}", name="rollout_edit", methods={"GET","POST"})
* @IsGranted("ROLLOUT_EDIT", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @param TranslatorInterface $translator
* @param $nonce
* @param string $type
* @return Response
*/
public function edit(Request $request, Rollout $rollout, TranslatorInterface $translator, $nonce, string $type = "index"): Response
{
if ($request->isMethod('GET') && !$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
$form = $this->createForm(
RolloutType::class,
$rollout,
[
'action' => $this->generateUrl(
'rollout_edit',
[
'id' => $rollout->getId(),
'type' => $type,
'nonce' => $nonce
]
),
'method' => 'POST',
'dt_trans_format' => $translator->trans('datetime_formtype')
]
);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
if ($type === "running") {
return $this->redirectToRoute('rollout_list_running');
}
if ($type === "planned") {
return $this->redirectToRoute('rollout_list_planned');
}
return $this->redirectToRoute('rollout_index');
}
return $this->render(
'rollout/edit.html.twig',
[
'nonce' => urldecode(str_replace('?2F?', '%2F', $nonce)),
'rollout' => $rollout,
'form' => $form->createView(),
]
);
}
/**
* @Route("/delete/{id}/{type}", name="rollout_delete", methods={"DELETE"})
* @IsGranted("ROLLOUT_DELETE", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @param TranslatorInterface $translator
* @param string $type
* @return Response
*/
public function delete(Request $request, Rollout $rollout, TranslatorInterface $translator, string $type = "index"): Response
{
if ($this->isCsrfTokenValid(
'delete' . $rollout->getId(),
$request->request->get('_token')
)) {
if (count($rollout->getRolloutDevices()) === 0) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->remove($rollout);
$entityManager->flush();
} else {
$this->addFlash("success", $translator->trans("msg.record_used_not_deletable"));
}
}
if ($type === "running") {
return $this->redirectToRoute('rollout_list_running');
}
if ($type === "planned") {
return $this->redirectToRoute('rollout_list_planned');
}
return $this->redirectToRoute('rollout_index');
}
/**
* @Route("delete/form/{id}/{type}", name="rollout_delete_form", methods={"GET"})
* @IsGranted("ROLLOUT_DELETE", subject="rollout")
* @param Request $request
* @param Rollout $rollout
* @param string $type
* @return Response
*/
public function deleteForm(Request $request, Rollout $rollout, string $type = "index"): Response
{
if (!$request->isXmlHttpRequest()) {
throw $this->createAccessDeniedException("Not an ajax request!");
}
$form = $this->createFormBuilder()
->setAction(
$this->generateUrl(
'rollout_delete',
array(
'id' => $rollout->getId(),
'type' => $type,
)
)
)
->setMethod('DELETE')
->getForm();
return $this->render(
'rollout/delete.html.twig',
[
'rollout' => $rollout,
'form' => $form->createView(),
]
);
}
/**
* @Route("/get/files/{id}", name="rollout_get_files_in_file_group", methods={"POST"})
* @param FileGroup $fileGroup
* @return JsonResponse
* @throws ExceptionInterface
*/
public function fileList(FileGroup $fileGroup): JsonResponse
{
/** @var File $file */
foreach ($fileGroup->getFiles() as $file) {
if ($file->getTransactionId() === null) {
$fileGroup->removeFile($file);
}
}
$serializer = new Serializer([new ObjectNormalizer()]);
$data = $serializer->normalize(
$fileGroup,
'json',
[
AbstractNormalizer::ATTRIBUTES => [
'files' => [
'id',
'name',
],
],
]
);
return new JsonResponse(
$data,
200
);
}
/**
* @Route("/delete/rollouts", name="delete_rollouts", methods={"POST"})
* @IsGranted("ROLLOUT_MULTI_DELETE")
* @param Request $request
* @param RolloutRepository $rolloutRepository
* @return Response
*/
public function deleteRollouts(Request $request, RolloutRepository $rolloutRepository): Response
{
/** @var User $user */
$user = $this->getUser();
$data = explode(
',',
$request->request->get('data_ids')
);
$em = $this->getDoctrine()->getManager();
foreach ($data as $id) {
$rollout = $rolloutRepository->findOneBy(["id" => $id, "workspace" => $user->getWorkspace()->getId()]);
if (is_object($rollout)) {
$em->remove($rollout);
}
}
$em->flush();
return new Response('success');
}
}