Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'spread mark' support #43

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/langate/network/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
path("devices/whitelist/", views.DeviceWhitelist.as_view(), name="device-whitelist"),
path("marks/", views.MarkList.as_view(), name="mark-list"),
path("mark/<int:old>/move/<int:new>/", views.MarkMove.as_view(), name="mark-move"),
path("mark/<int:old>/spread/", views.MarkSpread.as_view(), name="mark-spread"),
path("games/", views.GameList.as_view(), name="game-list"),
path("userdevices/<int:pk>/", views.UserDeviceDetail.as_view(), name="user-device-detail"),
]
10 changes: 5 additions & 5 deletions backend/langate/network/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def generate_dev_name():
except FileNotFoundError:
return "MISSINGNO"

def get_mark(user=None):
def get_mark(user=None, excluded_marks=[]):
"""
Get a mark from the settings based on random probability
"""
Expand All @@ -40,23 +40,23 @@ def get_mark(user=None):
existing_marks = [
mark
for mark in SETTINGS["games"][user.tournament]
if mark in [x["value"] for x in SETTINGS["marks"]]
if mark in [x["value"] for x in SETTINGS["marks"]] and mark not in excluded_marks
]

mark_proba = [
mark_data["priority"]
for mark in existing_marks
for mark_data in SETTINGS["marks"]
if mark_data["value"] == mark
if mark_data["value"] == mark and mark_data["value"] not in excluded_marks
]

# Chose a random mark from the user's tournament based on the probability
return random.choices(existing_marks, weights=mark_proba)[0]

# Get a random mark from the settings based on the probability
return random.choices(
[mark["value"] for mark in SETTINGS["marks"]],
weights=[mark["priority"] for mark in SETTINGS["marks"]]
[mark["value"] for mark in SETTINGS["marks"] if mark["value"] not in excluded_marks],
weights=[mark["priority"] for mark in SETTINGS["marks"] if mark["value"] not in excluded_marks]
)[0]

def validate_marks(marks):
Expand Down
29 changes: 28 additions & 1 deletion backend/langate/network/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from langate.settings import SETTINGS
from langate.user.models import Role
from langate.network.models import Device, UserDevice, DeviceManager
from langate.network.utils import validate_marks, validate_games, save_settings
from langate.network.utils import validate_marks, validate_games, save_settings, get_mark

from langate.network.serializers import DeviceSerializer, UserDeviceSerializer, FullDeviceSerializer

Expand Down Expand Up @@ -352,6 +352,33 @@ def post(self, request, old, new):

return Response(status=status.HTTP_200_OK)

class MarkSpread(APIView):
"""
API endpoint that allows all devices on a mark to be moved to another mark.
"""

permission_classes = [StaffPermission]

def post(self, request, old):
"""
Move all devices on a mark to another mark
"""
# Check that the old and new marks are valid
marks = [m["value"] for m in SETTINGS["marks"]]

if old not in marks:
return Response({"error": _("Invalid origin mark")}, status=status.HTTP_400_BAD_REQUEST)

if sum([mark["priority"] for mark in SETTINGS["marks"] if mark["value"] != old]) == 0:
return Response({"error": _("No mark to spread to")}, status=status.HTTP_400_BAD_REQUEST)

devices = Device.objects.filter(mark=old, whitelisted=False)
for device in devices:
new = get_mark(excluded_marks=[old])
DeviceManager.edit_device(device, device.mac, device.name, new)

return Response(status=status.HTTP_200_OK)

class GameList(APIView):
"""
API endpoint that allows games to be viewed.
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable no-console */
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faArrowLeft, faArrowsAlt,
faArrowsRotate, faBolt, faChevronDown, faChevronUp,
faArrowLeft, faArrowsAlt, faArrowsRotate, faArrowsSplitUpAndLeft,
faBolt, faChevronDown, faChevronUp,
faCircle, faCircleCheck, faCirclePlus, faClock,
faCrown, faDownload, faEye, faEyeSlash,
faFile, faHammer, faKey, faLocationDot,
Expand Down Expand Up @@ -34,6 +34,7 @@ library.add(
faCircle,
faClock,
faEye,
faArrowsSplitUpAndLeft,
faEyeSlash,
faChevronDown,
faChevronUp,
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/stores/devices.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,27 @@ export const useDeviceStore = defineStore('device', () => {
}
}

async function spread_marks(oldMark: number): Promise<boolean> {
await get_csrf();

try {
await axios.post(`/network/mark/${oldMark}/spread/`, {}, {
headers: {
'X-CSRFToken': csrf.value,
'Content-Type': 'application/json',
},
withCredentials: true,
});
return true;
} catch (err) {
addNotification(
(err as AxiosError<{ error?: string }>).response?.data || 'An error occurred while spreading the marks',
'error',
);
return false;
}
}

async function fetch_game_marks(): Promise<boolean> {
try {
const response = await axios.get<GameMark>('/network/games/', { withCredentials: true });
Expand Down Expand Up @@ -287,6 +308,7 @@ export const useDeviceStore = defineStore('device', () => {
fetch_marks,
patch_marks,
move_marks,
spread_marks,
fetch_game_marks,
change_game_marks,
edit_own_device,
Expand Down
113 changes: 104 additions & 9 deletions frontend/src/views/Management/Marks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { addNotification } = useNotificationStore();
const deviceStore = useDeviceStore();

const {
fetch_marks, patch_marks, move_marks, fetch_game_marks, change_game_marks,
fetch_marks, patch_marks, move_marks, spread_marks, fetch_game_marks, change_game_marks,
} = deviceStore;
const { marks, gameMarks } = storeToRefs(deviceStore);

Expand Down Expand Up @@ -50,24 +50,46 @@ const reset = async () => {
edit.value = false;
};

// -- Move Modal --
// -- Move and Spread Modals --

const move = ref(false);
const currentMark = ref(0);

// -- Move Modal --

const showMoveModal = ref(false);
const chosenMark = ref(0);

const openModal = (selectedMark: number) => {
const openMoveModal = (selectedMark: number) => {
currentMark.value = selectedMark;
// set the chosen mark to the first mark in the list
chosenMark.value = marks.value[0].value;
move.value = true;
showMoveModal.value = true;
};

const validateMove = async () => {
if (await move_marks(currentMark.value, chosenMark.value)) {
addNotification('Les appareils ont bien été déplacés', 'info');
// close the modal
move.value = false;
showMoveModal.value = false;

// reload the marks to update the number of devices
await fetch_marks();
}
};

// -- Spread Modal --
const showSpreadModal = ref(false);

const openSpreadModal = (selectedMark: number) => {
currentMark.value = selectedMark;
showSpreadModal.value = true;
};

const validateSpread = async () => {
if (await spread_marks(currentMark.value)) {
addNotification('Les appareils ont bien été déplacés', 'info');
// close the modal
showSpreadModal.value = false;

// reload the marks to update the number of devices
await fetch_marks();
Expand Down Expand Up @@ -290,7 +312,7 @@ const submitGame = async () => {
<button
class="group rounded bg-blue-500 p-1 hover:bg-blue-600"
type="button"
@click="openModal(mark.value)"
@click="openMoveModal(mark.value)"
>
<fa-awesome-icon
icon="arrows-alt"
Expand All @@ -306,6 +328,26 @@ const submitGame = async () => {
Déplacer les appareils avec cette mark
</div>
</button>
<button
class="group rounded bg-blue-500 p-1 hover:bg-blue-600"
type="button"
@click="openSpreadModal(mark.value)"
>
<fa-awesome-icon
icon="arrows-split-up-and-left"
size="lg"
class="-scale-x-100"
/>
<div
class="pointer-events-none absolute right-[-40px] z-20 mr-10 mt-10 w-32 rounded bg-gray-800 p-2 text-xs text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100"
:class="{
'bottom-8': index === (edit ? marksCopy.length - 1 : marks.length - 1),
'top-0': index !== (edit ? marksCopy.length - 1 : marks.length - 1),
}"
>
Dispatcher les appareils avec cette mark sur les autres
</div>
</button>
</div>
</template>
</td>
Expand Down Expand Up @@ -364,7 +406,7 @@ const submitGame = async () => {

<!-- Mark move Modal -->
<div
v-if="move"
v-if="showMoveModal"
class="fixed inset-0 flex items-center justify-center bg-black/50"
>
<div
Expand Down Expand Up @@ -423,7 +465,60 @@ const submitGame = async () => {
<button
class="rounded-md bg-theme-nav px-4 py-2 text-white"
type="button"
@click="move = false"
@click="showMoveModal = false"
>
Annuler
</button>
<button
class="rounded-md bg-blue-700 px-4 py-2 text-white"
type="submit"
>
Valider
</button>
</div>
</form>
</div>
</div>

<!-- Mark spread Modal -->
<div
v-if="showSpreadModal"
class="fixed inset-0 flex items-center justify-center bg-black/50"
>
<div
class="w-1/2 rounded-lg bg-zinc-800 p-4"
>
<h2
class="text-center text-2xl font-bold text-white"
>
Déplacer les appareils
</h2>
<form
class="mt-4 flex flex-col gap-4"
@submit.prevent="validateSpread"
>
<div
class="flex flex-row items-center gap-2"
>
<div>
Déplacer les appareils avec la mark
</div>
<div
class="rounded-md bg-theme-nav p-1 font-bold text-white"
>
{{ currentMark }}
</div>
<div>
sur les autres marks (selon leur priorité)
</div>
</div>
<div
class="flex justify-end gap-4"
>
<button
class="rounded-md bg-theme-nav px-4 py-2 text-white"
type="button"
@click="showSpreadModal = false"
>
Annuler
</button>
Expand Down
Loading